diff --git a/AUTHORS.md b/AUTHORS.md index 32f1ade0..49cb022c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,15 +1,8 @@ # Digital Voice Modem Host -## Project Technical Leads - - Bryan Biedenkapp (https://github.com/gatekeep) - -## Developers - - Natalie Moore (https://github.com/jelimoore) -## All other contributors and their affiliations - - Build Chain and Helper Tools - K4YT3X (https://github.com/k4yt3x) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec94671d..d08f05fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,128 +26,6 @@ #*/ cmake_minimum_required(VERSION 3.16.0) -# -## dvmhost source/header files -# -file(GLOB dvmhost_SRC - # DMR module - "src/dmr/*.h" - "src/dmr/*.cpp" - "src/dmr/acl/*.h" - "src/dmr/acl/*.cpp" - "src/dmr/data/*.h" - "src/dmr/data/*.cpp" - "src/dmr/edac/*.h" - "src/dmr/edac/*.cpp" - "src/dmr/lc/*.h" - "src/dmr/lc/*.cpp" - "src/dmr/lc/csbk/*.h" - "src/dmr/lc/csbk/*.cpp" - "src/dmr/lookups/*.h" - "src/dmr/lookups/*.cpp" - "src/dmr/packet*.h" - "src/dmr/packet/*.cpp" - - # P25 module - "src/p25/*.h" - "src/p25/*.cpp" - "src/p25/acl/*.h" - "src/p25/acl/*.cpp" - "src/p25/data/*.h" - "src/p25/data/*.cpp" - "src/p25/dfsi/*.h" - "src/p25/dfsi/*.cpp" - "src/p25/dfsi/packet/*.h" - "src/p25/dfsi/packet/*.cpp" - "src/p25/edac/*.h" - "src/p25/edac/*.cpp" - "src/p25/lc/*.h" - "src/p25/lc/*.cpp" - "src/p25/lc/tdulc/*.h" - "src/p25/lc/tdulc/*.cpp" - "src/p25/lc/tsbk/*.h" - "src/p25/lc/tsbk/*.cpp" - "src/p25/lookups/*.h" - "src/p25/lookups/*.cpp" - "src/p25/packet/*.h" - "src/p25/packet/*.cpp" - - # NXDN module - "src/nxdn/*.h" - "src/nxdn/*.cpp" - "src/nxdn/acl/*.h" - "src/nxdn/acl/*.cpp" - "src/nxdn/channel/*.h" - "src/nxdn/channel/*.cpp" - "src/nxdn/edac/*.h" - "src/nxdn/edac/*.cpp" - "src/nxdn/lc/*.h" - "src/nxdn/lc/*.cpp" - "src/nxdn/lc/rcch/*.h" - "src/nxdn/lc/rcch/*.cpp" - "src/nxdn/packet/*.h" - "src/nxdn/packet/*.cpp" - - # Core - "src/edac/*.h" - "src/edac/*.cpp" - "src/edac/rs/*.h" - "src/host/*.h" - "src/host/*.cpp" - "src/host/calibrate/*.h" - "src/host/calibrate/*.cpp" - "src/host/setup/*.h" - "src/host/setup/*.cpp" - "src/host/fne/*.h" - "src/host/fne/*.cpp" - "src/lookups/*.h" - "src/lookups/*.cpp" - "src/modem/*.h" - "src/modem/*.cpp" - "src/modem/port/*.h" - "src/modem/port/*.cpp" - "src/network/*.h" - "src/network/*.cpp" - "src/network/fne/*.h" - "src/network/fne/*.cpp" - "src/network/json/*.h" - "src/network/rest/*.h" - "src/network/rest/*.cpp" - "src/network/rest/http/*.h" - "src/network/rest/http/*.cpp" - "src/remote/RESTClient.cpp" - "src/remote/RESTClient.h" - "src/yaml/*.h" - "src/yaml/*.cpp" - "src/*.h" - "src/*.cpp" -) - -# -## dvmcmd source/header files -# -file(GLOB dvmcmd_SRC - "src/network/UDPSocket.h" - "src/network/UDPSocket.cpp" - "src/network/RESTDefines.h" - "src/network/json/*.h" - "src/network/rest/*.h" - "src/network/rest/*.cpp" - "src/network/rest/http/*.h" - "src/network/rest/http/*.cpp" - "src/remote/*.h" - "src/remote/*.cpp" - "src/edac/SHA256.h" - "src/edac/SHA256.cpp" - "src/Defines.h" - "src/Thread.h" - "src/Thread.cpp" - "src/Log.h" - "src/Log.cpp" - "src/Utils.h" - "src/Utils.cpp" -) - # ## dvmtest source/header files # @@ -157,137 +35,21 @@ file(GLOB dvmtests_SRC "tests/p25/*.cpp" ) -# Digital mode options and other compilation features -option(ENABLE_DMR "Enable DMR Digtial Mode" on) -option(ENABLE_P25 "Enable P25 Digital Mode" on) -option(ENABLE_NXDN "Enable NXDN Digital Mode" on) -option(ENABLE_DFSI_SUPPORT "Enable P25 DFSI Transport Support" off) option(ENABLE_TESTS "Enable compilation of test suite" off) - -message(CHECK_START "DMR Digital Mode") -if (ENABLE_DMR) - add_definitions(-DENABLE_DMR) - message(CHECK_PASS "enabled") -else () - message(CHECK_PASS "disabled") -endif (ENABLE_DMR) -message(CHECK_START "P25 Digital Mode") -if (ENABLE_P25) - add_definitions(-DENABLE_P25) - message(CHECK_PASS "enabled") -else () - message(CHECK_PASS "disabled") -endif (ENABLE_P25) -message(CHECK_START "NXDN Digital Mode") -if (ENABLE_NXDN) - add_definitions(-DENABLE_NXDN) - message(CHECK_PASS "enabled") -else () - message(CHECK_PASS "disabled") -endif (ENABLE_NXDN) -message(CHECK_START "P25 DFSI Support") -if (ENABLE_DFSI_SUPPORT) - add_definitions(-DENABLE_DFSI_SUPPORT) - message(CHECK_PASS "enabled") -else () - message(CHECK_PASS "disabled") -endif (ENABLE_DFSI_SUPPORT) -message(CHECK_START "Enable test suite compilation") +message(CHECK_START "Enable compilation of test suite") if (ENABLE_TESTS) - message(CHECK_PASS "enabled") + message(CHECK_PASS "yes") else () - message(CHECK_PASS "disabled") + message(CHECK_PASS "no") endif (ENABLE_TESTS) -# Debug compilation features/options (these should not be enabled for production!) -option(DEBUG_DMR_PDU_DATA "" off) -option(DEBUG_CRC "" off) -option(DEBUG_RS "" off) -option(DEBUG_MODEM_CAL "" off) -option(DEBUG_MODEM "" off) -option(DEBUG_NXDN_FACCH1 "" off) -option(DEBUG_NXDN_SACCH "" off) -option(DEBUG_NXDN_UDCH "" off) -option(DEBUG_NXDN_LICH "" off) -option(DEBUG_NXDN_CAC "" off) -option(DEBUG_P25_PDU_DATA "" off) -option(DEBUG_P25_HDU "" off) -option(DEBUG_P25_LDU1 "" off) -option(DEBUG_P25_LDU2 "" off) -option(DEBUG_P25_TDULC "" off) -option(DEBUG_P25_TSBK "" off) -option(FORCE_TSBK_CRC_WARN "" off) -option(DEBUG_P25_DFSI "" off) -option(DEBUG_RINGBUFFER "" off) -option(DEBUG_HTTP_PAYLOAD "" off) -option(DEBUG_TRELLIS "" off) - -if (DEBUG_DMR_PDU_DATA) - add_definitions(-DDEBUG_DMR_PDU_DATA) -endif (DEBUG_DMR_PDU_DATA) -if (DEBUG_CRC_ADD) - add_definitions(-DDEBUG_CRC_ADD) -endif (DEBUG_CRC_ADD) -if (DEBUG_CRC_CHECK) - add_definitions(-DDEBUG_CRC_CHECK) -endif (DEBUG_CRC_CHECK) -if (DEBUG_RS) - add_definitions(-DDEBUG_RS) -endif (DEBUG_RS) -if (DEBUG_MODEM_CAL) - add_definitions(-DDEBUG_MODEM_CAL) -endif (DEBUG_MODEM_CAL) -if (DEBUG_MODEM) - add_definitions(-DDEBUG_MODEM) -endif (DEBUG_MODEM) -if (DEBUG_NXDN_FACCH1) - add_definitions(-DDEBUG_NXDN_FACCH1) -endif (DEBUG_NXDN_FACCH1) -if (DEBUG_NXDN_SACCH) - add_definitions(-DDEBUG_NXDN_SACCH) -endif (DEBUG_NXDN_SACCH) -if (DEBUG_NXDN_UDCH) - add_definitions(-DDEBUG_NXDN_UDCH) -endif (DEBUG_NXDN_UDCH) -if (DEBUG_NXDN_LICH) - add_definitions(-DDEBUG_NXDN_LICH) -endif (DEBUG_NXDN_LICH) -if (DEBUG_NXDN_CAC) - add_definitions(-DDEBUG_NXDN_CAC) -endif (DEBUG_NXDN_CAC) -if (DEBUG_P25_PDU_DATA) - add_definitions(-DDEBUG_P25_PDU_DATA) -endif (DEBUG_P25_PDU_DATA) -if (DEBUG_P25_HDU) - add_definitions(-DDEBUG_P25_HDU) -endif (DEBUG_P25_HDU) -if (DEBUG_P25_LDU1) - add_definitions(-DDEBUG_P25_LDU1) -endif (DEBUG_P25_LDU1) -if (DEBUG_P25_LDU2) - add_definitions(-DDEBUG_P25_LDU2) -endif (DEBUG_P25_LDU2) -if (DEBUG_P25_TDULC) - add_definitions(-DDEBUG_P25_TDULC) -endif (DEBUG_P25_TDULC) -if (DEBUG_P25_TSBK) - add_definitions(-DDEBUG_P25_TSBK) -endif (DEBUG_P25_TSBK) -if (FORCE_TSBK_CRC_WARN) - add_definitions(-DFORCE_TSBK_CRC_WARN) -endif (FORCE_TSBK_CRC_WARN) -if (DEBUG_P25_DFSI) - add_definitions(-DDEBUG_P25_DFSI) -endif (DEBUG_P25_DFSI) -if (DEBUG_RINGBUFFER) - add_definitions(-DDEBUG_RINGBUFFER) -endif (DEBUG_RINGBUFFER) -if (DEBUG_HTTP_PAYLOAD) - add_definitions(-DDEBUG_HTTP_PAYLOAD) -endif (DEBUG_HTTP_PAYLOAD) -if (DEBUG_TRELLIS) - add_definitions(-DDEBUG_TRELLIS) -endif (DEBUG_TRELLIS) +option(ENABLE_TUI_SUPPORT "Enable TUI support" on) +message(CHECK_START "Enable TUI support") +if (ENABLE_TUI_SUPPORT) + message(CHECK_PASS "yes") +else () + message(CHECK_PASS "no") +endif (ENABLE_TUI_SUPPORT) # Cross-compile options option(CROSS_COMPILE_ARM "Cross-compile for 32-bit ARM" off) @@ -299,25 +61,19 @@ set(CMAKE_CXX_COMPILER g++) set(ARCH amd64) set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE amd64) -message(CHECK_START "Cross compiling for 32-bit ARM") if (CROSS_COMPILE_ARM) set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++) set(ARCH arm) set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm) - message(CHECK_PASS "yes") -else () - message(CHECK_PASS "no") + message(CHECK_START "Cross compiling for 32-bit ARM - ${CMAKE_C_COMPILER}") endif (CROSS_COMPILE_ARM) -message(CHECK_START "Cross compiling for 64-bit ARM") if (CROSS_COMPILE_AARCH64) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) set(ARCH arm64) set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm64) - message(CHECK_PASS "yes") -else () - message(CHECK_PASS "no") + message(CHECK_START "Cross compiling for 64-bit ARM - ${CMAKE_C_COMPILER}") endif (CROSS_COMPILE_AARCH64) option(WITH_RPI_ARM_TOOLS "Specifies the location for the RPI ARM tools" off) @@ -326,7 +82,6 @@ if (WITH_RPI_ARM_TOOLS) message(CHECK_START "With RPi 1 Tools: ${RPI_ARM_TOOLS}") endif (WITH_RPI_ARM_TOOLS) -message(CHECK_START "Cross compiling for (old RPi) 32-bit ARM") if (CROSS_COMPILE_RPI_ARM) if (NOT WITH_RPI_ARM_TOOLS) message("-- Cloning legacy Raspberry Pi compilation toolchain") @@ -345,11 +100,46 @@ if (CROSS_COMPILE_RPI_ARM) set(ARCH armhf) set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf) - message(CHECK_PASS "yes") -else () - message(CHECK_PASS "no") + message(CHECK_START "Cross compiling for (old RPi) 32-bit ARM - ${CMAKE_C_COMPILER}") endif (CROSS_COMPILE_RPI_ARM) +# Standard CMake options +set(THREADS_PREFER_PTHREAD_FLAG ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY .) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) +endif(NOT CMAKE_BUILD_TYPE) +message(CHECK_START "Build Type is ${CMAKE_BUILD_TYPE}") +if (CMAKE_BUILD_TYPE MATCHES Debug) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -Wall -std=c++14") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -Wall -std=c++14") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -g -O0 -Wall -std=c++14") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 -Wall -std=c++14") +elseif(CMAKE_BUILD_TYPE MATCHES Release) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3 -Wall -std=c++14 -s") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3 -Wall -std=c++14 -s") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -g -O3 -Wall -std=c++14 -s") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O3 -Wall -std=c++14 -s") +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3 -Wall -std=c++14") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3 -Wall -std=c++14") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -g -O3 -Wall -std=c++14") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O3 -Wall -std=c++14") +endif() +if (CROSS_COMPILE_ARM) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-psabi") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wno-psabi") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-psabi") +endif (CROSS_COMPILE_ARM) + +set(CMAKE_INSTALL_PREFIX "/usr/local") + +# +# Library Inclusions +# option(WITH_ASIO "Manually specify the location for the ASIO library" off) if (WITH_ASIO) set(ASIO_INCLUDE_DIR ${WITH_ASIO}/include) @@ -365,132 +155,34 @@ else() set(ASIO_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/_deps/asio-src/asio/include) endif (WITH_ASIO) -# Standard CMake options -set(THREADS_PREFER_PTHREAD_FLAG ON) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY .) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3 -Wall -std=c++11") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3 -Wall -std=c++11") -set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -g -O3 -Wall -std=c++11 -s") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O3 -Wall -std=c++11 -s") -if (CROSS_COMPILE_ARM) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-psabi") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wno-psabi") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-psabi") -endif (CROSS_COMPILE_ARM) - -set(CMAKE_BUILD_TYPE "RelWithDebInfo") - -set(CMAKE_INSTALL_PREFIX "/usr/local") +if (ENABLE_TUI_SUPPORT) +message("-- Cloning finalcut") +Include(FetchContent) +FetchContent_Declare( + FINALCUT + GIT_REPOSITORY https://github.com/gatekeep/finalcut-cmake.git +) +set(F_COMPILE_STATIC 1) +FetchContent_MakeAvailable(FINALCUT) +set(FINALCUT_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/_deps/finalcut-src/src) +endif (ENABLE_TUI_SUPPORT) +# +# Set GIT_VER compiler directive +# set(GIT_VER "") set(GIT_VER_HASH "") -execute_process(COMMAND git describe --abbrev=8 --dirty --always --tags WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE GIT_VER OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND git describe --abbrev=8 --always --tags WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE GIT_VER_HASH OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git describe --abbrev=8 --dirty --always WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE GIT_VER OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git describe --abbrev=8 --always WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE GIT_VER_HASH OUTPUT_STRIP_TRAILING_WHITESPACE) add_definitions(-D__GIT_VER__="${GIT_VER}") add_definitions(-D__GIT_VER_HASH__="${GIT_VER_HASH}") -# -## dvmhost project -# project(dvmhost) -set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") -find_package(Threads REQUIRED) - -# add ASIO -add_library(asio::asio INTERFACE IMPORTED) -target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR}) -target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE") -target_link_libraries(asio::asio INTERFACE Threads::Threads) - -# Check if platform-specific functions exist -include(CheckCXXSymbolExists) -check_cxx_symbol_exists(sendmsg sys/socket.h HAVE_SENDMSG) -check_cxx_symbol_exists(sendmmsg sys/socket.h HAVE_SENDMMSG) - -if(HAVE_SENDMSG) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_SENDMSG=1") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_SENDMSG=1") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DHAVE_SENDMSG=1") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DHAVE_SENDMSG=1") -endif() -if(HAVE_SENDMMSG) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_SENDMMSG=1") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_SENDMMSG=1") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DHAVE_SENDMMSG=1") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DHAVE_SENDMMSG=1") -endif() - -add_executable(dvmhost ${dvmhost_SRC}) -target_include_directories(dvmhost PRIVATE src) -target_link_libraries(dvmhost PRIVATE asio::asio Threads::Threads util) - -set(CPACK_SET_DESTDIR true) -set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/local") - -set(CPACK_GENERATOR "DEB") -set(CPACK_PACKAGE_NAME "dvmhost") -set(CPACK_DEBIAN_PACKAGE_NAME "dvmhost") - -set(CPACK_PACKAGE_VENDOR "DVMProject") - -set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.") -set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors") -set(CPACK_DEBIAN_PACKAGE_VERSION "3.0.0") -set(CPACK_DEBIAN_PACKAGE_RELEASE "0") -set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject") - -set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA - "${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/debian/postrm") - -set(CPACK_DEBIAN_FILE_NAME ${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb) - -include(CPack) - -# -## dvmcmd project -# -project(dvmcmd) -set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") -find_package(Threads REQUIRED) - -# add ASIO -target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR}) -target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE") -target_link_libraries(asio::asio INTERFACE Threads::Threads) - -add_executable(dvmcmd ${dvmcmd_SRC}) -target_link_libraries(dvmcmd PRIVATE asio::asio Threads::Threads) -target_include_directories(dvmcmd PRIVATE src) +include(src/CMakeLists.txt) if (ENABLE_TESTS) -# -## dvmtest project -# -project(dvmtest) -set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") -Include(FetchContent) - -FetchContent_Declare( - Catch2 - GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.0.1 # or a later release -) - -FetchContent_MakeAvailable(Catch2) -find_package(Threads REQUIRED) - -# add ASIO -target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR}) -target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE") -target_link_libraries(asio::asio INTERFACE Threads::Threads) - -add_executable(dvmtests ${dvmhost_SRC} ${dvmtests_SRC}) -target_compile_definitions(dvmtests PUBLIC -DCATCH2_TEST_COMPILATION) -target_link_libraries(dvmtests PRIVATE Catch2::Catch2WithMain asio::asio Threads::Threads util) -target_include_directories(dvmtests PRIVATE .) +include(tests/CMakeLists.txt) endif (ENABLE_TESTS) # @@ -498,13 +190,13 @@ endif (ENABLE_TESTS) # install(TARGETS dvmhost DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS dvmcmd DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) -install(FILES configs/config.example.yml configs/iden_table.dat configs/RSSI.dat configs/rid_acl.example.dat configs/tg_acl.example.dat DESTINATION ${CMAKE_INSTALL_PREFIX}/etc) +install(FILES configs/config.example.yml configs/fne-config.example.yml configs/iden_table.dat configs/RSSI.dat configs/rid_acl.example.dat configs/talkgroup_rules.example.yml DESTINATION ${CMAKE_INSTALL_PREFIX}/etc) install(PROGRAMS tools/start-dvm.sh tools/stop-dvm.sh tools/dvm-watchdog.sh tools/stop-watchdog.sh DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/filePath: ./filePath: \\\\/var\\\\/log\\\\//' /usr/local/etc/config.example.yml\")") install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/activityFilePath: ./activityFilePath: \\\\/var\\\\/log\\\\//' /usr/local/etc/config.example.yml\")") install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/file: iden_table.dat/file: \\\\/usr\\\\/local\\\\/etc\\\\/iden_table.dat/' /usr/local/etc/config.example.yml\")") install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/file: rid_acl.dat/file: \\\\/usr\\\\/local\\\\/etc\\\\/rid_acl.dat/' /usr/local/etc/config.example.yml\")") -install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/file: tg_acl.dat/file: \\\\/usr\\\\/local\\\\/etc\\\\/tg_acl.dat/' /usr/local/etc/config.example.yml\")") +install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/file: talkgroup_rules.yml/file: \\\\/usr\\\\/local\\\\/etc\\\\/talkgroup_rules.yml/' /usr/local/etc/config.example.yml\")") # # Helper target to force strip binaries. @@ -532,7 +224,7 @@ add_custom_target(tarball COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp ../tools/*.sh ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm COMMAND chmod +x ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/*.sh - COMMAND cp -v ../configs/config*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm + COMMAND cp -v ../configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm COMMAND cp -v ../configs/*.dat ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm COMMAND cd ${CMAKE_INSTALL_PREFIX_TARBALL} && tar czvf ../dvmhost_${CPACK_DEBIAN_PACKAGE_VERSION}_${ARCH}.tar.gz * COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL}) @@ -552,10 +244,11 @@ add_custom_target(old_install COMMAND install -m 755 dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 755 dvmcmd ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 644 ../configs/config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/config.example.yml + COMMAND install -m 644 ../configs/fne-config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/fne-config.example.yml COMMAND install -m 644 ../configs/iden_table.dat ${CMAKE_LEGACY_INSTALL_PREFIX}/iden_table.dat COMMAND install -m 644 ../configs/RSSI.dat ${CMAKE_LEGACY_INSTALL_PREFIX}/RSSI.dat COMMAND install -m 644 ../configs/rid_acl.example.dat ${CMAKE_LEGACY_INSTALL_PREFIX}/rid_acl.dat - COMMAND install -m 644 ../configs/tg_acl.example.dat ${CMAKE_LEGACY_INSTALL_PREFIX}/tg_acl.dat + COMMAND install -m 644 ../configs/talkgroup_rules.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/talkgroup_rules.example.yml COMMAND install -m 755 ../tools/start-dvm.sh ${CMAKE_LEGACY_INSTALL_PREFIX} COMMAND install -m 755 ../tools/stop-dvm.sh ${CMAKE_LEGACY_INSTALL_PREFIX} COMMAND install -m 755 ../tools/dvm-watchdog.sh ${CMAKE_LEGACY_INSTALL_PREFIX} @@ -572,10 +265,11 @@ add_custom_target(old_install-service COMMAND useradd --user-group -M --system dvmhost --shell /bin/false || true COMMAND usermod --groups dialout --append dvmhost || true COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/config.example.yml + COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/fne-config.example.yml COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/iden_table.dat COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/RSSI.dat COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/rid_acl.dat - COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/tg_acl.dat + COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/talkgroup_rules.example.yml COMMAND chown dvmhost:dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/log COMMAND cp ../linux/dvmhost.service /lib/systemd/system/ COMMAND bash \"-c\" \"sed -i 's/\\\\/usr\\\\/local\\\\/bin/\\\\/opt\\\\/dvm\\\\/bin/' /lib/systemd/system/dvmhost.service\" diff --git a/README.md b/README.md index 57692d80..8366ccfb 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,24 @@ Please feel free to reach out to us for help, comments or otherwise, on our Disc This project utilizes CMake for its build system. (All following information assumes familiarity with the standard Linux make system.) -The DVM Host software requires the library dependancies below. Generally, the software attempts to be as portable as possible and as library-free as possible. A basic GCC/G++ install is usually all thats needed to compile. +The DVM Host software requires the library dependancies below. Generally, the software attempts to be as portable as possible and as library-free as possible. A basic GCC/G++ install, with libasio and ncurses is usually all that is needed to compile. ### Dependencies -This project requires the ASIO library (https://think-async.com/Asio/) for its REST API services. This can be installed on most Debian/Ubuntu Linux's with: `apt-get install libasio-dev` +`apt-get install libasio-dev libncurses-dev` + +- ASIO Library (https://think-async.com/Asio/); on Debian/Ubuntu Linux's: `apt-get install libasio-dev` +- ncurses; on Debian/Ubuntu Linux's: ``apt-get install libncurses-dev` Alternatively, if you download the ASIO library from the ASIO website and extract it to a location, you can specify the path to the ASIO library using: `-DWITH_ASIO=/path/to/asio`. This method is required when cross-compiling for old Raspberry Pi ARM 32 bit. +If cross-compiling ensure you install the appropriate libraries, for example for AARCH64/ARM64: +``` +sudo dpkg --add-architecture arm64 +sudo apt-get update +sudo apt-get install libasio-dev:arm64 libncurses-dev:arm64 +``` + ### Build Instructions 1. Clone the repository. `git clone https://github.com/DVMProject/dvmhost.git` @@ -45,6 +55,13 @@ Please note cross-compliation requires you to have the appropriate development p [See build notes](#build-notes). +### Setup TUI (Text-based User Interface) + +Since, DVM Host 3.5, the old calibration and setup modes have been deprecated in favor of a ncurses-based TUI. This TUI is optional, and DVM Host can still be compiled without it for systems or devices that cannot utilize it. + +- `-DENABLE_SETUP_TUI=0` - This will disable the setup/calibration TUI interface. +- `-DENABLE_TUI_SUPPORT=0` - This will disable TUI support project wide. Any projects that require TUI support will not compile, or will have any TUI components disabled. + ### Compiled Protocol Options These are the protocols that are compiled-in to the host for data processing. By default, support for both DMR and P25 protocols are enabled. And, support for the NXDN protocol is disabled. What "compiled-in" support means is whether or not the host will perform _any_ processing for the specified protocol (and this is regardless of whether or not the `config.yml` has a protocol specified for being enabled or not). @@ -106,23 +123,33 @@ M: ... (HOST) Channel Id 2: BaseFrequency = 450000000Hz, TXOffsetMhz = 5.000000M ## Command Line Parameters ``` -usage: ./dvmhost [-vh] [-f] [--cal] [--setup] [-c ] [--remote [-a
] [-p ]] +usage: ./dvmhost [-vhf] [--setup] [--fne] [-c ] [--remote [-a
] [-p ]] + -v show version information + -h show this screen -f foreground mode - --cal calibration mode + --setup setup mode + --fne fixed network equipment mode (conference bridge) + -c specifies the configuration file to use --remote remote modem mode -a remote modem command address -p remote modem command port - -v show version information - -h show this screen -- stop handling options ``` +## Embedded FNE Mode + +DVMHost contains its own "embedded FNE" or "mini-FNE", which is a simple conference bridge style FNE that can be activated using the `--fne` command line options. This FNE mode does not use the standard DVMHost configuration file and uses its own configuration file (see `fne-config.example.yml`). + +The "embedded FNE" is a simplistic FNE, meant for simple single-master small-scale deployments. It, like the full-scale FNE, defines rules for available talkgroups and manages calls. Unlike the full-scale FNE, the "embedded FNE" does not have multi-system routing or support multiple masters. It can peer to other FNEs, however, unlike full-scale FNE the "embedded FNE" does not have provisioning for talkgroup mutuation (i.e. talkgroup number rewriting, where on System A TG123 routes to System B TG456), all TGs must be one to one across peers. + +The "embedded FNE" is meant as an easier alternative to a full-scale FNE where complex routing or multiple masters are not required. + ## Build Notes - The installation path of "/opt/dvm" is still supported by the CMake Makefile (and will be for the forseeable future); after compiling, in order to install to this path simply use: `make old_install`. diff --git a/configs/config.example.yml b/configs/config.example.yml index 44940022..ea7a3977 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -43,20 +43,25 @@ network: port: 62031 # FNE access password. password: "PASSWORD" + # Maximum allowable DMR network jitter. jitter: 360 + # Flag indicating whether DMR slot 1 traffic will be passed. slot1: true # Flag indicating whether DMR slot 2 traffic will be passed. slot2: true + # Flag indicating whether the local host lookup tables (RID, TGID, etc) will be updated from the network. updateLookups: false # Flag indicating whether or not the host activity log will be sent to the network. allowActivityTransfer: true # Flag indicating whether or not the host diagnostic log will be sent to the network. allowDiagnosticTransfer: true + # Flag indicating whether or not verbose debug logging is enabled. debug: false + # Flag indicating whether or not REST API is enabled. restEnable: false # IP address of the network interface to listen for REST API on (or 0.0.0.0 for all). @@ -319,6 +324,19 @@ system: # Channel Number (used to calculate actual host frequency based on the identity table). channelNo: 1 + # + # Control Channel + # Note: These parameters are used by a voice channel to communicate back to a + # control channel to give the control channel "realtime" traffic channel updates. + # + controlCh: + # REST API IP Address for control channel. + restAddress: 127.0.0.1 + # REST API Port number for control channel. + restPort: 9990 + # REST API access password for control channel. + restPassword: "PASSWORD" + # # Voice Channels # @@ -390,6 +408,12 @@ system: # Amount of packet correlations that should occur before P25 data is returned from the modem to the host. # (Note: Changing this value will impact P25 protocol stability, and should not be altered.) p25CorrCount: 8 + # Size (in bytes) of the DMR transmit FIFO buffer. + dmrFifoLength: 505 + # Size (in bytes) of the P25 transmit FIFO buffer. + p25FifoLength: 442 + # Size (in bytes) of the NXDN transmit FIFO buffer. + nxdnFifoLength: 538 # # Hotspot Modem Configuration @@ -506,9 +530,9 @@ system: # Radio ID ACL Configuration # radio_id: - # Full path to the identity table file. + # Full path to the RID ACL file. file: rid_acl.dat - # Amount of time between updates of identity table file. (minutes) + # Amount of time between updates of RID ACL file. (minutes) time: 2 # Flag indicating whether or not RID ACLs are enforced. acl: false @@ -517,9 +541,9 @@ system: # Talkgroupd ID ACL Configuration # talkgroup_id: - # Full path to the identity table file. - file: tg_acl.dat - # Amount of time between updates of identity table file. (minutes) + # Full path to the talkgroup rules file. + file: talkgroup_rules.yml + # Amount of time between updates of talkgroup rules file. (minutes) time: 2 # Flag indicating whether or not TGID ACLs are enforced. acl: false diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml new file mode 100644 index 00000000..be7524ca --- /dev/null +++ b/configs/fne-config.example.yml @@ -0,0 +1,131 @@ +# +# Digital Voice Modem - Host Software Configuration (FNE Conference Bridge Mode) +# +# @package DVM / Host Software +# + +# Flag indicating whether the host will run as a background or foreground task. +daemon: true + +# +# Logging Configuration +# Logging Levels: +# 1 - Debug +# 2 - Message +# 3 - Informational +# 4 - Warning +# 5 - Error +# 6 - Fatal +# +log: + # Console display logging level (used when in foreground). + displayLevel: 1 + # File logging level. + fileLevel: 1 + # Full path for the directory to store the log files. + filePath: . + # Full path for the directory to store the activity log files. + activityFilePath: . + # Log filename prefix. + fileRoot: DVM + +# +# Master +# +master: + # Network Peer ID + peerId: 9000100 + # Hostname/IP address to listen on (blank for all). + address: 0.0.0.0 + # Port number to listen on. + port: 62031 + # FNE access password. + password: RPT1234 + # Flag indicating whether or not verbose logging is enabled. + verbose: true + # Flag indicating whether or not verbose debug logging is enabled. + debug: false + + # Flag indicating whether or not DMR traffic will be passed. + allowDMRTraffic: true + # Flag indicating whether or not P25 traffic will be passed. + allowP25Traffic: true + # Flag indicating whether or not NXDN traffic will be passed. + allowNXDNTraffic: true + + # Delay from when a call on a parrot TG ends to when the playback starts (in milliseconds). + parrotDelay: 2000 + + # + # Talkgroup Rules Configuration + # + talkgroup_rules: + # Full path to the talkgroup rules file. + file: talkgroup_rules.yml + # Amount of time between updates of talkgroup rules file. (minutes) + time: 30 + +# +# Peers +# +peers: + - name: PARROT + # Flag indicating whether or not the peer is enabled. + enabled: true + # Hostname/IP address to listen on (blank for all). + address: 127.0.0.1 + # Port number to listen on. + port: 32091 + # Hostname/IP address of the FNE master to connect to. + masterAddress: 127.0.0.1 + # Port number of the FNE master to connect to. + masterPort: 32090 + # FNE access password. + password: RPT1234 + # Textual identity of this peer. + identity: PARROT + # Network Peer ID + peerId: 9000990 + + # + rxFrequency: 0 + # + txFrequency: 0 + # Latitude. + latitude: 0.0 + # Longitude. + longitude: 0.0 + # Textual location for this host. + location: Anywhere, USA + + # Flag indicating whether or not verbose debug logging is enabled. + debug: false + +# +# System Configuration +# +system: + # Time in seconds between pings to peers. + pingTime: 5 + # Maximum number of missable pings before a peer is considered disconnected. + maxMissedPings: 5 + + # Time in minutes between updates of the talkgroup rules. + tgRuleUpdateTime: 10 + + # Flag indicating the TGID information for this master will be sent to its peers. + sendTalkgroups: true + + # Flag indicating whether or not the host activity log will be sent to the network. + allowActivityTransfer: true + # Flag indicating whether or not the host diagnostic log will be sent to the network. + allowDiagnosticTransfer: true + + # + # Radio ID ACL Configuration + # + radio_id: + # Full path to the identity table file. + file: rid_acl.dat + # Amount of time between updates of identity table file. (minutes) + time: 2 diff --git a/configs/talkgroup_rules.example.yml b/configs/talkgroup_rules.example.yml new file mode 100644 index 00000000..e112281d --- /dev/null +++ b/configs/talkgroup_rules.example.yml @@ -0,0 +1,131 @@ +# +# Digital Voice Modem - Talkgroup Rules +# +# @package DVM / Host Software +# + +# +# Talkgroup Rules +# +groupVoice: + # Textual name of the talkgroup. + - name: Talkgroup 1 + # + # Talkgroup Configuration + # + config: + # Flag indicating whether this talkgroup is active or not. + active: true + # Flag indicating whether this talkgroup requires affiliations to repeat traffic. + affiliated: false + + # List of peer IDs included for this talkgroup (peers listed here will be selected for traffic). + inclusion: [] + # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). + exclusion: [] + # + # Source Configuration + # + source: + # Numerical talkgroup ID number. + tgid: 1 + # DMR slot number. + slot: 1 + + # Textual name of the talkgroup. + - name: Parrot + # + # Talkgroup Configuration + # + config: + # Flag indicating whether this talkgroup is active or not. + active: true + # Flag indicating whether this talkgroup requires affiliations to repeat traffic. + affiliated: false + # Flag indicating whether or not this talkgroup is a parrot talkgroup. + parrot: true + + # List of peer IDs included for this talkgroup (peers listed here will be selected for traffic). + inclusion: [] + # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). + exclusion: [] + # + # Source Configuration + # + source: + # Numerical talkgroup ID number. + tgid: 9990 + # DMR slot number. + slot: 1 + + # Textual name of the talkgroup. + - name: System Wide P25 + # + # Talkgroup Configuration + # + config: + # Flag indicating whether this talkgroup is active or not. + active: true + # Flag indicating whether this talkgroup requires affiliations to repeat traffic. + affiliated: false + + # List of peer IDs included for this talkgroup (peers listed here will be selected for traffic). + inclusion: [] + # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). + exclusion: [] + # + # Source Configuration + # + source: + # Numerical talkgroup ID number. + tgid: 65535 + # DMR slot number. + slot: 1 + + # Textual name of the talkgroup. + - name: System Wide DMR TS1 + # + # Talkgroup Configuration + # + config: + # Flag indicating whether this talkgroup is active or not. + active: true + # Flag indicating whether this talkgroup requires affiliations to repeat traffic. + affiliated: false + + # List of peer IDs included for this talkgroup (peers listed here will be selected for traffic). + inclusion: [] + # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). + exclusion: [] + # + # Source Configuration + # + source: + # Numerical talkgroup ID number. + tgid: 16777215 + # DMR slot number. + slot: 1 + + # Textual name of the talkgroup. + - name: System Wide DMR TS2 + # + # Talkgroup Configuration + # + config: + # Flag indicating whether this talkgroup is active or not. + active: true + # Flag indicating whether this talkgroup requires affiliations to repeat traffic. + affiliated: false + + # List of peer IDs included for this talkgroup (peers listed here will be selected for traffic). + inclusion: [] + # List of peer IDs excluded for this talkgroup (peers listed here will be ignored for traffic). + exclusion: [] + # + # Source Configuration + # + source: + # Numerical talkgroup ID number. + tgid: 16777215 + # DMR slot number. + slot: 2 diff --git a/configs/tg_acl.example.dat b/configs/tg_acl.example.dat deleted file mode 100644 index 36dd6744..00000000 --- a/configs/tg_acl.example.dat +++ /dev/null @@ -1,6 +0,0 @@ -# -# This file sets the valid Talkgroup IDs allowed on a repeater. -# -# TGID,Enabled (1 = Enabled / 0 = Disabled),Slot (0 = Both, 1 = Slot 1, 2 = Slot 2), -16777215,1,0, -65535,1,0, diff --git a/contrib/vscode/launch.json b/contrib/vscode/launch.json new file mode 100644 index 00000000..f474745d --- /dev/null +++ b/contrib/vscode/launch.json @@ -0,0 +1,25 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": "Debug", + "type": "cppdbg", + "request": "launch", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/build/dvmhost", + "args": ["-c", "./config.yml", "-f"], + "cwd": "${workspaceFolder}/build", + "stopAtEntry": false, + "logging": { + "moduleLoad": true, + "trace": true + } + }, + ] +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..e2404b40 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,360 @@ +#/** +#* Digital Voice Modem - Host Software +#* GPLv2 Open Source. Use is subject to license terms. +#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +#* +#* @package DVM / Host Software +#* +#*/ +#/* +#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +#* Copyright (C) 2022 by Natalie Moore +#* +#* This program is free software; you can redistribute it and/or modify +#* it under the terms of the GNU General Public License as published by +#* the Free Software Foundation; either version 2 of the License, or +#* (at your option) any later version. +#* +#* This program is distributed in the hope that it will be useful, +#* but WITHOUT ANY WARRANTY; without even the implied warranty of +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#* GNU General Public License for more details. +#* +#* You should have received a copy of the GNU General Public License +#* along with this program; if not, write to the Free Software +#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +#*/ +# +## dvmhost source/header files +# +file(GLOB dvmhost_SRC + # DMR module + "src/dmr/*.h" + "src/dmr/*.cpp" + "src/dmr/acl/*.h" + "src/dmr/acl/*.cpp" + "src/dmr/data/*.h" + "src/dmr/data/*.cpp" + "src/dmr/edac/*.h" + "src/dmr/edac/*.cpp" + "src/dmr/lc/*.h" + "src/dmr/lc/*.cpp" + "src/dmr/lc/csbk/*.h" + "src/dmr/lc/csbk/*.cpp" + "src/dmr/lookups/*.h" + "src/dmr/lookups/*.cpp" + "src/dmr/packet*.h" + "src/dmr/packet/*.cpp" + + # P25 module + "src/p25/*.h" + "src/p25/*.cpp" + "src/p25/acl/*.h" + "src/p25/acl/*.cpp" + "src/p25/data/*.h" + "src/p25/data/*.cpp" + "src/p25/dfsi/*.h" + "src/p25/dfsi/*.cpp" + "src/p25/dfsi/packet/*.h" + "src/p25/dfsi/packet/*.cpp" + "src/p25/edac/*.h" + "src/p25/edac/*.cpp" + "src/p25/lc/*.h" + "src/p25/lc/*.cpp" + "src/p25/lc/tdulc/*.h" + "src/p25/lc/tdulc/*.cpp" + "src/p25/lc/tsbk/*.h" + "src/p25/lc/tsbk/*.cpp" + "src/p25/lookups/*.h" + "src/p25/lookups/*.cpp" + "src/p25/packet/*.h" + "src/p25/packet/*.cpp" + + # NXDN module + "src/nxdn/*.h" + "src/nxdn/*.cpp" + "src/nxdn/acl/*.h" + "src/nxdn/acl/*.cpp" + "src/nxdn/channel/*.h" + "src/nxdn/channel/*.cpp" + "src/nxdn/edac/*.h" + "src/nxdn/edac/*.cpp" + "src/nxdn/lc/*.h" + "src/nxdn/lc/*.cpp" + "src/nxdn/lc/rcch/*.h" + "src/nxdn/lc/rcch/*.cpp" + "src/nxdn/packet/*.h" + "src/nxdn/packet/*.cpp" + + # Core + "src/edac/*.h" + "src/edac/*.cpp" + "src/edac/rs/*.h" + "src/host/*.h" + "src/host/*.cpp" + "src/host/calibrate/*.h" + "src/host/calibrate/*.cpp" + "src/host/setup/*.h" + "src/host/setup/*.cpp" + "src/host/fne/*.h" + "src/host/fne/*.cpp" + "src/lookups/*.h" + "src/lookups/*.cpp" + "src/modem/*.h" + "src/modem/*.cpp" + "src/modem/port/*.h" + "src/modem/port/*.cpp" + "src/network/*.h" + "src/network/*.cpp" + "src/network/fne/*.h" + "src/network/fne/*.cpp" + "src/network/json/*.h" + "src/network/rest/*.h" + "src/network/rest/*.cpp" + "src/network/rest/http/*.h" + "src/network/rest/http/*.cpp" + "src/remote/RESTClient.cpp" + "src/remote/RESTClient.h" + "src/yaml/*.h" + "src/yaml/*.cpp" + "src/*.h" + "src/*.cpp" +) + +# +## dvmcmd source/header files +# +file(GLOB dvmcmd_SRC + "src/network/UDPSocket.h" + "src/network/UDPSocket.cpp" + "src/network/RESTDefines.h" + "src/network/json/*.h" + "src/network/rest/*.h" + "src/network/rest/*.cpp" + "src/network/rest/http/*.h" + "src/network/rest/http/*.cpp" + "src/remote/*.h" + "src/remote/*.cpp" + "src/edac/SHA256.h" + "src/edac/SHA256.cpp" + "src/Defines.h" + "src/Thread.h" + "src/Thread.cpp" + "src/Log.h" + "src/Log.cpp" + "src/Utils.h" + "src/Utils.cpp" +) + +# Digital mode options and other compilation features +option(ENABLE_DMR "Enable DMR Digtial Mode" on) +if (ENABLE_DMR) + add_definitions(-DENABLE_DMR) + message(CHECK_START "DMR Digital Mode - enabled") +else () + message(CHECK_START "DMR Digital Mode - disabled") +endif (ENABLE_DMR) + +option(ENABLE_P25 "Enable P25 Digital Mode" on) +if (ENABLE_P25) + add_definitions(-DENABLE_P25) + message(CHECK_START "P25 Digital Mode - enabled") +else () + message(CHECK_START "P25 Digital Mode - disabled") +endif (ENABLE_P25) + +option(ENABLE_NXDN "Enable NXDN Digital Mode" on) +if (ENABLE_NXDN) + add_definitions(-DENABLE_NXDN) + message(CHECK_START "NXDN Digital Mode - enabled") +else () + message(CHECK_START "NXDN Digital Mode - disabled") +endif (ENABLE_NXDN) + +option(ENABLE_DFSI_SUPPORT "Enable P25 DFSI Transport Support" off) +if (ENABLE_DFSI_SUPPORT) + add_definitions(-DENABLE_DFSI_SUPPORT) + message(CHECK_START "P25 DFSI Support - enabled") +endif (ENABLE_DFSI_SUPPORT) + +if (ENABLE_TUI_SUPPORT) + option(ENABLE_SETUP_TUI "Enable interactive setup TUI" on) + if (ENABLE_SETUP_TUI) + add_definitions(-DENABLE_SETUP_TUI) + message(CHECK_START "Interactive Setup TUI - enabled") + endif (ENABLE_SETUP_TUI) +else() + set(ENABLE_SETUP_TUI off) +endif (ENABLE_TUI_SUPPORT) + +# Debug compilation features/options (these should not be enabled for production!) +option(DEBUG_DMR_PDU_DATA "" off) +option(DEBUG_CRC "" off) +option(DEBUG_RS "" off) +option(DEBUG_MODEM_CAL "" off) +option(DEBUG_MODEM "" off) +option(DEBUG_NXDN_FACCH1 "" off) +option(DEBUG_NXDN_SACCH "" off) +option(DEBUG_NXDN_UDCH "" off) +option(DEBUG_NXDN_LICH "" off) +option(DEBUG_NXDN_CAC "" off) +option(DEBUG_P25_PDU_DATA "" off) +option(DEBUG_P25_HDU "" off) +option(DEBUG_P25_LDU1 "" off) +option(DEBUG_P25_LDU2 "" off) +option(DEBUG_P25_TDULC "" off) +option(DEBUG_P25_TSBK "" off) +option(FORCE_TSBK_CRC_WARN "" off) +option(DEBUG_P25_DFSI "" off) +option(DEBUG_RINGBUFFER "" off) +option(DEBUG_HTTP_PAYLOAD "" off) +option(DEBUG_TRELLIS "" off) + +if (DEBUG_DMR_PDU_DATA) + add_definitions(-DDEBUG_DMR_PDU_DATA) +endif (DEBUG_DMR_PDU_DATA) +if (DEBUG_CRC_ADD) + add_definitions(-DDEBUG_CRC_ADD) +endif (DEBUG_CRC_ADD) +if (DEBUG_CRC_CHECK) + add_definitions(-DDEBUG_CRC_CHECK) +endif (DEBUG_CRC_CHECK) +if (DEBUG_RS) + add_definitions(-DDEBUG_RS) +endif (DEBUG_RS) +if (DEBUG_MODEM_CAL) + add_definitions(-DDEBUG_MODEM_CAL) +endif (DEBUG_MODEM_CAL) +if (DEBUG_MODEM) + add_definitions(-DDEBUG_MODEM) +endif (DEBUG_MODEM) +if (DEBUG_NXDN_FACCH1) + add_definitions(-DDEBUG_NXDN_FACCH1) +endif (DEBUG_NXDN_FACCH1) +if (DEBUG_NXDN_SACCH) + add_definitions(-DDEBUG_NXDN_SACCH) +endif (DEBUG_NXDN_SACCH) +if (DEBUG_NXDN_UDCH) + add_definitions(-DDEBUG_NXDN_UDCH) +endif (DEBUG_NXDN_UDCH) +if (DEBUG_NXDN_LICH) + add_definitions(-DDEBUG_NXDN_LICH) +endif (DEBUG_NXDN_LICH) +if (DEBUG_NXDN_CAC) + add_definitions(-DDEBUG_NXDN_CAC) +endif (DEBUG_NXDN_CAC) +if (DEBUG_P25_PDU_DATA) + add_definitions(-DDEBUG_P25_PDU_DATA) +endif (DEBUG_P25_PDU_DATA) +if (DEBUG_P25_HDU) + add_definitions(-DDEBUG_P25_HDU) +endif (DEBUG_P25_HDU) +if (DEBUG_P25_LDU1) + add_definitions(-DDEBUG_P25_LDU1) +endif (DEBUG_P25_LDU1) +if (DEBUG_P25_LDU2) + add_definitions(-DDEBUG_P25_LDU2) +endif (DEBUG_P25_LDU2) +if (DEBUG_P25_TDULC) + add_definitions(-DDEBUG_P25_TDULC) +endif (DEBUG_P25_TDULC) +if (DEBUG_P25_TSBK) + add_definitions(-DDEBUG_P25_TSBK) +endif (DEBUG_P25_TSBK) +if (FORCE_TSBK_CRC_WARN) + add_definitions(-DFORCE_TSBK_CRC_WARN) +endif (FORCE_TSBK_CRC_WARN) +if (DEBUG_P25_DFSI) + add_definitions(-DDEBUG_P25_DFSI) +endif (DEBUG_P25_DFSI) +if (DEBUG_RINGBUFFER) + add_definitions(-DDEBUG_RINGBUFFER) +endif (DEBUG_RINGBUFFER) +if (DEBUG_HTTP_PAYLOAD) + add_definitions(-DDEBUG_HTTP_PAYLOAD) +endif (DEBUG_HTTP_PAYLOAD) +if (DEBUG_TRELLIS) + add_definitions(-DDEBUG_TRELLIS) +endif (DEBUG_TRELLIS) + +# +## dvmhost project +# +project(dvmhost) +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") +find_package(Threads REQUIRED) + +# add ASIO +add_library(asio::asio INTERFACE IMPORTED) +target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR}) +target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE") +target_link_libraries(asio::asio INTERFACE Threads::Threads) + +if (ENABLE_SETUP_TUI) + # add finalcut + target_include_directories(finalcut INTERFACE ${FINALCUT_INCLUDE_DIR}) +endif (ENABLE_SETUP_TUI) + +# Check if platform-specific functions exist +include(CheckCXXSymbolExists) +check_cxx_symbol_exists(sendmsg sys/socket.h HAVE_SENDMSG) +check_cxx_symbol_exists(sendmmsg sys/socket.h HAVE_SENDMMSG) + +if (HAVE_SENDMSG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_SENDMSG=1") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_SENDMSG=1") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DHAVE_SENDMSG=1") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DHAVE_SENDMSG=1") +endif (HAVE_SENDMSG) +if (HAVE_SENDMMSG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_SENDMMSG=1") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_SENDMMSG=1") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DHAVE_SENDMMSG=1") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DHAVE_SENDMMSG=1") +endif (HAVE_SENDMMSG) + +add_executable(dvmhost ${dvmhost_SRC}) +target_include_directories(dvmhost PRIVATE src) +if (ENABLE_SETUP_TUI) + target_link_libraries(dvmhost PRIVATE asio::asio finalcut Threads::Threads util) +else() + target_link_libraries(dvmhost PRIVATE asio::asio Threads::Threads util) +endif (ENABLE_SETUP_TUI) + +set(CPACK_SET_DESTDIR true) +set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/local") + +set(CPACK_GENERATOR "DEB") +set(CPACK_PACKAGE_NAME "dvmhost") +set(CPACK_DEBIAN_PACKAGE_NAME "dvmhost") + +set(CPACK_PACKAGE_VENDOR "DVMProject") + +set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors") +set(CPACK_DEBIAN_PACKAGE_VERSION "3.0.0") +set(CPACK_DEBIAN_PACKAGE_RELEASE "0") +set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject") + +set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + "${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/debian/postrm") + +set(CPACK_DEBIAN_FILE_NAME ${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb) + +include(CPack) + +# +## dvmcmd project +# +project(dvmcmd) +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") +find_package(Threads REQUIRED) + +# add ASIO +target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR}) +target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE") +target_link_libraries(asio::asio INTERFACE Threads::Threads) + +add_executable(dvmcmd ${dvmcmd_SRC}) +target_link_libraries(dvmcmd PRIVATE asio::asio Threads::Threads) +target_include_directories(dvmcmd PRIVATE src) diff --git a/src/Clock.cpp b/src/Clock.cpp index ea974cac..4bbd9847 100644 --- a/src/Clock.cpp +++ b/src/Clock.cpp @@ -20,6 +20,7 @@ */ #include "Defines.h" #include "Clock.h" +#include "Log.h" using namespace system_clock; @@ -45,7 +46,7 @@ static const uint64_t NTP_SCALE_FRAC = 4294967296ULL; static inline uint32_t ntpDiffMS(uint64_t older, uint64_t newer) { if (older > newer) { - // LOG_ERROR("Older timestamp is actually newer"); + // LogError(LOG_HOST, "Older timestamp is actually newer"); } uint32_t s1 = (older >> 32) & 0xffffffff; @@ -56,7 +57,7 @@ static inline uint32_t ntpDiffMS(uint64_t older, uint64_t newer) uint64_t r = (((uint64_t)(s2 - s1) * 1000000) + ((us2 - us1))) / 1000; if (r > UINT32_MAX) { - // LOG_ERROR("NTP difference is too large: %llu. Limiting value", r); + // LogError(LOG_HOST, "NTP difference is too large: %llu. Limiting value", r); r = UINT32_MAX; } @@ -70,11 +71,7 @@ static inline uint32_t ntpDiffMS(uint64_t older, uint64_t newer) uint64_t ntp::now() { struct timeval tv; -#ifdef _WIN32 gettimeofday(&tv, NULL); -#else - gettimeofday(&tv, NULL); -#endif uint64_t tv_ntp = tv.tv_sec + EPOCH; uint64_t tv_usecs = (uint64_t)((float)(NTP_SCALE_FRAC * tv.tv_usec) / 1000000.f); @@ -158,9 +155,9 @@ uint64_t hrc::diffNowUS(hrc::hrc_t& then) /// /// /// -uint64_t msToJiffies(uint64_t ms) +uint64_t system_clock::msToJiffies(uint64_t ms) { - return (uint64_t)(((double)ms/1000)* 65536); + return (uint64_t)(((double)ms / 1000) * 65536); } /// @@ -168,45 +165,7 @@ uint64_t msToJiffies(uint64_t ms) /// /// /// -uint64_t jiffiesToMs(uint64_t jiffies) +uint64_t system_clock::jiffiesToMs(uint64_t jiffies) { return (uint64_t)(((double)jiffies / 65536) * 1000); } - -#ifdef _WIN32 -/// -/// -/// -/// -/// -/// -int gettimeofday(struct timeval* tp, struct timezone* tzp) -{ - if (tzp != nullptr) { - // LOG_ERROR("Timezone not supported"); - return -1; - } - - // https://stackoverflow.com/questions/10905892/equivalent-of-gettimeday-for-windows - - // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's - // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) - // until 00:00:00 January 1, 1970 - static const uint64_t epoch = ((uint64_t) 116444736000000000ULL); - - // TODO: Why do we have two epochs defined? - - SYSTEMTIME system_time; - FILETIME file_time; - uint64_t time; - - GetSystemTime(&system_time); - SystemTimeToFileTime(&system_time, &file_time); - time = ((uint64_t)file_time.dwLowDateTime); - time += ((uint64_t)file_time.dwHighDateTime) << 32; - - tp->tv_sec = (long)((time - epoch) / 10000000L); - tp->tv_usec = (long)(system_time.wMilliseconds * 1000); - return 0; -} -#endif \ No newline at end of file diff --git a/src/Clock.h b/src/Clock.h index 2c709c2f..060ce8e5 100644 --- a/src/Clock.h +++ b/src/Clock.h @@ -23,12 +23,7 @@ #include "Defines.h" -#ifdef _WIN32 -#include -#else #include -#endif - #include namespace system_clock @@ -67,11 +62,6 @@ namespace system_clock uint64_t msToJiffies(uint64_t ms); /// uint64_t jiffiesToMs(uint64_t jiffies); - -#ifdef _WIN32 - /// - int gettimeofday(struct timeval *tp, struct timezone *tzp); -#endif } // namespace system_clock #endif // __CLOCK_H__ \ No newline at end of file diff --git a/src/Defines.h b/src/Defines.h index 459ee724..10608b10 100644 --- a/src/Defines.h +++ b/src/Defines.h @@ -109,26 +109,24 @@ typedef unsigned long long ulong64_t; #define __PROG_NAME__ "Digital Voice Modem (DVM) Host" #define __NET_NAME__ "DVM_DMR_P25" #define __EXE_NAME__ "dvmhost" -#define __VER__ "D03.00.00 (" __GIT_VER__ ")" +#define __VER__ "D03.50.00 (" __GIT_VER__ ")" #define __BUILD__ __DATE__ " " __TIME__ #define HOST_SW_API -#if defined(_WIN32) || defined(_WIN64) #define DEFAULT_CONF_FILE "config.yml" -#else -#define DEFAULT_CONF_FILE "/opt/dvm/config.yml" -#endif // defined(_WIN32) || defined(_WIN64) -#if defined(_WIN32) || defined(_WIN64) -#define DEFAULT_LOCK_FILE "dvm.lock" -#else #define DEFAULT_LOCK_FILE "/tmp/dvm.lock" -#endif // defined(_WIN32) || defined(_WIN64) #if defined(__GNUC__) || defined(__GNUG__) #define __forceinline __attribute__((always_inline)) #endif +#if defined(__MINGW32__) || defined(__MINGW64__) || defined(__GNUC__) || defined(__GNUG__) +#define PACK(decl) decl __attribute__((__packed__)) +#else +#define PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop)) +#endif + #define NULL_PORT "null" #define UART_PORT "uart" #define PTY_PORT "pty" @@ -287,6 +285,11 @@ inline std::string strtoupper(const std::string value) { std::unique_ptr name = std::unique_ptr(new type[length]); \ ::memset(name.get(), 0x00U, length); +typedef std::unique_ptr UInt8Array; + +/// Creates a named uint8_t array buffer. +#define __UNIQUE_UINT8_ARRAY(name, length) __UNIQUE_BUFFER(name, uint8_t, length) + /** * Class Copy Code Pattern */ @@ -323,6 +326,10 @@ inline std::string strtoupper(const std::string value) { #define __PROTECTED_READONLY_PROPERTY(type, variableName, propName) \ protected: type m_##variableName; \ public: __forceinline type get##propName(void) const { return m_##variableName; } +/// Creates a read-only get property, does not use "get"/"set". +#define __PROTECTED_READONLY_PROPERTY_PLAIN(type, variableName, propName) \ + protected: type m_##variableName; \ + public: __forceinline type propName(void) const { return m_##variableName; } /// Creates a read-only get property, does not use "get". #define __READONLY_PROPERTY_PLAIN(type, variableName, propName) \ private: type m_##variableName; \ diff --git a/src/HostMain.cpp b/src/HostMain.cpp index cd3489d3..a3b26385 100644 --- a/src/HostMain.cpp +++ b/src/HostMain.cpp @@ -33,6 +33,7 @@ #include "host/Host.h" #include "host/calibrate/HostCal.h" #include "host/setup/HostSetup.h" +#include "host/fne/HostFNE.h" #include "Log.h" using namespace network; @@ -43,13 +44,11 @@ using namespace lookups; #include #include -#if !defined(_WIN32) && !defined(_WIN64) #include #include #include #include #include -#endif // --------------------------------------------------------------------------- // Constants @@ -84,6 +83,7 @@ using namespace lookups; int g_signal = 0; bool g_calibrate = false; bool g_setup = false; +bool g_fne = false; std::string g_progExe = std::string(__EXE_NAME__); std::string g_iniFile = std::string(DEFAULT_CONF_FILE); std::string g_lockFile = std::string(DEFAULT_LOCK_FILE); @@ -105,7 +105,11 @@ uint8_t* g_gitHashBytes = nullptr; // Global Functions // --------------------------------------------------------------------------- -#if !defined(_WIN32) && !defined(_WIN64) && !defined(CATCH2_TEST_COMPILATION) +#if !defined(CATCH2_TEST_COMPILATION) +/// +/// Internal signal handler. +/// +/// static void sigHandler(int signum) { g_killed = true; @@ -113,6 +117,11 @@ static void sigHandler(int signum) } #endif +/// +/// Helper to print a fatal error message and exit. +/// +/// This is a variable argument function. +/// Message. void fatal(const char* msg, ...) { char buffer[400U]; @@ -129,6 +138,11 @@ void fatal(const char* msg, ...) exit(EXIT_FAILURE); } +/// +/// Helper to pring usage the command line arguments. (And optionally an error.) +/// +/// Error message. +/// Error message arguments. void usage(const char* message, const char* arg) { ::fprintf(stdout, __PROG_NAME__ " %s (" DESCR_DMR DESCR_P25 DESCR_NXDN "CW Id, Network) (built %s)\n", __VER__, __BUILD__); @@ -140,10 +154,20 @@ void usage(const char* message, const char* arg) ::fprintf(stderr, "\n\n"); } - ::fprintf(stdout, "usage: %s [-vh] [-f] [--cal] [--setup] [-c ] [--remote [-a
] [-p ]]\n\n" + ::fprintf(stdout, + "usage: %s [-vhf]" + "[--setup]" + "[--fne]" + "[-c ]" + "[--remote [-a
] [-p ]]" + "\n\n" + " -v show version information\n" + " -h show this screen\n" " -f foreground mode\n" - " --cal calibration mode\n" - " --setup setup mode\n" + "\n" + " --setup setup and calibration mode\n" + "\n" + " --fne fixed network equipment mode (conference bridge)\n" "\n" " -c specifies the configuration file to use\n" "\n" @@ -151,13 +175,17 @@ void usage(const char* message, const char* arg) " -a remote modem command address\n" " -p remote modem command port\n" "\n" - " -v show version information\n" - " -h show this screen\n" " -- stop handling options\n", g_progExe.c_str()); exit(EXIT_FAILURE); } +/// +/// Helper to validate the command line arguments. +/// +/// Argument count. +/// Array of argument strings. +/// Count of remaining unprocessed arguments. int checkArgs(int argc, char* argv[]) { int i, p = 0; @@ -183,7 +211,14 @@ int checkArgs(int argc, char* argv[]) g_calibrate = true; } else if (IS("--setup")) { +#if defined(ENABLE_SETUP_TUI) g_setup = true; +#else + g_calibrate = true; +#endif // defined(ENABLE_SETUP_TUI) + } + else if (IS("--fne")) { + g_fne = true; } else if (IS("-c")) { if (argc-- <= 0) @@ -270,34 +305,47 @@ int main(int argc, char** argv) } } -#if !defined(_WIN32) && !defined(_WIN64) ::signal(SIGINT, sigHandler); ::signal(SIGTERM, sigHandler); ::signal(SIGHUP, sigHandler); -#endif int ret = 0; do { g_signal = 0; - if (g_calibrate || g_setup) { - if (g_setup) { - HostSetup* setup = new HostSetup(g_iniFile); - ret = setup->run(); - delete setup; + if (g_fne) { + HostFNE *fne = new HostFNE(g_iniFile); + ret = fne->run(); + delete fne; + } + else { + if (g_calibrate || g_setup) { +#if defined(ENABLE_SETUP_TUI) + if (g_setup) { + HostSetup* setup = new HostSetup(g_iniFile); + ret = setup->run(argc, argv); + delete setup; + } + else { + HostCal* cal = new HostCal(g_iniFile); + ret = cal->run(argc, argv); + delete cal; + } +#else + if (g_calibrate) { + HostCal* cal = new HostCal(g_iniFile); + ret = cal->run(argc, argv); + delete cal; + } +#endif // defined(ENABLE_SETUP_TUI) } else { - HostCal* cal = new HostCal(g_iniFile); - ret = cal->run(); - delete cal; + Host* host = new Host(g_iniFile); + ret = host->run(); + delete host; } } - else { - Host* host = new Host(g_iniFile); - ret = host->run(); - delete host; - } if (g_signal == 2) ::LogInfoEx(LOG_HOST, "Exited on receipt of SIGINT"); diff --git a/src/Log.cpp b/src/Log.cpp index e705acf6..5061d457 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -31,12 +31,7 @@ #include "Log.h" #include "network/Network.h" -#if defined(_WIN32) || defined(_WIN64) -#define WIN32_LEAN_AND_MEAN -#include -#else #include -#endif #if defined(CATCH2_TEST_COMPILATION) #include @@ -53,11 +48,7 @@ // Constants // --------------------------------------------------------------------------- -#if defined(_WIN32) || defined(_WIN64) -#define EOL "\n" -#else #define EOL "\r\n" -#endif const uint32_t ACT_LOG_BUFFER_LEN = 501U; const uint32_t LOG_BUFFER_LEN = 4096U; @@ -77,12 +68,14 @@ static network::Network* m_network; static FILE* m_fpLog = nullptr; static FILE* m_actFpLog = nullptr; -static uint32_t m_displayLevel = 2U; -static bool m_disableTimeDisplay = false; +uint32_t g_logDisplayLevel = 2U; +bool g_disableTimeDisplay = false; static struct tm m_tm; static struct tm m_actTm; +static std::ostream m_outStream{std::cerr.rdbuf()}; + static char LEVELS[] = " DMIWEF"; // --------------------------------------------------------------------------- @@ -116,11 +109,8 @@ static bool LogOpen() } char filename[200U]; -#if defined(_WIN32) || defined(_WIN64) - ::sprintf(filename, "%s\\%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); -#else ::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); -#endif + m_fpLog = ::fopen(filename, "a+t"); m_tm = *tm; @@ -148,17 +138,23 @@ static bool ActivityLogOpen() } char filename[200U]; -#if defined(_WIN32) || defined(_WIN64) - ::sprintf(filename, "%s\\%s-%04d-%02d-%02d.activity.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); -#else ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); -#endif + m_actFpLog = ::fopen(filename, "a+t"); m_actTm = *tm; return m_actFpLog != nullptr; } +/// +/// Internal helper to set an output stream to direct logging to. +/// +/// +void __InternalOutputStream(std::ostream& stream) +{ + m_outStream.rdbuf(stream.rdbuf()); +} + /// /// Sets the instance of the Network class to transfer the activity log with. /// @@ -205,6 +201,7 @@ void ActivityLogFinalise() /// /// Writes a new entry to the activity log. /// +/// This is a variable argument function. /// Digital mode (usually P25 or DMR). /// Flag indicating that the entry was generated from an RF event. /// Formatted string to write to activity log. @@ -217,19 +214,17 @@ void ActivityLog(const char *mode, const bool sourceRf, const char* msg, ...) assert(msg != nullptr); char buffer[ACT_LOG_BUFFER_LEN]; -#if defined(_WIN32) || defined(_WIN64) - SYSTEMTIME st; - ::GetSystemTime(&st); - - ::sprintf(buffer, "A: %04u-%02u-%02u %02u:%02u:%02u.%03u %s %s ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, mode, (sourceRf) ? "RF" : "Net"); -#else struct timeval now; ::gettimeofday(&now, NULL); struct tm* tm = ::gmtime(&now.tv_sec); - ::sprintf(buffer, "A: %04d-%02d-%02d %02d:%02d:%02d.%03lu %s %s ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000U, mode, (sourceRf) ? "RF" : "Net"); -#endif + if (strcmp(mode, "") == 0) { + ::sprintf(buffer, "A: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000U); + } + else { + ::sprintf(buffer, "A: %04d-%02d-%02d %02d:%02d:%02d.%03lu %s %s ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000U, mode, (sourceRf) ? "RF" : "Net"); + } va_list vl; va_start(vl, msg); @@ -258,7 +253,7 @@ void ActivityLog(const char *mode, const bool sourceRf, const char* msg, ...) ::fflush(m_fpLog); } - if (2U >= m_displayLevel && m_displayLevel != 0U) { + if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) { ::fprintf(stdout, "%s" EOL, buffer); ::fflush(stdout); } @@ -277,8 +272,8 @@ bool LogInitialise(const std::string& filePath, const std::string& fileRoot, uin m_filePath = filePath; m_fileRoot = fileRoot; m_fileLevel = fileLevel; - m_displayLevel = displayLevel; - m_disableTimeDisplay = disableTimeDisplay; + g_logDisplayLevel = displayLevel; + g_disableTimeDisplay = disableTimeDisplay; return ::LogOpen(); } @@ -297,38 +292,18 @@ void LogFinalise() /// /// Writes a new entry to the diagnostics log. /// +/// This is a variable argument function. /// Log level. /// Module name the log entry was genearted from. -/// Formatted string to write to activity log. +/// Formatted string to write to the log. void Log(uint32_t level, const char *module, const char* fmt, ...) { assert(fmt != nullptr); #if defined(CATCH2_TEST_COMPILATION) - m_disableTimeDisplay = true; + g_disableTimeDisplay = true; #endif char buffer[LOG_BUFFER_LEN]; -#if defined(_WIN32) || defined(_WIN64) - if (!m_disableTimeDisplay) { - SYSTEMTIME st; - ::GetSystemTime(&st); - - if (module != nullptr) { - ::sprintf(buffer, "%c: %04u-%02u-%02u %02u:%02u:%02u.%03u (%s) ", LEVELS[level], st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, module); - } - else { - ::sprintf(buffer, "%c: %04u-%02u-%02u %02u:%02u:%02u.%03u ", LEVELS[level], st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); - } - } - else { - if (module != nullptr) { - ::sprintf(buffer, "%c: (%s) ", LEVELS[level], module); - } - else { - ::sprintf(buffer, "%c: ", LEVELS[level]); - } - } -#else - if (!m_disableTimeDisplay) { + if (!g_disableTimeDisplay) { struct timeval now; ::gettimeofday(&now, NULL); @@ -346,10 +321,14 @@ void Log(uint32_t level, const char *module, const char* fmt, ...) ::sprintf(buffer, "%c: (%s) ", LEVELS[level], module); } else { - ::sprintf(buffer, "%c: ", LEVELS[level]); + if (level >= 9999U) { + ::sprintf(buffer, "U: "); + } + else { + ::sprintf(buffer, "%c: ", LEVELS[level]); + } } } -#endif va_list vl; va_start(vl, fmt); @@ -358,6 +337,10 @@ void Log(uint32_t level, const char *module, const char* fmt, ...) va_end(vl); + if (m_outStream && g_logDisplayLevel == 0U) { + m_outStream << buffer << std::endl; + } + if (m_network != nullptr) { // don't transfer debug data... if (level > 1U) { @@ -379,12 +362,13 @@ void Log(uint32_t level, const char *module, const char* fmt, ...) ::fflush(m_fpLog); } - if (level >= m_displayLevel && m_displayLevel != 0U) { + if (level >= g_logDisplayLevel && g_logDisplayLevel != 0U) { ::fprintf(stdout, "%s" EOL, buffer); ::fflush(stdout); } - if (level >= 6U) { // Fatal + // fatal error (specially allow any log levels above 9999) + if (level >= 6U && level < 9999U) { ::fclose(m_fpLog); exit(1); } diff --git a/src/Log.h b/src/Log.h index 3094f12f..6874558c 100644 --- a/src/Log.h +++ b/src/Log.h @@ -62,9 +62,19 @@ #define LogError(_module, fmt, ...) Log(5U, _module, fmt, ##__VA_ARGS__) #define LogFatal(_module, fmt, ...) Log(6U, _module, fmt, ##__VA_ARGS__) +// --------------------------------------------------------------------------- +// Externs +// --------------------------------------------------------------------------- + +extern uint32_t g_logDisplayLevel; +extern bool g_disableTimeDisplay; + // --------------------------------------------------------------------------- // Global Functions // --------------------------------------------------------------------------- +/// Internal helper to set an output stream to direct logging to. +extern HOST_SW_API void __InternalOutputStream(std::ostream& stream); + /// Sets the instance of the Network class to transfer the activity log with. extern HOST_SW_API void LogSetNetwork(void* network); diff --git a/src/StopWatch.cpp b/src/StopWatch.cpp index c68a6f7d..93a99abd 100644 --- a/src/StopWatch.cpp +++ b/src/StopWatch.cpp @@ -29,75 +29,13 @@ */ #include "StopWatch.h" -#if !defined(_WIN32) || !defined(_WIN64) #include #include -#endif // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- -#if defined(_WIN32) || defined(_WIN64) -/// -/// Initializes a new instance of the StopWatch class. -/// -StopWatch::StopWatch() : - m_frequencyS(), - m_frequencyMS(), - m_start() -{ - ::QueryPerformanceFrequency(&m_frequencyS); - - m_frequencyMS.QuadPart = m_frequencyS.QuadPart / 1000ULL; -} - -/// -/// Finalizes a instance of the StopWatch class. -/// -StopWatch::~StopWatch() -{ - /* stub */ -} - -/// -/// Gets the current running time. -/// -/// -ulong64_t StopWatch::time() const -{ - LARGE_INTEGER now; - ::QueryPerformanceCounter(&now); - - return (ulong64_t)(now.QuadPart / m_frequencyMS.QuadPart); -} - -/// -/// Starts the stopwatch. -/// -/// -ulong64_t StopWatch::start() -{ - ::QueryPerformanceCounter(&m_start); - - return (ulong64_t)(m_start.QuadPart / m_frequencyS.QuadPart); -} - -/// -/// Gets the elpased time since the stopwatch started. -/// -/// -uint32_t StopWatch::elapsed() -{ - LARGE_INTEGER now; - ::QueryPerformanceCounter(&now); - - LARGE_INTEGER temp; - temp.QuadPart = (now.QuadPart - m_start.QuadPart) * 1000; - - return (uint32_t)(temp.QuadPart / m_frequencyS.QuadPart); -} -#else /// /// Initializes a new instance of the StopWatch class. /// @@ -154,4 +92,3 @@ uint32_t StopWatch::elapsed() return nowMS - m_startMS; } -#endif diff --git a/src/StopWatch.h b/src/StopWatch.h index 3e2262c0..92196208 100644 --- a/src/StopWatch.h +++ b/src/StopWatch.h @@ -32,12 +32,7 @@ #include "Defines.h" -#if defined(_WIN32) || defined(_WIN64) -#define WIN32_LEAN_AND_MEAN -#include -#else #include -#endif // --------------------------------------------------------------------------- // Class Declaration @@ -60,13 +55,7 @@ public: uint32_t elapsed(); private: -#if defined(_WIN32) || defined(_WIN64) - LARGE_INTEGER m_frequencyS; - LARGE_INTEGER m_frequencyMS; - LARGE_INTEGER m_start; -#else ulong64_t m_startMS; -#endif }; #endif // __STOPWATCH_H__ diff --git a/src/Thread.cpp b/src/Thread.cpp index cc0f375b..a5bf9119 100644 --- a/src/Thread.cpp +++ b/src/Thread.cpp @@ -29,62 +29,12 @@ */ #include "Thread.h" -#if !defined(_WIN32) && !defined(_WIN64) #include -#endif // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- -#if defined(_WIN32) || defined(_WIN64) -/// -/// Initializes a new instance of the Thread class. -/// -Thread::Thread() : - m_handle() -{ - /* stub */ -} - -/// -/// Finalizes a instance of the Thread class. -/// -Thread::~Thread() -{ - /* stub */ -} - -/// -/// Starts the thread execution. -/// -/// True, if thread started, otherwise false. -bool Thread::run() -{ - m_handle = ::CreateThread(NULL, 0, &helper, this, 0, NULL); - - return m_handle != nullptr; -} - -/// -/// -/// -void Thread::wait() -{ - ::WaitForSingleObject(m_handle, INFINITE); - - ::CloseHandle(m_handle); -} - -/// -/// -/// -/// -void Thread::sleep(uint32_t ms) -{ - ::Sleep(ms); -} -#else /// /// Initializes a new instance of the Thread class. /// @@ -127,27 +77,11 @@ void Thread::sleep(uint32_t ms) { ::usleep(ms * 1000); } -#endif // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- -#if defined(_WIN32) || defined(_WIN64) -/// -/// -/// -/// -/// -DWORD Thread::helper(LPVOID arg) -{ - Thread* p = (Thread*)arg; - - p->entry(); - - return 0UL; -} -#else /// /// /// @@ -161,4 +95,3 @@ void* Thread::helper(void* arg) return nullptr; } -#endif diff --git a/src/Thread.h b/src/Thread.h index a9f20b8a..c58f3c32 100644 --- a/src/Thread.h +++ b/src/Thread.h @@ -32,12 +32,7 @@ #include "Defines.h" -#if defined(_WIN32) || defined(_WIN64) -#define WIN32_LEAN_AND_MEAN -#include -#else #include -#endif // --------------------------------------------------------------------------- // Class Declaration @@ -64,19 +59,10 @@ public: static void sleep(uint32_t ms); private: -#if defined(_WIN32) || defined(_WIN64) - HANDLE m_handle; -#else pthread_t m_thread; -#endif -#if defined(_WIN32) || defined(_WIN64) - /// - static DWORD __stdcall helper(LPVOID arg); -#else /// static void* helper(void* arg); -#endif }; #endif // __THREAD_H__ diff --git a/src/dmr/Control.cpp b/src/dmr/Control.cpp index 9397e6d5..b8c35ff3 100644 --- a/src/dmr/Control.cpp +++ b/src/dmr/Control.cpp @@ -55,7 +55,7 @@ using namespace dmr; /// Instance of the BaseNetwork class. /// Flag indicating full-duplex operation. /// Instance of the RadioIdLookup class. -/// Instance of the TalkgroupIdLookup class. +/// Instance of the TalkgroupRulesLookup class. /// Instance of the IdenTableLookup class. /// Instance of the RSSIInterpolator class. /// @@ -65,8 +65,8 @@ using namespace dmr; /// Flag indicating whether DMR debug is enabled. /// Flag indicating whether DMR verbose logging is enabled. Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly, - bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex, - ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, + bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex, + ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose) : m_authoritative(authoritative), m_supervisor(false), @@ -120,13 +120,14 @@ Control::~Control() /// Flag indicating whether the DMR has supervisory functions. /// Voice Channel Number list. /// Voice Channel data map. +/// Control Channel data. /// DMR Network ID. /// DMR Site ID. /// Channel ID. /// Channel Number. /// void Control::setOptions(yaml::Node& conf, bool supervisor, const std::vector voiceChNo, const std::unordered_map voiceChData, - uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions) + ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; yaml::Node dmrProtocol = conf["protocols"]["dmr"]; @@ -152,7 +153,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::vector(false); @@ -346,22 +347,7 @@ uint32_t Control::getFrame(uint32_t slotNo, uint8_t* data) void Control::clock(uint32_t ms) { if (m_network != nullptr) { - data::Data data; - bool ret = m_network->readDMR(data); - if (ret) { - uint32_t slotNo = data.getSlotNo(); - switch (slotNo) { - case 1U: - m_slot1->processNetwork(data); - break; - case 2U: - m_slot2->processNetwork(data); - break; - default: - LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); - break; - } - } + processNetwork(); } m_tsccCntInterval.clock(ms); @@ -421,6 +407,46 @@ void Control::permittedTG(uint32_t dstId, uint8_t slot) } } +/// +/// Releases a granted TG. +/// +/// +/// +void Control::releaseGrantTG(uint32_t dstId, uint8_t slot) +{ + switch (slot) { + case 1U: + m_slot1->releaseGrantTG(dstId); + break; + case 2U: + m_slot2->releaseGrantTG(dstId); + break; + default: + LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot); + break; + } +} + +/// +/// Touchs a granted TG to keep a channel grant alive. +/// +/// +/// +void Control::touchGrantTG(uint32_t dstId, uint8_t slot) +{ + switch (slot) { + case 1U: + m_slot1->touchGrantTG(dstId); + break; + case 2U: + m_slot2->touchGrantTG(dstId); + break; + default: + LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot); + break; + } +} + /// /// Gets instance of the AffiliationLookup class. /// @@ -597,3 +623,96 @@ void Control::setCSBKVerbose(bool verbose) m_dumpCSBKData = verbose; lc::CSBK::setVerbose(verbose); } + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Process a data frames from the network. +/// +void Control::processNetwork() +{ + uint32_t length = 0U; + bool ret = false; + UInt8Array buffer = m_network->readDMR(ret, length); + if (!ret) + return; + if (length == 0U) + return; + if (buffer == nullptr) { + return; + } + + data::Data data; + + uint8_t seqNo = buffer[4U]; + + uint32_t srcId = __GET_UINT16(buffer, 5U); + uint32_t dstId = __GET_UINT16(buffer, 8U); + + uint8_t flco = (buffer[15U] & 0x40U) == 0x40U ? dmr::FLCO_PRIVATE : dmr::FLCO_GROUP; + + uint32_t slotNo = (buffer[15U] & 0x80U) == 0x80U ? 2U : 1U; + + if (slotNo > 3U) { + LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); + return; + } + + // DMO mode slot disabling + if (slotNo == 1U && !m_network->getDuplex()) { + LogError(LOG_DMR, "DMR/DMO, invalid slot, slotNo = %u", slotNo); + return; + } + + // Individual slot disabling + if (slotNo == 1U && !m_network->getDMRSlot1()) { + LogError(LOG_DMR, "DMR, invalid slot, slot 1 disabled, slotNo = %u", slotNo); + return; + } + if (slotNo == 2U && !m_network->getDMRSlot2()) { + LogError(LOG_DMR, "DMR, invalid slot, slot 2 disabled, slotNo = %u", slotNo); + return; + } + + data.setSeqNo(seqNo); + data.setSlotNo(slotNo); + data.setSrcId(srcId); + data.setDstId(dstId); + data.setFLCO(flco); + + bool dataSync = (buffer[15U] & 0x20U) == 0x20U; + bool voiceSync = (buffer[15U] & 0x10U) == 0x10U; + + if (m_debug) { + LogDebug(LOG_NET, "DMR, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u", seqNo, srcId, dstId, flco, slotNo, length); + } + + if (dataSync) { + uint8_t dataType = buffer[15U] & 0x0FU; + data.setData(buffer.get() + 20U); + data.setDataType(dataType); + data.setN(0U); + } + else if (voiceSync) { + data.setData(buffer.get() + 20U); + data.setDataType(dmr::DT_VOICE_SYNC); + data.setN(0U); + } + else { + uint8_t n = buffer[15U] & 0x0FU; + data.setData(buffer.get() + 20U); + data.setDataType(dmr::DT_VOICE); + data.setN(n); + } + + switch (slotNo) { + case 1U: + m_slot1->processNetwork(data); + break; + case 2U: + m_slot2->processNetwork(data); + break; + } +} diff --git a/src/dmr/Control.h b/src/dmr/Control.h index f2f04d07..499060e8 100644 --- a/src/dmr/Control.h +++ b/src/dmr/Control.h @@ -36,11 +36,11 @@ #include "dmr/lookups/DMRAffiliationLookup.h" #include "dmr/Slot.h" #include "modem/Modem.h" -#include "network/BaseNetwork.h" +#include "network/Network.h" #include "lookups/RSSIInterpolator.h" #include "lookups/IdenTableLookup.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" #include "yaml/Yaml.h" namespace dmr @@ -61,15 +61,15 @@ namespace dmr public: /// Initializes a new instance of the Control class. Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly, - bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex, - ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssi, + bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex, + ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssi, uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose); /// Finalizes a instance of the Control class. ~Control(); /// Helper to set DMR configuration options. void setOptions(yaml::Node& conf, bool supervisor, const std::vector voiceChNo, const std::unordered_map voiceChData, - uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions); + ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions); /// Gets a flag indicating whether the DMR control channel is running. bool getCCRunning() { return m_ccRunning; } @@ -96,6 +96,11 @@ namespace dmr /// Permits a TGID on a non-authoritative host. void permittedTG(uint32_t dstId, uint8_t slot); + /// Releases a granted TG. + void releaseGrantTG(uint32_t dstId, uint8_t slot); + /// Touchs a granted TG to keep a channel grant alive. + void touchGrantTG(uint32_t dstId, uint8_t slot); + /// Gets instance of the DMRAffiliationLookup class. lookups::DMRAffiliationLookup affiliations(); @@ -134,14 +139,14 @@ namespace dmr uint32_t m_colorCode; modem::Modem* m_modem; - network::BaseNetwork* m_network; + network::Network* m_network; Slot* m_slot1; Slot* m_slot2; ::lookups::IdenTableLookup* m_idenTable; ::lookups::RadioIdLookup* m_ridLookup; - ::lookups::TalkgroupIdLookup* m_tidLookup; + ::lookups::TalkgroupRulesLookup* m_tidLookup; bool m_enableTSCC; @@ -156,6 +161,9 @@ namespace dmr bool m_dumpCSBKData; bool m_verbose; bool m_debug; + + /// Process a data frames from the network. + void processNetwork(); }; } // namespace dmr diff --git a/src/dmr/DMRDefines.h b/src/dmr/DMRDefines.h index 35772120..a2fa1ddf 100644 --- a/src/dmr/DMRDefines.h +++ b/src/dmr/DMRDefines.h @@ -33,17 +33,6 @@ #include "Defines.h" -// Data Type ID String(s) -#define DMR_DT_TERMINATOR_WITH_LC "DMR_DT_TERMINATOR_WITH_LC (Terminator with Link Control)" -#define DMR_DT_DATA_HEADER "DMR_DT_DATA_HEADER (Data Header)" -#define DMR_DT_RATE_12_DATA "DMR_DT_RATE_12_DATA (1/2-rate Data)" -#define DMR_DT_RATE_34_DATA "DMR_DT_RATE_34_DATA (3/4-rate Data)" -#define DMR_DT_RATE_1_DATA "DMR_DT_RATE_1_DATA (1-rate Data)" -#define DMR_DT_VOICE_LC_HEADER "DMR_DT_VOICE_LC_HEADER (Voice Header with Link Control)" -#define DMR_DT_VOICE_PI_HEADER "DMR_DT_VOICE_PI_HEADER (Voice Header with Privacy Indicator)" -#define DMR_DT_VOICE_SYNC "DMR_DT_VOICE_SYNC (Voice Data with Sync)" -#define DMR_DT_VOICE "DMR_DT_VOICE (Voice Data)" - namespace dmr { // --------------------------------------------------------------------------- @@ -197,18 +186,29 @@ namespace dmr // Data Type(s) const uint8_t DT_VOICE_PI_HEADER = 0x00U; +#define DMR_DT_VOICE_PI_HEADER "DMR_DT_VOICE_PI_HEADER (Voice Header with Privacy Indicator)" const uint8_t DT_VOICE_LC_HEADER = 0x01U; +#define DMR_DT_VOICE_LC_HEADER "DMR_DT_VOICE_LC_HEADER (Voice Header with Link Control)" const uint8_t DT_TERMINATOR_WITH_LC = 0x02U; +#define DMR_DT_TERMINATOR_WITH_LC "DMR_DT_TERMINATOR_WITH_LC (Terminator with Link Control)" const uint8_t DT_CSBK = 0x03U; const uint8_t DT_DATA_HEADER = 0x06U; +#define DMR_DT_DATA_HEADER "DMR_DT_DATA_HEADER (Data Header)" const uint8_t DT_RATE_12_DATA = 0x07U; +#define DMR_DT_RATE_12_DATA "DMR_DT_RATE_12_DATA (1/2-rate Data)" const uint8_t DT_RATE_34_DATA = 0x08U; +#define DMR_DT_RATE_34_DATA "DMR_DT_RATE_34_DATA (3/4-rate Data)" const uint8_t DT_IDLE = 0x09U; const uint8_t DT_RATE_1_DATA = 0x0AU; +#define DMR_DT_RATE_1_DATA "DMR_DT_RATE_1_DATA (1-rate Data)" - // Dummy values + /* + ** Internal Data Type(s) + */ const uint8_t DT_VOICE_SYNC = 0xF0U; +#define DMR_DT_VOICE_SYNC "DMR_DT_VOICE_SYNC (Voice Data with Sync)" const uint8_t DT_VOICE = 0xF1U; +#define DMR_DT_VOICE "DMR_DT_VOICE (Voice Data)" // Site Models const uint8_t SITE_MODEL_TINY = 0x00U; @@ -277,9 +277,9 @@ namespace dmr const uint8_t BCAST_ANNC_VOTE_NOW = 0x02U; // Vote Now Advice const uint8_t BCAST_ANNC_LOCAL_TIME = 0x03U; // Broadcast Local Time const uint8_t BCAST_ANNC_MASS_REG = 0x04U; // Mass Registration - const uint8_t BCAST_ANNC_CHAN_FREQ = 0x05U; // Announce a logical channel/frequency relationship - const uint8_t BCAST_ANNC_ADJ_SITE = 0x06U; // Adjacent Site information - const uint8_t BCAST_ANNC_SITE_PARMS = 0x07U; // General Site Parameters information + const uint8_t BCAST_ANNC_CHAN_FREQ = 0x05U; // Logical Channel/Frequency + const uint8_t BCAST_ANNC_ADJ_SITE = 0x06U; // Adjacent Site Information + const uint8_t BCAST_ANNC_SITE_PARMS = 0x07U; // General Site Parameters // Full-Link Control Opcode(s) const uint8_t FLCO_GROUP = 0x00U; // GRP VCH USER - Group Voice Channel User @@ -293,15 +293,15 @@ namespace dmr // Control Signalling Block Opcode(s) const uint8_t CSBKO_NONE = 0x00U; // const uint8_t CSBKO_UU_V_REQ = 0x04U; // UU VCH REQ - Unit-to-Unit Voice Channel Request - const uint8_t CSBKO_UU_ANS_RSP = 0x05U; // UU ANS RSP - Unit to Unit Answer Response + const uint8_t CSBKO_UU_ANS_RSP = 0x05U; // UU ANS RSP - Unit-to-Unit Answer Response const uint8_t CSBKO_CTCSBK = 0x07U; // CT CSBK - Channel Timing CSBK - const uint8_t CSBKO_ALOHA = 0x19U; // ALOHA - Aloha PDUs for the random access protocol + const uint8_t CSBKO_ALOHA = 0x19U; // ALOHA - Aloha PDU for Random Access const uint8_t CSBKO_AHOY = 0x1CU; // AHOY - Enquiry from TSCC const uint8_t CSBKO_RAND = 0x1FU; // (ETSI) RAND - Random Access / (DMRA) CALL ALRT - Call Alert const uint8_t CSBKO_ACK_RSP = 0x20U; // ACK RSP - Acknowledge Response const uint8_t CSBKO_EXT_FNCT = 0x24U; // (DMRA) EXT FNCT - Extended Function const uint8_t CSBKO_NACK_RSP = 0x26U; // NACK RSP - Negative Acknowledgement Response - const uint8_t CSBKO_BROADCAST = 0x28U; // BCAST - Announcement PDUs + const uint8_t CSBKO_BROADCAST = 0x28U; // BCAST - Announcement PDU const uint8_t CSBKO_P_CLEAR = 0x2EU; // P_CLEAR - Payload Channel Clear const uint8_t CSBKO_PV_GRANT = 0x30U; // PV_GRANT - Private Voice Channel Grant const uint8_t CSBKO_TV_GRANT = 0x31U; // TV_GRANT - Talkgroup Voice Channel Grant @@ -311,7 +311,7 @@ namespace dmr const uint8_t CSBKO_BSDWNACT = 0x38U; // BS DWN ACT - BS Outbound Activation const uint8_t CSBKO_PRECCSBK = 0x3DU; // PRE CSBK - Preamble CSBK - const uint8_t CSBKO_DVM_GIT_HASH = 0xFBU; // + const uint8_t CSBKO_DVM_GIT_HASH = 0x3FU; // const uint8_t TALKER_ID_NONE = 0x00U; const uint8_t TALKER_ID_HEADER = 0x01U; diff --git a/src/dmr/Slot.cpp b/src/dmr/Slot.cpp index 9d843407..f72171d2 100644 --- a/src/dmr/Slot.cpp +++ b/src/dmr/Slot.cpp @@ -62,14 +62,15 @@ bool Slot::m_embeddedLCOnly = false; bool Slot::m_dumpTAData = true; modem::Modem* Slot::m_modem = nullptr; -network::BaseNetwork* Slot::m_network = nullptr; +network::Network* Slot::m_network = nullptr; bool Slot::m_duplex = true; ::lookups::IdenTableLookup* Slot::m_idenTable = nullptr; ::lookups::RadioIdLookup* Slot::m_ridLookup = nullptr; -::lookups::TalkgroupIdLookup* Slot::m_tidLookup = nullptr; +::lookups::TalkgroupRulesLookup* Slot::m_tidLookup = nullptr; dmr::lookups::DMRAffiliationLookup *Slot::m_affiliations = nullptr; +::lookups::VoiceChData Slot::m_controlChData = ::lookups::VoiceChData(); ::lookups::IdenTable Slot::m_idenEntry = ::lookups::IdenTable(); @@ -113,7 +114,8 @@ uint8_t Slot::m_alohaBackOff = 1U; Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSize, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose) : m_slotNo(slotNo), - m_queue(queueSize, "DMR Slot Frame"), + m_txImmQueue(queueSize, "DMR Imm Slot Frame"), + m_txQueue(queueSize, "DMR Slot Frame"), m_rfState(RS_RF_LISTENING), m_rfLastDstId(0U), m_netState(RS_NET_IDLE), @@ -213,6 +215,10 @@ bool Slot::processFrame(uint8_t *data, uint32_t len) } } + if (!m_tscc->m_enableTSCC) { + notifyCC_ReleaseGrant(m_rfLC->getDstId()); + } + if (m_rfTimeout) { writeEndRF(); return false; @@ -325,12 +331,20 @@ uint32_t Slot::getFrame(uint8_t* data) { assert(data != nullptr); - if (m_queue.isEmpty()) + if (m_txQueue.isEmpty() && m_txImmQueue.isEmpty()) return 0U; uint8_t len = 0U; - m_queue.getData(&len, 1U); - m_queue.getData(data, len); + + // tx immediate queue takes priority + if (!m_txImmQueue.isEmpty()) { + m_txImmQueue.getData(&len, 1U); + m_txImmQueue.getData(data, len); + } + else { + m_txQueue.getData(&len, 1U); + m_txQueue.getData(data, len); + } return len; } @@ -422,7 +436,7 @@ void Slot::clock() if (!m_ccRunning) { m_ccHalted = false; m_ccPrevRunning = m_ccRunning; - m_queue.clear(); // clear the frame buffer + m_txQueue.clear(); // clear the frame buffer } } else { @@ -456,7 +470,7 @@ void Slot::clock() } if (m_ccPrevRunning && !m_ccRunning) { - m_queue.clear(); // clear the frame buffer + m_txQueue.clear(); // clear the frame buffer m_ccPrevRunning = m_ccRunning; } } @@ -543,7 +557,7 @@ void Slot::clock() if (m_rfState == RS_RF_REJECTED) { if (!m_enableTSCC) { - m_queue.clear(); + m_txQueue.clear(); } m_rfFrames = 0U; @@ -571,12 +585,50 @@ void Slot::permittedTG(uint32_t dstId) } if (m_verbose) { - LogDebug(LOG_DMR, "DMR Slot %u, non-authoritative TG permit, dstId = %u", m_slotNo, dstId); + LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG permit, dstId = %u", m_slotNo, dstId); } m_permittedDstId = dstId; } +/// +/// Releases a granted TG. +/// +/// +void Slot::releaseGrantTG(uint32_t dstId) +{ + if (!m_control) { + return; + } + + if (m_affiliations->isGranted(dstId)) { + if (m_verbose) { + LogMessage(LOG_DMR, "DMR Slot %u, REST request, release TG grant, dstId = %u", m_slotNo, dstId); + } + + m_affiliations->releaseGrant(dstId, false); + } +} + +/// +/// Touchs a granted TG to keep a channel grant alive. +/// +/// +void Slot::touchGrantTG(uint32_t dstId) +{ + if (!m_control) { + return; + } + + if (m_affiliations->isGranted(dstId)) { + if (m_verbose) { + LogMessage(LOG_DMR, "DMR Slot %u, REST request, touch TG grant, dstId = %u", m_slotNo, dstId); + } + + m_affiliations->touchGrant(dstId); + } +} + /// /// Helper to change the debug and verbose state. /// @@ -640,13 +692,13 @@ void Slot::setSilenceThreshold(uint32_t threshold) /// Instance of the BaseNetwork class. /// Flag indicating full-duplex operation. /// Instance of the RadioIdLookup class. -/// Instance of the TalkgroupIdLookup class. +/// Instance of the TalkgroupRulesLookup class. /// Instance of the IdenTableLookup class. /// Instance of the RSSIInterpolator class. /// /// void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem, - network::BaseNetwork* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, + network::Network* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose) { assert(dmr != nullptr); @@ -744,13 +796,14 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s /// /// Voice Channel Number list. /// Voice Channel data map. +/// Control Channel data. /// DMR Network ID. /// DMR Site ID. /// Channel ID. /// Channel Number. /// void Slot::setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData, - uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReg) + ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReg) { m_siteData = SiteData(SITE_MODEL_SMALL, netId, siteId, 3U, requireReg); m_channelNo = channelNo; @@ -770,6 +823,8 @@ void Slot::setSiteData(const std::vector voiceChNo, const std::unorder std::unordered_map chData = std::unordered_map(voiceChData); m_affiliations->setRFChData(chData); + m_controlChData = controlChData; + lc::CSBK::setSiteData(m_siteData); } @@ -791,9 +846,10 @@ void Slot::setAlohaConfig(uint8_t nRandWait, uint8_t backOff) /// /// Add data frame to the data ring buffer. /// -/// -/// -void Slot::addFrame(const uint8_t *data, bool net) +/// Frame data to add to Tx queue. +/// Flag indicating whether the data came from the network or not +/// Flag indicating whether or not the data is priority and is added to the immediate queue. +void Slot::addFrame(const uint8_t *data, bool net, bool imm) { assert(data != nullptr); @@ -803,12 +859,38 @@ void Slot::addFrame(const uint8_t *data, bool net) } uint8_t len = DMR_FRAME_LENGTH_BYTES + 2U; - uint32_t space = m_queue.freeSpace(); + if (m_debug) { + Utils::symbols("!!! *Tx DMR", data + 2U, len - 2U); + } + + // is this immediate data? + if (imm) { + // resize immediate queue if necessary (this shouldn't really ever happen) + uint32_t space = m_txImmQueue.freeSpace(); + if (space < (len + 1U)) { + if (!net) { + uint32_t queueLen = m_txImmQueue.length(); + m_txImmQueue.resize(queueLen + len); + LogError(LOG_DMR, "Slot %u, overflow in the imm DMR slot queue; queue free is %u, needed %u; resized was %u is %u", m_slotNo, space, len, queueLen, m_txQueue.length()); + return; + } + else { + LogError(LOG_DMR, "Slot %u, overflow in the imm DMR slot queue while writing network data; queue free is %u, needed %u", m_slotNo, space, len); + return; + } + } + + m_txImmQueue.addData(&len, 1U); + m_txImmQueue.addData(data, len); + return; + } + + uint32_t space = m_txQueue.freeSpace(); if (space < (len + 1U)) { if (!net) { - uint32_t queueLen = m_queue.length(); - m_queue.resize(queueLen + (DMR_FRAME_LENGTH_BYTES + 2U)); - LogError(LOG_DMR, "Slot %u, overflow in the DMR slot queue; queue free is %u, needed %u; resized was %u is %u", m_slotNo, space, len, queueLen, m_queue.length()); + uint32_t queueLen = m_txQueue.length(); + m_txQueue.resize(queueLen + (DMR_FRAME_LENGTH_BYTES + 2U)); + LogError(LOG_DMR, "Slot %u, overflow in the DMR slot queue; queue free is %u, needed %u; resized was %u is %u", m_slotNo, space, len, queueLen, m_txQueue.length()); return; } else { @@ -817,12 +899,64 @@ void Slot::addFrame(const uint8_t *data, bool net) } } - if (m_debug) { - Utils::symbols("!!! *Tx DMR", data + 2U, len - 2U); + m_txQueue.addData(&len, 1U); + m_txQueue.addData(data, len); +} + +/// +/// Helper to send a REST API request to the CC to release a channel grant at the end of a call. +/// +/// +void Slot::notifyCC_ReleaseGrant(uint32_t dstId) +{ + // callback REST API to release the granted TG on the specified control channel + if (!m_controlChData.address().empty() && m_controlChData.port() > 0) { + if (m_controlChData.address() == "127.0.0.1") { + // cowardly ignore trying to send release grants to ourselves + return; + } + + json::object req = json::object(); + int state = modem::DVM_STATE::STATE_DMR; + req["state"].set(state); + req["dstId"].set(dstId); + uint8_t slot = m_slotNo; + req["slot"].set(slot); + + int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), + HTTP_PUT, PUT_RELEASE_TG, req, m_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + } } +} - m_queue.addData(&len, 1U); - m_queue.addData(data, len); +/// +/// Helper to send a REST API request to the CC to "touch" a channel grant to refresh grant timers. +/// +/// +void Slot::notifyCC_TouchGrant(uint32_t dstId) +{ + // callback REST API to touch the granted TG on the specified control channel + if (!m_controlChData.address().empty() && m_controlChData.port() > 0) { + if (m_controlChData.address() == "127.0.0.1") { + // cowardly ignore trying to send touch grants to ourselves + return; + } + + json::object req = json::object(); + int state = modem::DVM_STATE::STATE_DMR; + req["state"].set(state); + req["dstId"].set(dstId); + uint8_t slot = m_slotNo; + req["slot"].set(slot); + + int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), + HTTP_PUT, PUT_TOUCH_TG, req, m_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + } + } } /// @@ -1006,7 +1140,7 @@ void Slot::writeRF_ControlData(uint16_t frameCnt, uint8_t n) // don't add any frames if the queue is full uint8_t len = DMR_FRAME_LENGTH_BYTES + 2U; - uint32_t space = m_queue.freeSpace(); + uint32_t space = m_txQueue.freeSpace(); if (space < (len + 1U)) { m_ccSeq--; if (m_ccSeq < 0U) diff --git a/src/dmr/Slot.h b/src/dmr/Slot.h index 371cce89..8a51b2d7 100644 --- a/src/dmr/Slot.h +++ b/src/dmr/Slot.h @@ -39,11 +39,11 @@ #include "dmr/packet/Data.h" #include "dmr/packet/Voice.h" #include "modem/Modem.h" -#include "network/BaseNetwork.h" +#include "network/Network.h" #include "lookups/RSSIInterpolator.h" #include "lookups/IdenTableLookup.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" #include "RingBuffer.h" #include "StopWatch.h" #include "Timer.h" @@ -97,6 +97,11 @@ namespace dmr /// Permits a TGID on a non-authoritative host. void permittedTG(uint32_t dstId); + /// Releases a granted TG. + void releaseGrantTG(uint32_t dstId); + /// Touchs a granted TG to keep a channel grant alive. + void touchGrantTG(uint32_t dstId); + /// Gets instance of the ControlSignaling class. packet::ControlSignaling* control() { return m_control; } @@ -116,11 +121,11 @@ namespace dmr /// Helper to initialize the slot processor. static void init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem, - network::BaseNetwork* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, + network::Network* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose); /// Sets local configured site data. static void setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData, - uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReq); + ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReq); /// Sets TSCC Aloha configuration. static void setAlohaConfig(uint8_t nRandWait, uint8_t backOff); @@ -135,7 +140,8 @@ namespace dmr uint32_t m_slotNo; - RingBuffer m_queue; + RingBuffer m_txImmQueue; + RingBuffer m_txQueue; RPT_RF_STATE m_rfState; uint32_t m_rfLastDstId; @@ -220,14 +226,15 @@ namespace dmr static bool m_dumpTAData; static modem::Modem* m_modem; - static network::BaseNetwork* m_network; + static network::Network* m_network; static bool m_duplex; static ::lookups::IdenTableLookup* m_idenTable; static ::lookups::RadioIdLookup* m_ridLookup; - static ::lookups::TalkgroupIdLookup* m_tidLookup; + static ::lookups::TalkgroupRulesLookup* m_tidLookup; static lookups::DMRAffiliationLookup* m_affiliations; + static ::lookups::VoiceChData m_controlChData; static ::lookups::IdenTable m_idenEntry; @@ -254,7 +261,12 @@ namespace dmr static uint8_t m_alohaBackOff; /// Add data frame to the data ring buffer. - void addFrame(const uint8_t* data, bool net = false); + void addFrame(const uint8_t* data, bool net = false, bool imm = false); + + /// Helper to send a REST API request to the CC to release a channel grant at the end of a call. + void notifyCC_ReleaseGrant(uint32_t dstId); + /// Helper to send a REST API request to the CC to "touch" a channel grant to refresh grant timers. + void notifyCC_TouchGrant(uint32_t dstId); /// Write data frame to the network. void writeNetwork(const uint8_t* data, uint8_t dataType, uint8_t errors = 0U); diff --git a/src/dmr/acl/AccessControl.cpp b/src/dmr/acl/AccessControl.cpp index d6e9758e..ea0e6334 100644 --- a/src/dmr/acl/AccessControl.cpp +++ b/src/dmr/acl/AccessControl.cpp @@ -43,14 +43,14 @@ using namespace dmr::acl; // --------------------------------------------------------------------------- RadioIdLookup* AccessControl::m_ridLookup; -TalkgroupIdLookup* AccessControl::m_tidLookup; +TalkgroupRulesLookup* AccessControl::m_tidLookup; /// /// Initializes the DMR access control. /// /// Instance of the RadioIdLookup class. -/// Instance of the TalkgroupIdLookup class. -void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup) +/// Instance of the TalkgroupRulesLookup class. +void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; @@ -99,15 +99,15 @@ bool AccessControl::validateTGId(uint32_t slotNo, uint32_t id) } // lookup TID and perform test for validity - TalkgroupId tid = m_tidLookup->find(id); - if (!tid.tgEnabled()) + TalkgroupRuleGroupVoice tid = m_tidLookup->find(id); + if (tid.isInvalid()) return false; - if (tid.tgSlot() == 0) - return true; // TG Slot of 0 for the talkgroup entry means both + if (!tid.config().active()) + return false; if (slotNo != 0) { - if (tid.tgSlot() != slotNo) + if (tid.source().tgSlot() != slotNo) return false; return true; diff --git a/src/dmr/acl/AccessControl.h b/src/dmr/acl/AccessControl.h index 27de586b..56153312 100644 --- a/src/dmr/acl/AccessControl.h +++ b/src/dmr/acl/AccessControl.h @@ -33,7 +33,7 @@ #include "Defines.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" namespace dmr { @@ -49,7 +49,7 @@ namespace dmr class HOST_SW_API AccessControl { public: /// Initializes the DMR access control. - static void init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup); + static void init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLookup); /// Helper to validate a source radio ID. static bool validateSrcId(uint32_t id); @@ -58,7 +58,7 @@ namespace dmr private: static RadioIdLookup* m_ridLookup; - static TalkgroupIdLookup* m_tidLookup; + static TalkgroupRulesLookup* m_tidLookup; }; } // namespace acl } // namespace dmr diff --git a/src/dmr/lc/CSBK.cpp b/src/dmr/lc/CSBK.cpp index b7ff2299..f3fd0d50 100644 --- a/src/dmr/lc/CSBK.cpp +++ b/src/dmr/lc/CSBK.cpp @@ -101,6 +101,15 @@ CSBK::~CSBK() /* stub */ } +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK::toString() +{ + return std::string("CSBKO_UNKWN (Unknown CSBK)"); +} + /// /// Regenerate a DMR CSBK without decoding. /// @@ -175,9 +184,9 @@ ulong64_t CSBK::toValue(const uint8_t* csbk) /// /// /// -std::unique_ptr CSBK::fromValue(const ulong64_t csbkValue) +UInt8Array CSBK::fromValue(const ulong64_t csbkValue) { - __UNIQUE_BUFFER(csbk, uint8_t, DMR_CSBK_LENGTH_BYTES); + __UNIQUE_UINT8_ARRAY(csbk, DMR_CSBK_LENGTH_BYTES); // split ulong64_t (8 byte) value into bytes csbk[2U] = (uint8_t)((csbkValue >> 56) & 0xFFU); diff --git a/src/dmr/lc/CSBK.h b/src/dmr/lc/CSBK.h index b0d2d1b1..eca81351 100644 --- a/src/dmr/lc/CSBK.h +++ b/src/dmr/lc/CSBK.h @@ -59,6 +59,9 @@ namespace dmr /// Encodes a DMR CSBK. virtual void encode(uint8_t* data) = 0; + /// Returns a string that represents the current CSBK. + virtual std::string toString(); + /// Regenerate a DMR CSBK without decoding. /// This is because the DMR archeticture allows fall-thru of unsupported CSBKs. static bool regenerate(uint8_t* data); @@ -144,7 +147,7 @@ namespace dmr /// Internal helper to convert CSBK bytes to a 64-bit long value. static ulong64_t toValue(const uint8_t* Csbk); /// Internal helper to convert a 64-bit long value to CSBK bytes. - static std::unique_ptr fromValue(const ulong64_t csbkValue); + static UInt8Array fromValue(const ulong64_t csbkValue); /// Internal helper to decode a control signalling block. bool decode(const uint8_t* data, uint8_t* csbk); diff --git a/src/dmr/lc/csbk/CSBK_ACK_RSP.cpp b/src/dmr/lc/csbk/CSBK_ACK_RSP.cpp index de4bbd98..09f8b717 100644 --- a/src/dmr/lc/csbk/CSBK_ACK_RSP.cpp +++ b/src/dmr/lc/csbk/CSBK_ACK_RSP.cpp @@ -97,3 +97,12 @@ void CSBK_ACK_RSP::encode(uint8_t* data) std::unique_ptr csbk = CSBK::fromValue(csbkValue); CSBK::encode(data, csbk.get()); } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_ACK_RSP::toString() +{ + return std::string("CSBKO_ACK_RSP (Acknowledge Response)"); +} diff --git a/src/dmr/lc/csbk/CSBK_ACK_RSP.h b/src/dmr/lc/csbk/CSBK_ACK_RSP.h index 11b283ef..1f3b715b 100644 --- a/src/dmr/lc/csbk/CSBK_ACK_RSP.h +++ b/src/dmr/lc/csbk/CSBK_ACK_RSP.h @@ -46,9 +46,12 @@ namespace dmr CSBK_ACK_RSP(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_ALOHA.cpp b/src/dmr/lc/csbk/CSBK_ALOHA.cpp index 0d5c701b..20ececd2 100644 --- a/src/dmr/lc/csbk/CSBK_ALOHA.cpp +++ b/src/dmr/lc/csbk/CSBK_ALOHA.cpp @@ -92,6 +92,15 @@ void CSBK_ALOHA::encode(uint8_t* data) CSBK::encode(data, csbk.get()); } +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_ALOHA::toString() +{ + return std::string("CSBKO_ALOHA (Aloha PDU)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/dmr/lc/csbk/CSBK_ALOHA.h b/src/dmr/lc/csbk/CSBK_ALOHA.h index 01e10502..f2d937d5 100644 --- a/src/dmr/lc/csbk/CSBK_ALOHA.h +++ b/src/dmr/lc/csbk/CSBK_ALOHA.h @@ -46,9 +46,12 @@ namespace dmr CSBK_ALOHA(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; public: /// Aloha Site Time Slot Synchronization. diff --git a/src/dmr/lc/csbk/CSBK_BROADCAST.cpp b/src/dmr/lc/csbk/CSBK_BROADCAST.cpp index c9f4ad13..e77f739e 100644 --- a/src/dmr/lc/csbk/CSBK_BROADCAST.cpp +++ b/src/dmr/lc/csbk/CSBK_BROADCAST.cpp @@ -154,6 +154,20 @@ void CSBK_BROADCAST::encode(uint8_t* data) CSBK::encode(data, csbk.get()); } +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_BROADCAST::toString() +{ + switch (m_anncType) { + case BCAST_ANNC_ANN_WD_TSCC: return std::string("CSBKO_BROADCAST (Announcement PDU), BCAST_ANNC_ANN_WD_TSCC (Announce-WD TSCC Channel)"); + case BCAST_ANNC_CHAN_FREQ: return std::string("CSBKO_BROADCAST (Announcement PDU), BCAST_ANNC_CHAN_FREQ (Logical Channel/Frequency)"); + case BCAST_ANNC_SITE_PARMS: return std::string("CSBKO_BROADCAST (Announcement PDU), BCAST_ANNC_SITE_PARMS (General Site Parameters)"); + default: return std::string("CSBKO_BROADCAST (Announcement PDU)"); + } +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/dmr/lc/csbk/CSBK_BROADCAST.h b/src/dmr/lc/csbk/CSBK_BROADCAST.h index 03f106d1..e8699bd9 100644 --- a/src/dmr/lc/csbk/CSBK_BROADCAST.h +++ b/src/dmr/lc/csbk/CSBK_BROADCAST.h @@ -46,9 +46,12 @@ namespace dmr CSBK_BROADCAST(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; public: /// Broadcast Announcment Type. diff --git a/src/dmr/lc/csbk/CSBK_BSDWNACT.cpp b/src/dmr/lc/csbk/CSBK_BSDWNACT.cpp index a484122d..34e21cb7 100644 --- a/src/dmr/lc/csbk/CSBK_BSDWNACT.cpp +++ b/src/dmr/lc/csbk/CSBK_BSDWNACT.cpp @@ -83,6 +83,15 @@ void CSBK_BSDWNACT::encode(uint8_t* data) /* stub */ } +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_BSDWNACT::toString() +{ + return std::string("CSBKO_BSDWNACT (BS Outbound Activation)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/dmr/lc/csbk/CSBK_BSDWNACT.h b/src/dmr/lc/csbk/CSBK_BSDWNACT.h index 86ef22c7..b5c71a52 100644 --- a/src/dmr/lc/csbk/CSBK_BSDWNACT.h +++ b/src/dmr/lc/csbk/CSBK_BSDWNACT.h @@ -46,9 +46,12 @@ namespace dmr CSBK_BSDWNACT(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; public: /// Base Station ID. diff --git a/src/dmr/lc/csbk/CSBK_CALL_ALRT.cpp b/src/dmr/lc/csbk/CSBK_CALL_ALRT.cpp index 57f07cd4..038904a6 100644 --- a/src/dmr/lc/csbk/CSBK_CALL_ALRT.cpp +++ b/src/dmr/lc/csbk/CSBK_CALL_ALRT.cpp @@ -90,3 +90,12 @@ void CSBK_CALL_ALRT::encode(uint8_t* data) std::unique_ptr csbk = CSBK::fromValue(csbkValue); CSBK::encode(data, csbk.get()); } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_CALL_ALRT::toString() +{ + return std::string("CSBKO_RAND (Call Alert)"); +} diff --git a/src/dmr/lc/csbk/CSBK_CALL_ALRT.h b/src/dmr/lc/csbk/CSBK_CALL_ALRT.h index fc24a84d..4378b15c 100644 --- a/src/dmr/lc/csbk/CSBK_CALL_ALRT.h +++ b/src/dmr/lc/csbk/CSBK_CALL_ALRT.h @@ -46,9 +46,12 @@ namespace dmr CSBK_CALL_ALRT(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_DVM_GIT_HASH.cpp b/src/dmr/lc/csbk/CSBK_DVM_GIT_HASH.cpp index 4a78a088..b4f3ca2b 100644 --- a/src/dmr/lc/csbk/CSBK_DVM_GIT_HASH.cpp +++ b/src/dmr/lc/csbk/CSBK_DVM_GIT_HASH.cpp @@ -84,3 +84,12 @@ void CSBK_DVM_GIT_HASH::encode(uint8_t* data) std::unique_ptr csbk = CSBK::fromValue(csbkValue); CSBK::encode(data, csbk.get()); } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_DVM_GIT_HASH::toString() +{ + return std::string("CSBKO_DVM_GIT_HASH (DVM Git Hash Identifier)"); +} diff --git a/src/dmr/lc/csbk/CSBK_DVM_GIT_HASH.h b/src/dmr/lc/csbk/CSBK_DVM_GIT_HASH.h index d1a4aab8..6219f2e9 100644 --- a/src/dmr/lc/csbk/CSBK_DVM_GIT_HASH.h +++ b/src/dmr/lc/csbk/CSBK_DVM_GIT_HASH.h @@ -46,9 +46,12 @@ namespace dmr CSBK_DVM_GIT_HASH(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_EXT_FNCT.cpp b/src/dmr/lc/csbk/CSBK_EXT_FNCT.cpp index 9b5570e9..4b0a07fe 100644 --- a/src/dmr/lc/csbk/CSBK_EXT_FNCT.cpp +++ b/src/dmr/lc/csbk/CSBK_EXT_FNCT.cpp @@ -96,6 +96,15 @@ void CSBK_EXT_FNCT::encode(uint8_t* data) CSBK::encode(data, csbk.get()); } +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_EXT_FNCT::toString() +{ + return std::string("CSBKO_EXT_FNCT (Extended Function)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/dmr/lc/csbk/CSBK_EXT_FNCT.h b/src/dmr/lc/csbk/CSBK_EXT_FNCT.h index 561eea58..8bb98b7b 100644 --- a/src/dmr/lc/csbk/CSBK_EXT_FNCT.h +++ b/src/dmr/lc/csbk/CSBK_EXT_FNCT.h @@ -46,9 +46,12 @@ namespace dmr CSBK_EXT_FNCT(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; public: /// Extended function opcode. diff --git a/src/dmr/lc/csbk/CSBK_NACK_RSP.cpp b/src/dmr/lc/csbk/CSBK_NACK_RSP.cpp index 355bb913..a79b922c 100644 --- a/src/dmr/lc/csbk/CSBK_NACK_RSP.cpp +++ b/src/dmr/lc/csbk/CSBK_NACK_RSP.cpp @@ -96,6 +96,15 @@ void CSBK_NACK_RSP::encode(uint8_t* data) CSBK::encode(data, csbk.get()); } +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_NACK_RSP::toString() +{ + return std::string("CSBKO_NACK_RSP (Negative Acknowledgement Response)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/dmr/lc/csbk/CSBK_NACK_RSP.h b/src/dmr/lc/csbk/CSBK_NACK_RSP.h index 7d629eca..03b0d969 100644 --- a/src/dmr/lc/csbk/CSBK_NACK_RSP.h +++ b/src/dmr/lc/csbk/CSBK_NACK_RSP.h @@ -46,9 +46,12 @@ namespace dmr CSBK_NACK_RSP(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; public: /// Service Kind. diff --git a/src/dmr/lc/csbk/CSBK_PD_GRANT.cpp b/src/dmr/lc/csbk/CSBK_PD_GRANT.cpp index 86dfa602..17998e4a 100644 --- a/src/dmr/lc/csbk/CSBK_PD_GRANT.cpp +++ b/src/dmr/lc/csbk/CSBK_PD_GRANT.cpp @@ -82,3 +82,12 @@ void CSBK_PD_GRANT::encode(uint8_t* data) std::unique_ptr csbk = CSBK::fromValue(csbkValue); CSBK::encode(data, csbk.get()); } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_PD_GRANT::toString() +{ + return std::string("CSBKO_PD_GRANT (Private Data Channel Grant)"); +} diff --git a/src/dmr/lc/csbk/CSBK_PD_GRANT.h b/src/dmr/lc/csbk/CSBK_PD_GRANT.h index 588bc0c1..6fe88e6b 100644 --- a/src/dmr/lc/csbk/CSBK_PD_GRANT.h +++ b/src/dmr/lc/csbk/CSBK_PD_GRANT.h @@ -46,9 +46,12 @@ namespace dmr CSBK_PD_GRANT(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_PRECCSBK.cpp b/src/dmr/lc/csbk/CSBK_PRECCSBK.cpp index b7f1a469..88f69af6 100644 --- a/src/dmr/lc/csbk/CSBK_PRECCSBK.cpp +++ b/src/dmr/lc/csbk/CSBK_PRECCSBK.cpp @@ -84,3 +84,12 @@ void CSBK_PRECCSBK::encode(uint8_t* data) /* stub */ } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_PRECCSBK::toString() +{ + return std::string("CSBKO_PRECCSBK (Preamble CSBK)"); +} diff --git a/src/dmr/lc/csbk/CSBK_PRECCSBK.h b/src/dmr/lc/csbk/CSBK_PRECCSBK.h index 59a31ced..eaaf60b0 100644 --- a/src/dmr/lc/csbk/CSBK_PRECCSBK.h +++ b/src/dmr/lc/csbk/CSBK_PRECCSBK.h @@ -46,9 +46,12 @@ namespace dmr CSBK_PRECCSBK(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_PV_GRANT.cpp b/src/dmr/lc/csbk/CSBK_PV_GRANT.cpp index f0f749be..29eb525c 100644 --- a/src/dmr/lc/csbk/CSBK_PV_GRANT.cpp +++ b/src/dmr/lc/csbk/CSBK_PV_GRANT.cpp @@ -82,3 +82,12 @@ void CSBK_PV_GRANT::encode(uint8_t* data) std::unique_ptr csbk = CSBK::fromValue(csbkValue); CSBK::encode(data, csbk.get()); } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_PV_GRANT::toString() +{ + return std::string("CSBKO_PV_GRANT (Private Voice Channel Grant)"); +} diff --git a/src/dmr/lc/csbk/CSBK_PV_GRANT.h b/src/dmr/lc/csbk/CSBK_PV_GRANT.h index e87f1328..851a8064 100644 --- a/src/dmr/lc/csbk/CSBK_PV_GRANT.h +++ b/src/dmr/lc/csbk/CSBK_PV_GRANT.h @@ -46,9 +46,12 @@ namespace dmr CSBK_PV_GRANT(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_P_CLEAR.cpp b/src/dmr/lc/csbk/CSBK_P_CLEAR.cpp index 25056b6e..0c77fd4a 100644 --- a/src/dmr/lc/csbk/CSBK_P_CLEAR.cpp +++ b/src/dmr/lc/csbk/CSBK_P_CLEAR.cpp @@ -81,3 +81,12 @@ void CSBK_P_CLEAR::encode(uint8_t* data) std::unique_ptr csbk = CSBK::fromValue(csbkValue); CSBK::encode(data, csbk.get()); } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_P_CLEAR::toString() +{ + return std::string("CSBKO_P_CLEAR (Payload Channel Clear)"); +} diff --git a/src/dmr/lc/csbk/CSBK_P_CLEAR.h b/src/dmr/lc/csbk/CSBK_P_CLEAR.h index a7d58274..6d63e6a9 100644 --- a/src/dmr/lc/csbk/CSBK_P_CLEAR.h +++ b/src/dmr/lc/csbk/CSBK_P_CLEAR.h @@ -46,9 +46,12 @@ namespace dmr CSBK_P_CLEAR(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_P_GRANT.cpp b/src/dmr/lc/csbk/CSBK_P_GRANT.cpp index ae44214b..18082876 100644 --- a/src/dmr/lc/csbk/CSBK_P_GRANT.cpp +++ b/src/dmr/lc/csbk/CSBK_P_GRANT.cpp @@ -82,3 +82,12 @@ void CSBK_P_GRANT::encode(uint8_t* data) std::unique_ptr csbk = CSBK::fromValue(csbkValue); CSBK::encode(data, csbk.get()); } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_P_GRANT::toString() +{ + return std::string("CSBK_P_GRANT (Payload Grant)"); +} diff --git a/src/dmr/lc/csbk/CSBK_P_GRANT.h b/src/dmr/lc/csbk/CSBK_P_GRANT.h index d6aa4be0..997a1440 100644 --- a/src/dmr/lc/csbk/CSBK_P_GRANT.h +++ b/src/dmr/lc/csbk/CSBK_P_GRANT.h @@ -46,9 +46,12 @@ namespace dmr CSBK_P_GRANT(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_RAND.cpp b/src/dmr/lc/csbk/CSBK_RAND.cpp index 0a0dd997..fac4bdac 100644 --- a/src/dmr/lc/csbk/CSBK_RAND.cpp +++ b/src/dmr/lc/csbk/CSBK_RAND.cpp @@ -99,6 +99,30 @@ void CSBK_RAND::encode(uint8_t* data) CSBK::encode(data, csbk.get()); } +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_RAND::toString() +{ + switch (m_serviceKind) { + case SVC_KIND_IND_VOICE_CALL: return std::string("CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)"); + case SVC_KIND_GRP_VOICE_CALL: return std::string("CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call)"); + case SVC_KIND_IND_DATA_CALL: return std::string("CSBKO_RAND (Random Access), SVC_KIND_IND_DATA_CALL (Individual Data Call)"); + case SVC_KIND_GRP_DATA_CALL: return std::string("CSBKO_RAND (Random Access), SVC_KIND_GRP_DATA_CALL (Group Data Call)"); + case SVC_KIND_IND_UDT_DATA_CALL: return std::string("CSBKO_RAND (Random Access), SVC_KIND_IND_UDT_DATA_CALL (Individual UDT Short Data Call)"); + case SVC_KIND_GRP_UDT_DATA_CALL: return std::string("CSBKO_RAND (Random Access), SVC_KIND_GRP_UDT_DATA_CALL (Group UDT Short Data Call)"); + case SVC_KIND_UDT_SHORT_POLL: return std::string("CSBKO_RAND (Random Access), SVC_KIND_UDT_SHORT_POLL (UDT Short Data Polling Service)"); + case SVC_KIND_STATUS_TRANSPORT: return std::string("CSBKO_RAND (Random Access), SVC_KIND_STATUS_TRANSPORT (Status Transport Service)"); + case SVC_KIND_CALL_DIVERSION: return std::string("CSBKO_RAND (Random Access), SVC_KIND_CALL_DIVERSION (Call Diversion Service)"); + case SVC_KIND_CALL_ANSWER: return std::string("CSBKO_RAND (Random Access), SVC_KIND_CALL_ANSWER (Call Answer Service)"); + case SVC_KIND_SUPPLEMENTARY_SVC: return std::string("CSBKO_RAND (Random Access), SVC_KIND_SUPPLEMENTARY_SVC (Supplementary Service)"); + case SVC_KIND_REG_SVC: return std::string("CSBKO_RAND (Random Access), SVC_KIND_REG_SVC (Registration Service)"); + case SVC_KIND_CANCEL_CALL: return std::string("CSBKO_RAND (Random Access), SVC_KIND_CANCEL_CALL (Cancel Call Service)"); + default: return std::string("CSBKO_RAND (Random Access)"); + } +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/dmr/lc/csbk/CSBK_RAND.h b/src/dmr/lc/csbk/CSBK_RAND.h index 0842b79f..eb73db56 100644 --- a/src/dmr/lc/csbk/CSBK_RAND.h +++ b/src/dmr/lc/csbk/CSBK_RAND.h @@ -46,9 +46,12 @@ namespace dmr CSBK_RAND(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; public: /// Service Options. diff --git a/src/dmr/lc/csbk/CSBK_RAW.h b/src/dmr/lc/csbk/CSBK_RAW.h index 375292dc..74700ff1 100644 --- a/src/dmr/lc/csbk/CSBK_RAW.h +++ b/src/dmr/lc/csbk/CSBK_RAW.h @@ -48,9 +48,9 @@ namespace dmr ~CSBK_RAW(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); /// Sets the CSBK to encode. void setCSBK(const uint8_t* csbk); diff --git a/src/dmr/lc/csbk/CSBK_TD_GRANT.cpp b/src/dmr/lc/csbk/CSBK_TD_GRANT.cpp index 7c439b55..5a777631 100644 --- a/src/dmr/lc/csbk/CSBK_TD_GRANT.cpp +++ b/src/dmr/lc/csbk/CSBK_TD_GRANT.cpp @@ -82,3 +82,12 @@ void CSBK_TD_GRANT::encode(uint8_t* data) std::unique_ptr csbk = CSBK::fromValue(csbkValue); CSBK::encode(data, csbk.get()); } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_TD_GRANT::toString() +{ + return std::string("CSBKO_TD_GRANT (Talkgroup Data Channel Grant)"); +} diff --git a/src/dmr/lc/csbk/CSBK_TD_GRANT.h b/src/dmr/lc/csbk/CSBK_TD_GRANT.h index b1b109d4..df026ca4 100644 --- a/src/dmr/lc/csbk/CSBK_TD_GRANT.h +++ b/src/dmr/lc/csbk/CSBK_TD_GRANT.h @@ -46,9 +46,12 @@ namespace dmr CSBK_TD_GRANT(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_TV_GRANT.cpp b/src/dmr/lc/csbk/CSBK_TV_GRANT.cpp index 51670a8f..261552f8 100644 --- a/src/dmr/lc/csbk/CSBK_TV_GRANT.cpp +++ b/src/dmr/lc/csbk/CSBK_TV_GRANT.cpp @@ -84,6 +84,15 @@ void CSBK_TV_GRANT::encode(uint8_t* data) CSBK::encode(data, csbk.get()); } +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_TV_GRANT::toString() +{ + return std::string("CSBKO_TV_GRANT (Talkgroup Voice Channel Grant)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/dmr/lc/csbk/CSBK_TV_GRANT.h b/src/dmr/lc/csbk/CSBK_TV_GRANT.h index d1be7128..d7b3c9b2 100644 --- a/src/dmr/lc/csbk/CSBK_TV_GRANT.h +++ b/src/dmr/lc/csbk/CSBK_TV_GRANT.h @@ -46,9 +46,12 @@ namespace dmr CSBK_TV_GRANT(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; public: /// Flag indicating whether the grant is a late entry. diff --git a/src/dmr/lc/csbk/CSBK_UU_ANS_RSP.cpp b/src/dmr/lc/csbk/CSBK_UU_ANS_RSP.cpp index 500c024f..5a9deb23 100644 --- a/src/dmr/lc/csbk/CSBK_UU_ANS_RSP.cpp +++ b/src/dmr/lc/csbk/CSBK_UU_ANS_RSP.cpp @@ -81,3 +81,12 @@ void CSBK_UU_ANS_RSP::encode(uint8_t* data) /* stub */ } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_UU_ANS_RSP::toString() +{ + return std::string("CSBKO_UU_ANS_RSP (Unit-to-Unit Answer Response)"); +} diff --git a/src/dmr/lc/csbk/CSBK_UU_ANS_RSP.h b/src/dmr/lc/csbk/CSBK_UU_ANS_RSP.h index 1452db09..4df27d67 100644 --- a/src/dmr/lc/csbk/CSBK_UU_ANS_RSP.h +++ b/src/dmr/lc/csbk/CSBK_UU_ANS_RSP.h @@ -46,9 +46,12 @@ namespace dmr CSBK_UU_ANS_RSP(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lc/csbk/CSBK_UU_V_REQ.cpp b/src/dmr/lc/csbk/CSBK_UU_V_REQ.cpp index b0943f32..8a5422f9 100644 --- a/src/dmr/lc/csbk/CSBK_UU_V_REQ.cpp +++ b/src/dmr/lc/csbk/CSBK_UU_V_REQ.cpp @@ -81,3 +81,12 @@ void CSBK_UU_V_REQ::encode(uint8_t* data) /* stub */ } + +/// +/// Returns a string that represents the current CSBK. +/// +/// +std::string CSBK_UU_V_REQ::toString() +{ + return std::string("CSBKO_UU_V_REQ (Unit-to-Unit Voice Channel Request)"); +} diff --git a/src/dmr/lc/csbk/CSBK_UU_V_REQ.h b/src/dmr/lc/csbk/CSBK_UU_V_REQ.h index 972d8e97..67f1b4e3 100644 --- a/src/dmr/lc/csbk/CSBK_UU_V_REQ.h +++ b/src/dmr/lc/csbk/CSBK_UU_V_REQ.h @@ -46,9 +46,12 @@ namespace dmr CSBK_UU_V_REQ(); /// Decode a control signalling block. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a control signalling block. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); + + /// Returns a string that represents the current CSBK. + virtual std::string toString() override; }; } // namespace csbk } // namespace lc diff --git a/src/dmr/lookups/DMRAffiliationLookup.h b/src/dmr/lookups/DMRAffiliationLookup.h index bf805d29..72f59409 100644 --- a/src/dmr/lookups/DMRAffiliationLookup.h +++ b/src/dmr/lookups/DMRAffiliationLookup.h @@ -49,13 +49,13 @@ namespace dmr virtual ~DMRAffiliationLookup(); /// Helper to grant a channel. - virtual bool grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTimeout); + bool grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTimeout) override; /// Helper to grant a channel and slot. bool grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t slot, uint32_t grantTimeout); /// Helper to release the channel grant for the destination ID. - virtual bool releaseGrant(uint32_t dstId, bool releaseAll); + bool releaseGrant(uint32_t dstId, bool releaseAll) override; /// Helper to determine if the channel number is busy. - virtual bool isChBusy(uint32_t chNo) const; + bool isChBusy(uint32_t chNo) const override; /// Helper to get the slot granted for the given destination ID. uint8_t getGrantedSlot(uint32_t dstId) const; diff --git a/src/dmr/packet/ControlSignaling.cpp b/src/dmr/packet/ControlSignaling.cpp index 22e8104f..ec382d62 100644 --- a/src/dmr/packet/ControlSignaling.cpp +++ b/src/dmr/packet/ControlSignaling.cpp @@ -72,7 +72,7 @@ using namespace dmr::packet; // Make sure control data is supported. #define IS_SUPPORT_CONTROL_CHECK(_PCKT_STR, _PCKT, _SRCID) \ if (!m_slot->m_dmr->getTSCCSlot()->m_enableTSCC) { \ - LogWarning(LOG_RF, "DMR Slot %u, " _PCKT_STR " denial, unsupported service, srcId = %u", m_slot->m_slotNo, _SRCID); \ + LogWarning(LOG_RF, "DMR Slot %u, %s denial, unsupported service, srcId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID); \ writeRF_CSBK_ACK_RSP(_SRCID, TS_DENY_RSN_SYS_UNSUPPORTED_SVC, 0U); \ return false; \ } @@ -80,7 +80,7 @@ using namespace dmr::packet; // Validate the source RID. #define VALID_SRCID(_PCKT_STR, _PCKT, _SRCID) \ if (!acl::AccessControl::validateSrcId(_SRCID)) { \ - LogWarning(LOG_RF, "DMR Slot %u, " _PCKT_STR " denial, RID rejection, srcId = %u", m_slot->m_slotNo, _SRCID); \ + LogWarning(LOG_RF, "DMR Slot %u, %s denial, RID rejection, srcId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID); \ writeRF_CSBK_ACK_RSP(_SRCID, TS_DENY_RSN_PERM_USER_REFUSED, 0U); \ return false; \ } @@ -88,7 +88,7 @@ using namespace dmr::packet; // Validate the target RID. #define VALID_DSTID(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ if (!acl::AccessControl::validateSrcId(_DSTID)) { \ - LogWarning(LOG_RF, "DMR Slot %u, " _PCKT_STR " denial, RID rejection, dstId = %u", m_slot->m_slotNo, _DSTID); \ + LogWarning(LOG_RF, "DMR Slot %u, %s denial, RID rejection, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _DSTID); \ writeRF_CSBK_ACK_RSP(_SRCID, TS_DENY_RSN_TEMP_USER_REFUSED, 0U); \ return false; \ } @@ -96,7 +96,7 @@ using namespace dmr::packet; // Validate the talkgroup ID. #define VALID_TGID(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ if (!acl::AccessControl::validateTGId(0U, _DSTID)) { \ - LogWarning(LOG_RF, "DMR Slot %u, " _PCKT_STR " denial, TGID rejection, dstId = %u", m_slot->m_slotNo, _DSTID); \ + LogWarning(LOG_RF, "DMR Slot %u, %s denial, TGID rejection, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _DSTID); \ writeRF_CSBK_ACK_RSP(_SRCID, TS_DENY_RSN_TGT_GROUP_NOT_VALID, 0U); \ return false; \ } @@ -104,11 +104,35 @@ using namespace dmr::packet; // Verify the source RID is registered. #define VERIFY_SRCID_REG(_PCKT_STR, _PCKT, _SRCID) \ if (!m_slot->m_affiliations->isUnitReg(_SRCID) && m_slot->m_verifyReg) { \ - LogWarning(LOG_RF, "DMR Slot %u, " _PCKT_STR " denial, RID not registered, srcId = %u", m_slot->m_slotNo, _SRCID); \ + LogWarning(LOG_RF, "DMR Slot %u, %s denial, RID not registered, srcId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID); \ writeRF_CSBK_ACK_RSP(_SRCID, TS_DENY_RSN_PERM_USER_REFUSED, 0U); \ return false; \ } +// Macro helper to verbose log a generic CSBK. +#define VERBOSE_LOG_CSBK(_PCKT_STR, _SRCID, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID, _DSTID); \ + } + +// Macro helper to verbose log a generic CSBK. +#define VERBOSE_LOG_CSBK_DST(_PCKT_STR, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _DSTID); \ + } + +// Macro helper to verbose log a generic network CSBK. +#define VERBOSE_LOG_CSBK_NET(_PCKT_STR, _SRCID, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID, _DSTID); \ + } + +// Macro helper to verbose log a generic network CSBK. +#define DEBUG_LOG_CSBK(_PCKT_STR) \ + if (m_debug) { \ + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s", m_slot->m_slotNo, _PCKT_STR.c_str()); \ + } + // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- @@ -173,23 +197,17 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) bool handled = false; switch (csbko) { case CSBKO_UU_V_REQ: - if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_UU_V_REQ (Unit to Unit Voice Service Request), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); - } + VERBOSE_LOG_CSBK(csbk->toString(), srcId, dstId); break; case CSBKO_UU_ANS_RSP: - if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_UU_ANS_RSP (Unit to Unit Voice Service Answer Response), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); - } + VERBOSE_LOG_CSBK(csbk->toString(), srcId, dstId); break; case CSBKO_RAND: { if (csbk->getFID() == FID_DMRA) { if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_CALL_ALRT (Call Alert), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->toString().c_str(), srcId, dstId); } ::ActivityLog("DMR", true, "Slot %u call alert request from %u to %u", m_slot->m_slotNo, srcId, dstId); @@ -205,16 +223,16 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) switch (isp->getServiceKind()) { case SVC_KIND_IND_VOICE_CALL: // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)", SVC_KIND_IND_VOICE_CALL, srcId); + IS_SUPPORT_CONTROL_CHECK(csbk->toString(), SVC_KIND_IND_VOICE_CALL, srcId); // validate the source RID - VALID_SRCID("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)", SVC_KIND_IND_VOICE_CALL, srcId); + VALID_SRCID(csbk->toString(), SVC_KIND_IND_VOICE_CALL, srcId); // validate the target RID - VALID_DSTID("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)", SVC_KIND_IND_VOICE_CALL, srcId, dstId); + VALID_DSTID(csbk->toString(), SVC_KIND_IND_VOICE_CALL, srcId, dstId); // verify the source RID is registered - VERIFY_SRCID_REG("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)", SVC_KIND_IND_VOICE_CALL, srcId); + VERIFY_SRCID_REG(csbk->toString(), SVC_KIND_IND_VOICE_CALL, srcId); writeRF_CSBK_ACK_RSP(srcId, TS_WAIT_RSN, 1U); @@ -226,13 +244,13 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) break; case SVC_KIND_GRP_VOICE_CALL: // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call)", SVC_KIND_GRP_VOICE_CALL, srcId); + IS_SUPPORT_CONTROL_CHECK(csbk->toString(), SVC_KIND_GRP_VOICE_CALL, srcId); // validate the source RID - VALID_SRCID("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call)", SVC_KIND_GRP_VOICE_CALL, srcId); + VALID_SRCID(csbk->toString(), SVC_KIND_GRP_VOICE_CALL, srcId); // validate the talkgroup ID - VALID_TGID("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call)", SVC_KIND_GRP_VOICE_CALL, srcId, dstId); + VALID_TGID(csbk->toString(), SVC_KIND_GRP_VOICE_CALL, srcId, dstId); writeRF_CSBK_ACK_RSP(srcId, TS_WAIT_RSN, 1U); @@ -245,16 +263,16 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) case SVC_KIND_IND_DATA_CALL: case SVC_KIND_IND_UDT_DATA_CALL: // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)", SVC_KIND_IND_VOICE_CALL, srcId); + IS_SUPPORT_CONTROL_CHECK(csbk->toString(), SVC_KIND_IND_VOICE_CALL, srcId); // validate the source RID - VALID_SRCID("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)", SVC_KIND_IND_VOICE_CALL, srcId); + VALID_SRCID(csbk->toString(), SVC_KIND_IND_VOICE_CALL, srcId); // validate the target RID - VALID_DSTID("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)", SVC_KIND_IND_VOICE_CALL, srcId, dstId); + VALID_DSTID(csbk->toString(), SVC_KIND_IND_VOICE_CALL, srcId, dstId); // verify the source RID is registered - VERIFY_SRCID_REG("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call)", SVC_KIND_IND_VOICE_CALL, srcId); + VERIFY_SRCID_REG(csbk->toString(), SVC_KIND_IND_VOICE_CALL, srcId); writeRF_CSBK_ACK_RSP(srcId, TS_WAIT_RSN, 0U); @@ -263,13 +281,13 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) case SVC_KIND_GRP_DATA_CALL: case SVC_KIND_GRP_UDT_DATA_CALL: // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call)", SVC_KIND_GRP_VOICE_CALL, srcId); + IS_SUPPORT_CONTROL_CHECK(csbk->toString(), SVC_KIND_GRP_VOICE_CALL, srcId); // validate the source RID - VALID_SRCID("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call)", SVC_KIND_GRP_VOICE_CALL, srcId); + VALID_SRCID(csbk->toString(), SVC_KIND_GRP_VOICE_CALL, srcId); // validate the talkgroup ID - VALID_TGID("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call)", SVC_KIND_GRP_VOICE_CALL, srcId, dstId); + VALID_TGID(csbk->toString(), SVC_KIND_GRP_VOICE_CALL, srcId, dstId); writeRF_CSBK_ACK_RSP(srcId, TS_WAIT_RSN, 0U); @@ -277,7 +295,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) break; case SVC_KIND_REG_SVC: // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_REG_SVC (Registration Service)", SVC_KIND_REG_SVC, srcId); + IS_SUPPORT_CONTROL_CHECK(csbk->toString(), SVC_KIND_REG_SVC, srcId); writeRF_CSBK_U_Reg_Rsp(srcId, isp->getServiceOptions()); break; @@ -291,11 +309,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) break; case CSBKO_ACK_RSP: { - if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_ACK_RSP (Acknowledge Response), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); - } - + VERBOSE_LOG_CSBK(csbk->toString(), srcId, dstId); ::ActivityLog("DMR", true, "Slot %u ack response from %u to %u", m_slot->m_slotNo, srcId, dstId); } break; @@ -303,8 +317,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) { CSBK_EXT_FNCT* isp = static_cast(csbk.get()); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", - m_slot->m_slotNo, isp->getExtendedFunction(), dstId, srcId); + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, op = $%02X, arg = %u, tgt = %u", + m_slot->m_slotNo, csbk->toString().c_str(), isp->getExtendedFunction(), dstId, srcId); } // generate activity log entry @@ -328,24 +342,21 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) ::ActivityLog("DMR", true, "Slot %u radio uninhibit response from %u to %u", m_slot->m_slotNo, dstId, srcId); break; default: - LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_EXT_FNCT (Extended Function), unhandled op, op = $%02X", m_slot->m_slotNo, isp->getExtendedFunction()); + LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, %s, unhandled op, op = $%02X", m_slot->m_slotNo, csbk->toString().c_str(), isp->getExtendedFunction()); break; } } break; case CSBKO_NACK_RSP: { - if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_NACK_RSP (Negative Acknowledgment Response), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); - } + VERBOSE_LOG_CSBK(csbk->toString(), srcId, dstId); } break; case CSBKO_PRECCSBK: { if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_PRECCSBK (%s Preamble CSBK), toFollow = %u, src = %u, dst = %s%u", - m_slot->m_slotNo, csbk->getDataContent() ? "Data" : "CSBK", csbk->getCBF(), srcId, gi ? "TG " : "", dstId); + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_PRECCSBK (%s Preamble CSBK), toFollow = %u, srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->getDataContent() ? "Data" : "CSBK", csbk->getCBF(), srcId, dstId); } } break; @@ -404,7 +415,6 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) if (csbko == CSBKO_BSDWNACT) return; - bool gi = csbk->getGI(); uint32_t srcId = csbk->getSrcId(); uint32_t dstId = csbk->getDstId(); @@ -414,26 +424,20 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) switch (csbko) { case CSBKO_UU_V_REQ: { - if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_UU_V_REQ (Unit to Unit Voice Service Request), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); - } + VERBOSE_LOG_CSBK_NET(csbk->toString(), srcId, dstId); } break; case CSBKO_UU_ANS_RSP: { - if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_UU_ANS_RSP (Unit to Unit Voice Service Answer Response), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); - } + VERBOSE_LOG_CSBK_NET(csbk->toString(), srcId, dstId); } break; case CSBKO_RAND: { if (csbk->getFID() == FID_DMRA) { if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_CALL_ALRT (Call Alert), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->toString().c_str(), srcId, dstId); } ::ActivityLog("DMR", false, "Slot %u call alert request from %u to %u", m_slot->m_slotNo, srcId, dstId); @@ -493,11 +497,7 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) break; case CSBKO_ACK_RSP: { - if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_ACK_RSP (Acknowledge Response), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); - } - + VERBOSE_LOG_CSBK_NET(csbk->toString(), srcId, dstId); ::ActivityLog("DMR", false, "Slot %u ack response from %u to %u", m_slot->m_slotNo, srcId, dstId); } break; @@ -505,8 +505,8 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) { CSBK_EXT_FNCT* isp = static_cast(csbk.get()); if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", - m_slot->m_slotNo, isp->getExtendedFunction(), dstId, srcId); + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, op = $%02X, arg = %u, tgt = %u", + m_slot->m_slotNo, csbk->toString().c_str(), isp->getExtendedFunction(), dstId, srcId); } // generate activity log entry @@ -530,24 +530,21 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) ::ActivityLog("DMR", false, "Slot %u radio uninhibit response from %u to %u", m_slot->m_slotNo, dstId, srcId); break; default: - LogWarning(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_EXT_FNCT (Extended Function), unhandled op, op = $%02X", m_slot->m_slotNo, isp->getExtendedFunction()); + LogWarning(LOG_NET, "DMR Slot %u, DT_CSBK, %s, unhandled op, op = $%02X", m_slot->m_slotNo, csbk->toString().c_str(), isp->getExtendedFunction()); break; } } break; case CSBKO_NACK_RSP: { - if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_NACK_RSP (Negative Acknowledgment Response), src = %u, dst = %s%u", - m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); - } + VERBOSE_LOG_CSBK_NET(csbk->toString(), srcId, dstId); } break; case CSBKO_PRECCSBK: { if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_PRECCSBK (%s Preamble CSBK), toFollow = %u, src = %u, dst = %s%u", - m_slot->m_slotNo, csbk->getDataContent() ? "Data" : "CSBK", csbk->getCBF(), srcId, gi ? "TG " : "", dstId); + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_PRECCSBK (%s Preamble CSBK), toFollow = %u, srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->getDataContent() ? "Data" : "CSBK", csbk->getCBF(), srcId, dstId); } } break; @@ -612,9 +609,15 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) /// Destination radio ID. void ControlSignaling::writeRF_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId) { + std::unique_ptr csbk = new_unique(CSBK_EXT_FNCT); + csbk->setGI(false); + csbk->setExtendedFunction(func); + csbk->setSrcId(arg); + csbk->setDstId(dstId); + if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", - m_slot->m_slotNo, func, arg, dstId); + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, op = $%02X, arg = %u, tgt = %u", + m_slot->m_slotNo, csbk->toString().c_str(), func, arg, dstId); } // generate activity log entry @@ -628,12 +631,6 @@ void ControlSignaling::writeRF_Ext_Func(uint32_t func, uint32_t arg, uint32_t ds ::ActivityLog("DMR", true, "Slot %u radio uninhibit request from %u to %u", m_slot->m_slotNo, arg, dstId); } - std::unique_ptr csbk = new_unique(CSBK_EXT_FNCT); - csbk->setGI(false); - csbk->setExtendedFunction(func); - csbk->setSrcId(arg); - csbk->setDstId(dstId); - writeRF_CSBK(csbk.get()); } @@ -644,18 +641,14 @@ void ControlSignaling::writeRF_Ext_Func(uint32_t func, uint32_t arg, uint32_t ds /// Destination radio ID. void ControlSignaling::writeRF_Call_Alrt(uint32_t srcId, uint32_t dstId) { - if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_CALL_ALRT (Call Alert), src = %u, dst = %u", - m_slot->m_slotNo, srcId, dstId); - } - - ::ActivityLog("DMR", true, "Slot %u call alert request from %u to %u", m_slot->m_slotNo, srcId, dstId); - std::unique_ptr csbk = new_unique(CSBK_CALL_ALRT); csbk->setGI(false); csbk->setSrcId(srcId); csbk->setDstId(dstId); + VERBOSE_LOG_CSBK(csbk->toString(), srcId, dstId); + ::ActivityLog("DMR", true, "Slot %u call alert request from %u to %u", m_slot->m_slotNo, srcId, dstId); + writeRF_CSBK(csbk.get()); } @@ -693,11 +686,12 @@ ControlSignaling::~ControlSignaling() /// /// /// -void ControlSignaling::writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite) +/// +void ControlSignaling::writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite, bool imm) { // don't add any frames if the queue is full uint8_t len = DMR_FRAME_LENGTH_BYTES + 2U; - uint32_t space = m_slot->m_queue.freeSpace(); + uint32_t space = m_slot->m_txQueue.freeSpace(); if (space < (len + 1U)) { return; } @@ -725,14 +719,14 @@ void ControlSignaling::writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite) if (clearBeforeWrite) { if (m_slot->m_slotNo == 1U) - m_slot->m_modem->clearDMRData1(); + m_slot->m_modem->clearDMRFrame1(); if (m_slot->m_slotNo == 2U) - m_slot->m_modem->clearDMRData2(); - m_slot->m_queue.clear(); + m_slot->m_modem->clearDMRFrame2(); + m_slot->m_txQueue.clear(); } if (m_slot->m_duplex) - m_slot->addFrame(data); + m_slot->addFrame(data, false, imm); } /// @@ -749,7 +743,7 @@ void ControlSignaling::writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint csbk->setSrcId(DMR_WUID_ALL); // hmmm... csbk->setDstId(dstId); - writeRF_CSBK(csbk.get()); + writeRF_CSBK_Imm(csbk.get()); } /// @@ -766,7 +760,7 @@ void ControlSignaling::writeRF_CSBK_NACK_RSP(uint32_t dstId, uint8_t reason, uin csbk->setSrcId(DMR_WUID_ALL); // hmmm... csbk->setDstId(dstId); - writeRF_CSBK(csbk.get()); + writeRF_CSBK_Imm(csbk.get()); } /// @@ -933,8 +927,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ csbk->setSlotNo(slot); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", - m_tscc->m_slotNo, emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); + LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + m_tscc->m_slotNo, csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } csbk->setEmergency(emergency); @@ -943,7 +937,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ // transmit group grant (x2) for (uint8_t i = 0; i < 2U; i++) - writeRF_CSBK(csbk.get()); + writeRF_CSBK_Imm(csbk.get()); // if the channel granted isn't the same as the TSCC; remote activate the payload channel if (chNo != m_tscc->m_channelNo) { @@ -1008,8 +1002,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ csbk->setSlotNo(slot); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", - m_tscc->m_slotNo, emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); + LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + m_tscc->m_slotNo, csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } csbk->setEmergency(emergency); @@ -1018,7 +1012,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ // transmit private grant (x2) for (uint8_t i = 0; i < 2U; i++) - writeRF_CSBK(csbk.get()); + writeRF_CSBK_Imm(csbk.get()); // if the channel granted isn't the same as the TSCC; remote activate the payload channel if (chNo != m_tscc->m_channelNo) { @@ -1166,8 +1160,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u csbk->setSlotNo(slot); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_DATA_CALL (Group Data Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", - m_tscc->m_slotNo, emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); + LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + m_tscc->m_slotNo, csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } csbk->setEmergency(emergency); @@ -1176,7 +1170,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u // transmit group grant (x2) for (uint8_t i = 0; i < 2U; i++) - writeRF_CSBK(csbk.get()); + writeRF_CSBK_Imm(csbk.get()); // if the channel granted isn't the same as the TSCC; remote activate the payload channel if (chNo != m_tscc->m_channelNo) { @@ -1213,8 +1207,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u csbk->setSlotNo(slot); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_DATA_CALL (Individual Data Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", - m_tscc->m_slotNo, emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); + LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + m_tscc->m_slotNo, csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } csbk->setEmergency(emergency); @@ -1223,7 +1217,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u // transmit private grant (x2) for (uint8_t i = 0; i < 2U; i++) - writeRF_CSBK(csbk.get()); + writeRF_CSBK_Imm(csbk.get()); // if the channel granted isn't the same as the TSCC; remote activate the payload channel if (chNo != m_tscc->m_channelNo) { @@ -1267,7 +1261,7 @@ void ControlSignaling::writeRF_CSBK_U_Reg_Rsp(uint32_t srcId, uint8_t serviceOpt if (!dereg) { if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_REG_SVC (Registration Service), srcId = %u, serviceOptions = $%02X", m_tscc->m_slotNo, srcId, serviceOptions); + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, srcId = %u, serviceOptions = $%02X", m_tscc->m_slotNo, csbk->toString().c_str(), srcId, serviceOptions); } // remove dynamic unit registration table entry @@ -1281,14 +1275,14 @@ void ControlSignaling::writeRF_CSBK_U_Reg_Rsp(uint32_t srcId, uint8_t serviceOpt // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { - LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_REG_SVC (Registration Service), denial, RID rejection, srcId = %u", m_tscc->m_slotNo, srcId); + LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, %s, denial, RID rejection, srcId = %u", m_tscc->m_slotNo, csbk->toString().c_str(), srcId); ::ActivityLog("DMR", true, "unit registration request from %u denied", srcId); csbk->setReason(TS_DENY_RSN_REG_DENIED); } if (csbk->getReason() == TS_ACK_RSN_REG) { if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_REG_SVC (Registration Service), srcId = %u, serviceOptions = $%02X", m_tscc->m_slotNo, srcId, serviceOptions); + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, srcId = %u, serviceOptions = $%02X", m_tscc->m_slotNo, csbk->toString().c_str(), srcId, serviceOptions); } ::ActivityLog("DMR", true, "unit registration request from %u", srcId); @@ -1303,7 +1297,7 @@ void ControlSignaling::writeRF_CSBK_U_Reg_Rsp(uint32_t srcId, uint8_t serviceOpt csbk->setSrcId(DMR_WUID_REGI); csbk->setDstId(srcId); - writeRF_CSBK(csbk.get()); + writeRF_CSBK_Imm(csbk.get()); } /// @@ -1360,8 +1354,8 @@ void ControlSignaling::writeRF_CSBK_Payload_Grant(uint32_t dstId, uint32_t srcId csbk->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBK_P_GRANT (Payload Grant), chNo = %u, slot = %u, srcId = %u, dstId = %u", - m_slot->m_slotNo, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, chNo = %u, slot = %u, srcId = %u, dstId = %u", + m_slot->m_slotNo, csbk->toString().c_str(), csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } writeRF_CSBK(csbk.get()); @@ -1372,11 +1366,8 @@ void ControlSignaling::writeRF_CSBK_Payload_Grant(uint32_t dstId, uint32_t srcId /// void ControlSignaling::writeRF_TSCC_Aloha() { - if (m_debug) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_ALOHA (Aloha)", m_slot->m_slotNo); - } - std::unique_ptr csbk = new_unique(CSBK_ALOHA); + DEBUG_LOG_CSBK(csbk->toString()); csbk->setNRandWait(m_slot->m_alohaNRandWait); csbk->setBackoffNo(m_slot->m_alohaBackOff); @@ -1390,11 +1381,6 @@ void ControlSignaling::writeRF_TSCC_Aloha() /// void ControlSignaling::writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd) { - if (m_debug) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_BROADCAST (Broadcast), BCAST_ANNC_ANN_WD_TSCC (Announce-WD TSCC Channel), channelNo = %u, annWd = %u", - m_slot->m_slotNo, channelNo, annWd); - } - m_slot->m_rfSeqNo = 0U; std::unique_ptr csbk = new_unique(CSBK_BROADCAST); @@ -1404,6 +1390,11 @@ void ControlSignaling::writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd) csbk->setLogicalCh1(channelNo); csbk->setAnnWdCh1(annWd); + if (m_debug) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, channelNo = %u, annWd = %u", + m_slot->m_slotNo, csbk->toString().c_str(), channelNo, annWd); + } + writeRF_CSBK(csbk.get()); } @@ -1412,11 +1403,8 @@ void ControlSignaling::writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd) /// void ControlSignaling::writeRF_TSCC_Bcast_Sys_Parm() { - if (m_debug) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_BROADCAST (Broadcast), BCAST_ANNC_SITE_PARMS (Announce Site Parms)", m_slot->m_slotNo); - } - std::unique_ptr csbk = new_unique(CSBK_BROADCAST); + DEBUG_LOG_CSBK(csbk->toString()); csbk->setAnncType(BCAST_ANNC_SITE_PARMS); writeRF_CSBK(csbk.get()); @@ -1427,11 +1415,8 @@ void ControlSignaling::writeRF_TSCC_Bcast_Sys_Parm() /// void ControlSignaling::writeRF_TSCC_Git_Hash() { - if (m_debug) { - LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_DVM_GIT_HASH (DVM Git Hash)", m_slot->m_slotNo); - } - std::unique_ptr csbk = new_unique(CSBK_DVM_GIT_HASH); + DEBUG_LOG_CSBK(csbk->toString()); writeRF_CSBK(csbk.get()); } diff --git a/src/dmr/packet/ControlSignaling.h b/src/dmr/packet/ControlSignaling.h index f35cd77e..51fd84ff 100644 --- a/src/dmr/packet/ControlSignaling.h +++ b/src/dmr/packet/ControlSignaling.h @@ -39,8 +39,6 @@ #include "modem/Modem.h" #include "network/BaseNetwork.h" #include "RingBuffer.h" -#include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" #include "StopWatch.h" #include "Timer.h" @@ -87,8 +85,10 @@ namespace dmr /// Finalizes a instance of the ControlSignaling class. ~ControlSignaling(); + /// Helper to write a immediate CSBK packet. + void writeRF_CSBK_Imm(lc::CSBK *csbk) { writeRF_CSBK(csbk, false, true); } /// Helper to write a CSBK packet. - void writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite = false); + void writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite = false, bool imm = false); /// Helper to write a ACK RSP packet. void writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint8_t responseInfo); diff --git a/src/dmr/packet/Data.h b/src/dmr/packet/Data.h index 94498502..9f688e05 100644 --- a/src/dmr/packet/Data.h +++ b/src/dmr/packet/Data.h @@ -40,8 +40,6 @@ #include "modem/Modem.h" #include "network/BaseNetwork.h" #include "RingBuffer.h" -#include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" #include "StopWatch.h" #include "Timer.h" diff --git a/src/dmr/packet/Voice.cpp b/src/dmr/packet/Voice.cpp index c3eeaf0a..c1436dbd 100644 --- a/src/dmr/packet/Voice.cpp +++ b/src/dmr/packet/Voice.cpp @@ -192,7 +192,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rssiCount = 1U; if (m_slot->m_duplex) { - m_slot->m_queue.clear(); + m_slot->m_txQueue.clear(); m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) @@ -549,7 +549,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_rssiCount = 1U; if (m_slot->m_duplex) { - m_slot->m_queue.clear(); + m_slot->m_txQueue.clear(); m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) @@ -700,7 +700,7 @@ void Voice::processNetwork(const data::Data& dmrData) m_slot->m_voice->m_netTalkerId = TALKER_ID_NONE; if (m_slot->m_duplex) { - m_slot->m_queue.clear(); + m_slot->m_txQueue.clear(); m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); } @@ -746,7 +746,7 @@ void Voice::processNetwork(const data::Data& dmrData) m_slot->m_netTimeout = false; if (m_slot->m_duplex) { - m_slot->m_queue.clear(); + m_slot->m_txQueue.clear(); m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); } @@ -846,7 +846,7 @@ void Voice::processNetwork(const data::Data& dmrData) m_slot->m_netTimeout = false; if (m_slot->m_duplex) { - m_slot->m_queue.clear(); + m_slot->m_txQueue.clear(); m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); } @@ -905,6 +905,11 @@ void Voice::processNetwork(const data::Data& dmrData) m_slot->m_slotNo, m_netN, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); } } + + if (m_netN >= 5U) { + m_slot->m_netErrs = 0U; + } + m_slot->m_netBits += 141U; data[0U] = modem::TAG_DATA; diff --git a/src/dmr/packet/Voice.h b/src/dmr/packet/Voice.h index 9ed14213..fbf5f2d0 100644 --- a/src/dmr/packet/Voice.h +++ b/src/dmr/packet/Voice.h @@ -38,8 +38,6 @@ #include "dmr/lc/PrivacyLC.h" #include "edac/AMBEFEC.h" #include "network/BaseNetwork.h" -#include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" #include diff --git a/src/edac/CRC.cpp b/src/edac/CRC.cpp index 38c44ee7..9541f6da 100644 --- a/src/edac/CRC.cpp +++ b/src/edac/CRC.cpp @@ -712,6 +712,29 @@ uint16_t CRC::addCRC16(uint8_t* in, uint32_t bitLength) return crc; } +/// +/// +/// +/// Input byte array. +/// Length of byte array in bits. +/// +uint16_t CRC::createCRC16(const uint8_t* in, uint32_t bitLength) +{ + uint16_t crc = 0xFFFFU; + + for (uint32_t i = 0U; i < bitLength; i++) { + bool bit1 = READ_BIT(in, i) != 0x00U; + bool bit2 = (crc & 0x8000U) == 0x8000U; + + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= 0x1021U; + } + + return crc & 0xFFFFU; +} + // --------------------------------------------------------------------------- // Private Static Class Members // --------------------------------------------------------------------------- @@ -784,26 +807,3 @@ uint16_t CRC::createCRC15(const uint8_t* in, uint32_t bitLength) return crc & 0x7FFFU; } - -/// -/// -/// -/// Input byte array. -/// Length of byte array in bits. -/// -uint16_t CRC::createCRC16(const uint8_t* in, uint32_t bitLength) -{ - uint16_t crc = 0xFFFFU; - - for (uint32_t i = 0U; i < bitLength; i++) { - bool bit1 = READ_BIT(in, i) != 0x00U; - bool bit2 = (crc & 0x8000U) == 0x8000U; - - crc <<= 1; - - if (bit1 ^ bit2) - crc ^= 0x1021U; - } - - return crc & 0xFFFFU; -} diff --git a/src/edac/CRC.h b/src/edac/CRC.h index a62062bc..7f74946c 100644 --- a/src/edac/CRC.h +++ b/src/edac/CRC.h @@ -88,6 +88,9 @@ namespace edac /// Encode 16-bit CRC CCITT-162 w/ initial generator of 1. static uint16_t addCRC16(uint8_t* in, uint32_t bitLength); + /// + static uint16_t createCRC16(const uint8_t* in, uint32_t bitLength); + private: /// static uint8_t createCRC6(const uint8_t* in, uint32_t bitLength); @@ -95,8 +98,6 @@ namespace edac static uint16_t createCRC12(const uint8_t* in, uint32_t bitLength); /// static uint16_t createCRC15(const uint8_t* in, uint32_t bitLength); - /// - static uint16_t createCRC16(const uint8_t* in, uint32_t bitLength); }; } // namespace edac diff --git a/src/host/Console.cpp b/src/host/Console.cpp index b33badbe..d0c098a4 100644 --- a/src/host/Console.cpp +++ b/src/host/Console.cpp @@ -31,64 +31,15 @@ #include -#if defined(_WIN32) || defined(_WIN64) -#include -#else #include #include #include #include -#endif // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- -#if defined(_WIN32) || defined(_WIN64) -/// -/// Initializes a new instance of the Console class. -/// -Console::Console() -{ - /* stub */ -} - -/// -/// Finalizes a instance of the Console class. -/// -Console::~Console() -{ - /* stub */ -} - -/// -/// Opens the terminal console. -/// -bool Console::open() -{ - return true; -} - -/// -/// Retrieves a character input on the keyboard. -/// -/// -int Console::getChar() -{ - if (::_kbhit() == 0) - return -1; - - return ::_getch(); -} - -/// -/// Closes the terminal console. -/// -void Console::close() -{ - /* stub */ -} -#else /// /// Initializes a new instance of the Console class. /// @@ -173,7 +124,6 @@ void Console::close() if (n != 0) ::fprintf(stderr, "tcsetattr: returned %d\r\n", n); } -#endif /// /// Retrieves an array of characters input on the keyboard. diff --git a/src/host/Console.h b/src/host/Console.h index 16101d3c..06e1d413 100644 --- a/src/host/Console.h +++ b/src/host/Console.h @@ -32,9 +32,7 @@ #include "Defines.h" -#if !defined(_WIN32) && !defined(_WIN64) #include -#endif // --------------------------------------------------------------------------- // Class Declaration @@ -62,9 +60,7 @@ public: void close(); private: -#if !defined(_WIN32) && !defined(_WIN64) termios m_termios; -#endif }; #endif // __CONSOLE_H__ diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 58965f37..eb4bbd29 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -58,13 +58,11 @@ using namespace lookups; #include #include -#if !defined(_WIN32) && !defined(_WIN64) #include #include #include #include #include -#endif // --------------------------------------------------------------------------- // Constants @@ -115,6 +113,7 @@ Host::Host(const std::string& confFile) : m_channelNo(0U), m_voiceChNo(), m_voiceChData(), + m_controlChData(), m_idenTable(nullptr), m_ridLookup(nullptr), m_tidLookup(nullptr), @@ -145,7 +144,7 @@ Host::Host(const std::string& confFile) : m_idleTickDelay(5U), m_RESTAPI(nullptr) { - UDPSocket::startup(); + /* stub */ } /// @@ -153,7 +152,7 @@ Host::Host(const std::string& confFile) : /// Host::~Host() { - UDPSocket::shutdown(); + /* stub */ } /// @@ -170,7 +169,7 @@ int Host::run() } } catch (yaml::OperationException const& e) { - ::fatal("cannot read the configuration file, %s", e.message()); + ::fatal("cannot read the configuration file - %s (%s)", m_confFile.c_str(), e.message()); } bool m_daemon = m_conf["daemon"].as(false); @@ -190,7 +189,6 @@ int Host::run() ::fatal("unable to open the activity log file\n"); } -#if !defined(_WIN32) && !defined(_WIN64) // handle POSIX process forking if (m_daemon) { // create new process @@ -227,7 +225,6 @@ int Host::run() ::close(STDOUT_FILENO); ::close(STDERR_FILENO); } -#endif // !defined(_WIN32) && !defined(_WIN64) getHostVersion(); ::LogInfo(">> Modem Controller"); @@ -314,13 +311,13 @@ int Host::run() uint32_t tidReloadTime = systemConf["talkgroup_id"]["time"].as(0U); bool tidAcl = systemConf["talkgroup_id"]["acl"].as(false); - LogInfo("Talkgroup Id Lookups"); + LogInfo("Talkgroup Rule Lookups"); LogInfo(" File: %s", tidLookupFile.length() > 0U ? tidLookupFile.c_str() : "None"); if (tidReloadTime > 0U) LogInfo(" Reload: %u mins", tidReloadTime); LogInfo(" ACL: %s", tidAcl ? "yes" : "no"); - m_tidLookup = new TalkgroupIdLookup(tidLookupFile, tidReloadTime, tidAcl); + m_tidLookup = new TalkgroupRulesLookup(tidLookupFile, tidReloadTime, tidAcl); m_tidLookup->read(); // initialize networking @@ -431,10 +428,11 @@ int Host::run() g_fireDMRBeacon = true; } - dmr = std::unique_ptr(new dmr::Control(m_authoritative, m_dmrColorCode, callHang, m_dmrQueueSizeBytes, embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang, - m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, jitter, dmrDumpDataPacket, dmrRepeatDataPacket, - dmrDumpCsbkData, dmrDebug, dmrVerbose)); - dmr->setOptions(m_conf, m_supervisor, m_voiceChNo, m_voiceChData, m_dmrNetId, m_siteId, m_channelId, m_channelNo, true); + dmr = std::unique_ptr(new dmr::Control(m_authoritative, m_dmrColorCode, callHang, m_dmrQueueSizeBytes, + embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, + m_idenTable, rssi, jitter, dmrDumpDataPacket, dmrRepeatDataPacket, dmrDumpCsbkData, dmrDebug, dmrVerbose)); + dmr->setOptions(m_conf, m_supervisor, m_voiceChNo, m_voiceChData, m_controlChData, m_dmrNetId, m_siteId, m_channelId, + m_channelNo, true); if (dmrCtrlChannel) { dmr->setCCRunning(true); @@ -504,11 +502,11 @@ int Host::run() } } - p25 = std::unique_ptr(new p25::Control(m_authoritative, m_p25NAC, callHang, m_p25QueueSizeBytes, m_modem, m_network, m_timeout, m_rfTalkgroupHang, - m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket, p25RepeatDataPacket, - p25DumpTsbkData, p25Debug, p25Verbose)); - p25->setOptions(m_conf, m_supervisor, m_cwCallsign, m_voiceChNo, m_voiceChData, m_p25PatchSuperGroup, m_p25NetId, m_sysId, m_p25RfssId, - m_siteId, m_channelId, m_channelNo, true); + p25 = std::unique_ptr(new p25::Control(m_authoritative, m_p25NAC, callHang, m_p25QueueSizeBytes, m_modem, + m_network, m_timeout, m_rfTalkgroupHang, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket, + p25RepeatDataPacket, p25DumpTsbkData, p25Debug, p25Verbose)); + p25->setOptions(m_conf, m_supervisor, m_cwCallsign, m_voiceChNo, m_voiceChData, m_controlChData, + m_p25PatchSuperGroup, m_p25NetId, m_sysId, m_p25RfssId, m_siteId, m_channelId, m_channelNo, true); if (p25CtrlChannel) { p25->setCCRunning(true); @@ -569,10 +567,11 @@ int Host::run() } } - nxdn = std::unique_ptr(new nxdn::Control(m_authoritative, m_nxdnRAN, callHang, m_nxdnQueueSizeBytes, m_timeout, m_rfTalkgroupHang, - m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, + nxdn = std::unique_ptr(new nxdn::Control(m_authoritative, m_nxdnRAN, callHang, m_nxdnQueueSizeBytes, + m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, nxdnDumpRcchData, nxdnDebug, nxdnVerbose)); - nxdn->setOptions(m_conf, m_supervisor, m_cwCallsign, m_voiceChNo, m_voiceChData, m_siteId, m_sysId, m_channelId, m_channelNo, true); + nxdn->setOptions(m_conf, m_supervisor, m_cwCallsign, m_voiceChNo, m_voiceChData, m_controlChData, m_siteId, + m_sysId, m_channelId, m_channelNo, true); if (nxdnCtrlChannel) { nxdn->setCCRunning(true); @@ -879,7 +878,7 @@ int Host::run() if (m_state == STATE_DMR) { START_DMR_DUPLEX_IDLE(true); - m_modem->writeDMRData1(data, len); + m_modem->writeDMRFrame1(data, len); // if there is no DMR CC running; run the interrupt macro to stop // any running DMR beacon @@ -924,7 +923,7 @@ int Host::run() if (m_state == STATE_DMR) { START_DMR_DUPLEX_IDLE(true); - m_modem->writeDMRData2(data, len); + m_modem->writeDMRFrame2(data, len); // if there is no DMR CC running; run the interrupt macro to stop // any running DMR beacon @@ -971,9 +970,9 @@ int Host::run() setState(STATE_P25); } - // if the state is P25; write P25 data + // if the state is P25; write P25 frame data if (m_state == STATE_P25) { - m_modem->writeP25Data(data, len); + m_modem->writeP25Frame(data, len); INTERRUPT_DMR_BEACON; @@ -1051,7 +1050,7 @@ int Host::run() // if the state is NXDN; write NXDN data if (m_state == STATE_NXDN) { - m_modem->writeNXDNData(data, len); + m_modem->writeNXDNFrame(data, len); INTERRUPT_DMR_BEACON; @@ -1087,7 +1086,7 @@ int Host::run() if (dmr != nullptr) { // read DMR slot 1 frames from the modem, and if there is any // write those frames to the DMR controller - len = m_modem->readDMRData1(data); + len = m_modem->readDMRFrame1(data); if (len > 0U) { if (m_state == STATE_IDLE) { // if the modem is in duplex -- process wakeup CSBKs @@ -1148,7 +1147,7 @@ int Host::run() // read DMR slot 2 frames from the modem, and if there is any // write those frames to the DMR controller - len = m_modem->readDMRData2(data); + len = m_modem->readDMRFrame2(data); if (len > 0U) { if (m_state == STATE_IDLE) { // if the modem is in duplex -- process wakeup CSBKs @@ -1213,7 +1212,7 @@ int Host::run() // read P25 frames from modem, and if there are frames // write those frames to the P25 controller if (p25 != nullptr) { - len = m_modem->readP25Data(data); + len = m_modem->readP25Frame(data); if (len > 0U) { if (m_state == STATE_IDLE) { // process P25 frames @@ -1287,7 +1286,7 @@ int Host::run() // write those frames to the NXDN controller #if defined(ENABLE_NXDN) if (nxdn != nullptr) { - len = m_modem->readNXDNData(data); + len = m_modem->readNXDNFrame(data); if (len > 0U) { if (m_state == STATE_IDLE) { // process NXDN frames @@ -1594,8 +1593,8 @@ int Host::run() if (dmr != nullptr) { if (m_dmrCtrlChannel) { if (!hasTxShutdown) { - m_modem->clearDMRData1(); - m_modem->clearDMRData2(); + m_modem->clearDMRFrame1(); + m_modem->clearDMRFrame2(); } dmr->setCCRunning(false); @@ -1611,7 +1610,7 @@ int Host::run() if (p25 != nullptr) { if (m_p25CtrlChannel) { if (!hasTxShutdown) { - m_modem->clearP25Data(); + m_modem->clearP25Frame(); p25->reset(); } @@ -1627,7 +1626,7 @@ int Host::run() if (nxdn != nullptr) { if (m_nxdnCtrlChannel) { if (!hasTxShutdown) { - m_modem->clearNXDNData(); + m_modem->clearNXDNFrame(); nxdn->reset(); } @@ -1785,6 +1784,9 @@ bool Host::readParams() m_idenTable = new IdenTableLookup(idenLookupFile, idenReloadTime); m_idenTable->read(); + /* + ** Channel Configuration + */ yaml::Node rfssConfig = systemConf["config"]; m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); if (m_channelId > 15U) { // clamp to 15 @@ -1825,6 +1827,25 @@ bool Host::readParams() m_rxFrequency = m_txFrequency; } + /* + ** Control Channel + */ + { + yaml::Node controlCh = rfssConfig["controlCh"]; + + std::string restApiAddress = controlCh["restAddress"].as("127.0.0.1"); + uint16_t restApiPort = (uint16_t)controlCh["restPort"].as(REST_API_DEFAULT_PORT); + std::string restApiPassword = controlCh["restPassword"].as(); + + VoiceChData data = VoiceChData(0U, restApiAddress, restApiPort, restApiPassword); + m_controlChData = data; + + ::LogInfoEx(LOG_HOST, "Control Channel REST API Adddress %s:%u", m_controlChData.address().c_str(), m_controlChData.port()); + } + + /* + ** Voice Channels + */ yaml::Node& voiceChList = rfssConfig["voiceChNo"]; if (voiceChList.size() == 0U) { @@ -1866,6 +1887,9 @@ bool Host::readParams() } strVoiceChNo.erase(strVoiceChNo.find_last_of(",")); + /* + ** Site Parameters + */ m_siteId = (uint8_t)::strtoul(rfssConfig["siteId"].as("1").c_str(), NULL, 16); m_siteId = p25::P25Utils::siteId(m_siteId); @@ -2060,6 +2084,10 @@ bool Host::createModem() uint8_t rssiCoarse = (uint8_t)softpotParams["rssiCoarse"].as(127U); uint8_t rssiFine = (uint8_t)softpotParams["rssiFine"].as(127U); + uint16_t dmrFifoLength = (uint16_t)modemConf["dmrFifoLength"].as(DMR_TX_BUFFER_LEN); + uint16_t p25FifoLength = (uint16_t)modemConf["p25FifoLength"].as(P25_TX_BUFFER_LEN); + uint16_t nxdnFifoLength = (uint16_t)modemConf["nxdnFifoLength"].as(NXDN_TX_BUFFER_LEN); + float rxLevel = modemConf["rxLevel"].as(50.0F); float cwIdTXLevel = modemConf["cwIdTxLevel"].as(50.0F); float dmrTXLevel = modemConf["dmrTxLevel"].as(50.0F); @@ -2127,14 +2155,9 @@ bool Host::createModem() } if (portType == PTY_PORT) { -#if !defined(_WIN32) && !defined(_WIN64) modemPort = new port::UARTPort(uartPort, serialSpeed, false); LogInfo(" PTY Port: %s", uartPort.c_str()); LogInfo(" PTY Speed: %u", uartSpeed); -#else - LogError(LOG_HOST, "Pseudo PTY is not supported on Windows!"); - return false; -#endif // !defined(_WIN32) && !defined(_WIN64) } else { modemPort = new port::UARTPort(uartPort, serialSpeed, true); @@ -2193,6 +2216,9 @@ bool Host::createModem() LogInfo(" DMR Queue Size: %u (%u bytes)", dmrQueueSize, m_dmrQueueSizeBytes); LogInfo(" P25 Queue Size: %u (%u bytes)", p25QueueSize, m_p25QueueSizeBytes); LogInfo(" NXDN Queue Size: %u (%u bytes)", nxdnQueueSize, m_nxdnQueueSizeBytes); + LogInfo(" DMR FIFO Size: %u bytes", dmrFifoLength); + LogInfo(" P25 FIFO Size: %u bytes", p25FifoLength); + LogInfo(" NXDN FIFO Size: %u bytes", nxdnFifoLength); if (m_useDFSI) { LogInfo(" Digital Fixed Station Interface: yes"); @@ -2241,6 +2267,8 @@ bool Host::createModem() return false; } + m_modem->setFifoLength(dmrFifoLength, p25FifoLength, nxdnFifoLength); + // are we on a protocol version older then 3? if (m_modem->getVersion() < 3U) { if (m_nxdnEnabled) { @@ -2273,7 +2301,7 @@ bool Host::createNetwork() uint16_t restApiPort = (uint16_t)networkConf["restPort"].as(REST_API_DEFAULT_PORT); std::string restApiPassword = networkConf["restPassword"].as(); bool restApiDebug = networkConf["restDebug"].as(false); - uint32_t id = networkConf["id"].as(0U); + uint32_t id = networkConf["id"].as(1001U); uint32_t jitter = networkConf["talkgroupHang"].as(360U); std::string password = networkConf["password"].as(); bool slot1 = networkConf["slot1"].as(true); @@ -2300,7 +2328,7 @@ bool Host::createNetwork() LogInfo("Network Parameters"); LogInfo(" Enabled: %s", netEnable ? "yes" : "no"); if (netEnable) { - LogInfo(" Peer Id: %u", id); + LogInfo(" Peer ID: %u", id); LogInfo(" Address: %s", address.c_str()); LogInfo(" Port: %u", port); if (local > 0U) @@ -2339,6 +2367,7 @@ bool Host::createNetwork() m_network->setRESTAPIData(restApiPassword, restApiPort); } + m_network->enable(true); bool ret = m_network->open(); if (!ret) { delete m_network; @@ -2347,7 +2376,6 @@ bool Host::createNetwork() return false; } - m_network->enable(true); ::LogSetNetwork(m_network); } @@ -2532,9 +2560,9 @@ void Host::setState(uint8_t state) m_modem->setState(STATE_IDLE); - m_modem->clearDMRData1(); - m_modem->clearDMRData2(); - m_modem->clearP25Data(); + m_modem->clearDMRFrame1(); + m_modem->clearDMRFrame2(); + m_modem->clearP25Frame(); if (m_state == HOST_STATE_ERROR) { m_modem->sendCWId(m_cwCallsign); @@ -2554,15 +2582,6 @@ void Host::setState(uint8_t state) delete m_modem; } - if (m_tidLookup != nullptr) { - m_tidLookup->stop(); - delete m_tidLookup; - } - if (m_ridLookup != nullptr) { - m_ridLookup->stop(); - delete m_ridLookup; - } - if (m_network != nullptr) { m_network->close(); delete m_network; @@ -2572,6 +2591,15 @@ void Host::setState(uint8_t state) m_RESTAPI->close(); delete m_RESTAPI; } + + if (m_tidLookup != nullptr) { + m_tidLookup->stop(); + delete m_tidLookup; + } + if (m_ridLookup != nullptr) { + m_ridLookup->stop(); + delete m_ridLookup; + } } else { m_state = STATE_IDLE; diff --git a/src/host/Host.h b/src/host/Host.h index e83f2955..750d01c0 100644 --- a/src/host/Host.h +++ b/src/host/Host.h @@ -39,7 +39,7 @@ #include "lookups/AffiliationLookup.h" #include "lookups/IdenTableLookup.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" #include "yaml/Yaml.h" #include @@ -117,10 +117,11 @@ private: std::vector m_voiceChNo; std::unordered_map m_voiceChData; + lookups::VoiceChData m_controlChData; lookups::IdenTableLookup* m_idenTable; lookups::RadioIdLookup* m_ridLookup; - lookups::TalkgroupIdLookup* m_tidLookup; + lookups::TalkgroupRulesLookup* m_tidLookup; bool m_dmrBeacons; bool m_dmrTSCCData; diff --git a/src/host/calibrate/HostCal.cpp b/src/host/calibrate/HostCal.cpp index 63b631f0..e239cf6e 100644 --- a/src/host/calibrate/HostCal.cpp +++ b/src/host/calibrate/HostCal.cpp @@ -13,7 +13,7 @@ /* * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX * Copyright (C) 2017,2018 by Andy Uribe CA6JAU -* Copyright (C) 2017-2022 by Bryan Biedenkapp N2PLL +* Copyright (C) 2017-2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,101 +30,13 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "host/calibrate/HostCal.h" -#include "dmr/DMRDefines.h" -#include "modem/port/UARTPort.h" -#include "p25/P25Defines.h" -#include "p25/data/DataHeader.h" -#include "p25/lc/LC.h" -#include "p25/lc/tsbk/TSBKFactory.h" -#include "p25/NID.h" -#include "p25/Sync.h" -#include "p25/P25Utils.h" -#include "nxdn/NXDNDefines.h" -#include "nxdn/channel/LICH.h" -#include "nxdn/NXDNUtils.h" -#include "edac/CRC.h" +#include "modem/Modem.h" #include "HostMain.h" #include "Log.h" -#include "StopWatch.h" -#include "Utils.h" using namespace modem; using namespace lookups; -#include -#include - -#if !defined(_WIN32) && !defined(_WIN64) -#include -#endif - -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -#define DMR_CAL_STR "[Tx] DMR 1200 Hz Tone Mode (2.75Khz Deviation)" -#define P25_CAL_STR "[Tx] P25 1200 Hz Tone Mode (2.83Khz Deviation)" -#define DMR_LF_CAL_STR "[Tx] DMR Low Frequency Mode (80 Hz square wave)" -#define DMR_CAL_1K_STR "[Tx] DMR BS 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" -#define DMR_DMO_CAL_1K_STR "[Tx] DMR MS 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" -#define P25_CAL_1K_STR "[Tx] P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" -#define P25_TDU_TEST_STR "[Tx] P25 TDU Data Test Stream" -#define NXDN_CAL_1K_STR "[Tx] NXDN 1031 Hz Test Pattern (RAN1 ID1 TG1)" -#define DMR_FEC_STR "[Rx] DMR MS FEC BER Test Mode" -#define DMR_FEC_1K_STR "[Rx] DMR MS 1031 Hz Test Pattern (CC1 ID1 TG9)" -#define P25_FEC_STR "[Rx] P25 FEC BER Test Mode" -#define P25_FEC_1K_STR "[Rx] P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" -#define NXDN_FEC_STR "[Rx] NXDN FEC BER Test Mode" -#define RSSI_CAL_STR "RSSI Calibration Mode" - -// Voice LC MS Header, CC: 1, srcID: 1, dstID: TG9 -const uint8_t VH_DMO1K[] = { - 0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U, - 0x60U, 0x84U, 0x6DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x7EU, 0x30U, 0x30U, - 0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU }; - -// Voice Term MS with LC, CC: 1, srcID: 1, dstID: TG9 -const uint8_t VT_DMO1K[] = { - 0x00U, 0x4FU, 0x08U, 0xDCU, 0x02U, 0x88U, 0x15U, 0x78U, 0x2CU, 0xD0U, 0x14U, - 0xC0U, 0x84U, 0xADU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x79U, 0x65U, 0x24U, - 0x02U, 0x28U, 0x06U, 0x20U, 0x0FU, 0x80U, 0x1BU, 0xC1U, 0x07U, 0x80U, 0x5CU }; - -// Voice coding data + FEC, 1031 Hz Test Pattern -const uint8_t VOICE_1K[] = { - 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU, 0xCEU, 0xA8U, - 0xFEU, 0x83U, 0xA0U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0CU, 0xC4U, 0x58U, - 0x20U, 0x0AU, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU }; - -// Recommended 1011 Hz test pattern for P25 Phase 1 (ANSI/TIA-102.CAAA) -// NAC: 0x293, srcID: 1, dstID: TG1 -const uint8_t LDU1_1K[] = { - 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x35, 0x54, 0x7B, 0xCB, 0x19, 0x4D, 0x0D, 0xCE, 0x24, 0xA1, 0x24, - 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4, 0xE2, 0x4A, 0x10, - 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, - 0x89, 0xD8, 0x39, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, - 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x70, 0xE2, 0x4A, 0x12, 0x40, - 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x6D, 0xE5, 0xD5, 0x48, - 0xAD, 0xE3, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, - 0xD8, 0x3B, 0xA1, 0x41, 0xC2, 0xD2, 0xBA, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, - 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x94, 0xC8, 0xFB, 0x02, 0x35, 0xA4, 0xE2, 0x4A, 0x12, 0x43, 0x50, - 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, 0x00, 0x0C, - 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4 }; - -const uint8_t LDU2_1K[] = { - 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x3A, 0xB8, 0xA4, 0xEF, 0xB0, 0x9A, 0x8A, 0xCE, 0x24, 0xA1, 0x24, - 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC, 0xE2, 0x4A, 0x10, - 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, - 0x89, 0xD8, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, - 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE2, 0x4A, 0x12, 0x40, - 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xE0, 0xE0, 0x00, 0x00, - 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, - 0xD8, 0x39, 0xAE, 0x8B, 0x48, 0xB6, 0x49, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, - 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB9, 0xA8, 0xF4, 0xF1, 0xFD, 0x60, 0xE2, 0x4A, 0x12, 0x43, 0x50, - 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, 0x00, 0x0C, - 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC }; - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -133,41 +45,8 @@ const uint8_t LDU2_1K[] = { /// Initializes a new instance of the HostCal class. /// /// Full-path to the configuration file. -HostCal::HostCal(const std::string& confFile) : - m_confFile(confFile), - m_conf(), - m_modem(nullptr), - m_queue(4096U, "Frame Buffer"), - m_console(), - m_fec(), - m_transmit(false), - m_duplex(true), - m_dmrEnabled(false), - m_dmrRx1K(false), - m_p25Enabled(false), - m_p25Rx1K(false), - m_p25TduTest(false), - m_nxdnEnabled(false), - m_isHotspot(false), - m_debug(false), - m_mode(STATE_DMR_CAL_1K), - m_modeStr(DMR_CAL_1K_STR), - m_rxFrequency(0U), - m_rxAdjustedFreq(0U), - m_txFrequency(0U), - m_txAdjustedFreq(0U), - m_channelId(0U), - m_channelNo(0U), - m_idenTable(nullptr), - m_berFrames(0U), - m_berBits(0U), - m_berErrs(0U), - m_berUndecodableLC(0U), - m_berUncorrectable(0U), - m_timeout(300U), - m_timer(0U), - m_updateConfigFromModem(false), - m_hasFetchedStatus(false) +HostCal::HostCal(const std::string& confFile) : HostSetup(confFile), + m_console() { /* stub */ } @@ -177,18 +56,26 @@ HostCal::HostCal(const std::string& confFile) : /// HostCal::~HostCal() { - delete m_modem; + /* stub */ } /// /// Executes the calibration processing loop. /// +/// +/// /// Zero if successful, otherwise error occurred. -int HostCal::run() +int HostCal::run(int argc, char **argv) { - bool ret = yaml::Parse(m_conf, m_confFile.c_str()); - if (!ret) { - ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); + bool ret = false; + try { + ret = yaml::Parse(m_conf, m_confFile.c_str()); + if (!ret) { + ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); + } + } + catch (yaml::OperationException const& e) { + ::fatal("cannot read the configuration file - %s (%s)", m_confFile.c_str(), e.message()); } // initialize system logging @@ -232,207 +119,15 @@ int HostCal::run() m_channelId = 15U; } - IdenTable entry = m_idenTable->find(m_channelId); - if (entry.baseFrequency() == 0U) { - ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); + if (!calculateRxTxFreq()) { return false; } - m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); - if (m_channelNo == 0U) { // clamp to 1 - m_channelNo = 1U; - } - if (m_channelNo > 4095U) { // clamp to 4095 - m_channelNo = 4095U; - } - - if (m_duplex) { - if (entry.txOffsetMhz() == 0U) { - ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId); - return false; - } - - uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); - float calcTxOffset = entry.txOffsetMhz() * 1000000; - - m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + calcTxOffset); - m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); - } - else { - uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); - - m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); - m_txFrequency = m_rxFrequency; - } - - LogInfo("System Config Parameters"); - LogInfo(" RX Frequency: %uHz", m_rxFrequency); - LogInfo(" TX Frequency: %uHz", m_txFrequency); - LogInfo(" Base Frequency: %uHz", entry.baseFrequency()); - LogInfo(" TX Offset: %fMHz", entry.txOffsetMhz()); - - yaml::Node modemConf = systemConf["modem"]; - - bool rxInvert = modemConf["rxInvert"].as(false); - bool txInvert = modemConf["txInvert"].as(false); - bool pttInvert = modemConf["pttInvert"].as(false); - bool dcBlocker = modemConf["dcBlocker"].as(true); - - int rxDCOffset = modemConf["rxDCOffset"].as(0); - int txDCOffset = modemConf["txDCOffset"].as(0); - float rxLevel = modemConf["rxLevel"].as(50.0F); - float txLevel = modemConf["txLevel"].as(50.0F); - - yaml::Node hotspotParams = modemConf["hotspot"]; - - int dmrDiscBWAdj = hotspotParams["dmrDiscBWAdj"].as(0); - int p25DiscBWAdj = hotspotParams["p25DiscBWAdj"].as(0); - int nxdnDiscBWAdj = hotspotParams["nxdnDiscBWAdj"].as(0); - int dmrPostBWAdj = hotspotParams["dmrPostBWAdj"].as(0); - int p25PostBWAdj = hotspotParams["p25PostBWAdj"].as(0); - int nxdnPostBWAdj = hotspotParams["nxdnPostBWAdj"].as(0); - - ADF_GAIN_MODE adfGainMode = (ADF_GAIN_MODE)hotspotParams["adfGainMode"].as(0U); - - bool afcEnable = hotspotParams["afcEnable"].as(false); - uint8_t afcKI = (uint8_t)hotspotParams["afcKI"].as(11U); - uint8_t afcKP = (uint8_t)hotspotParams["afcKP"].as(4U); - uint8_t afcRange = (uint8_t)hotspotParams["afcRange"].as(1U); - - int rxTuning = hotspotParams["rxTuning"].as(0); - int txTuning = hotspotParams["txTuning"].as(0); - - // apply the frequency tuning offsets - m_rxAdjustedFreq = m_rxFrequency + rxTuning; - m_txAdjustedFreq = m_txFrequency + txTuning; - - yaml::Node repeaterParams = modemConf["repeater"]; - - int dmrSymLevel3Adj = repeaterParams["dmrSymLvl3Adj"].as(0); - int dmrSymLevel1Adj = repeaterParams["dmrSymLvl1Adj"].as(0); - int p25SymLevel3Adj = repeaterParams["p25SymLvl3Adj"].as(0); - int p25SymLevel1Adj = repeaterParams["p25SymLvl1Adj"].as(0); - int nxdnSymLevel3Adj = repeaterParams["nxdnSymLvl3Adj"].as(0); - int nxdnSymLevel1Adj = repeaterParams["nxdnSymLvl1Adj"].as(0); - - yaml::Node softpotParams = modemConf["softpot"]; - - uint8_t rxCoarsePot = (uint8_t)softpotParams["rxCoarse"].as(127U); - uint8_t rxFinePot = (uint8_t)softpotParams["rxFine"].as(127U); - uint8_t txCoarsePot = (uint8_t)softpotParams["txCoarse"].as(127U); - uint8_t txFinePot = (uint8_t)softpotParams["txFine"].as(127U); - uint8_t rssiCoarsePot = (uint8_t)softpotParams["rssiCoarse"].as(127U); - uint8_t rssiFinePot = (uint8_t)softpotParams["rssiFine"].as(127U); - - uint8_t fdmaPreamble = (uint8_t)modemConf["fdmaPreamble"].as(80U); - uint8_t dmrRxDelay = (uint8_t)modemConf["dmrRxDelay"].as(7U); - uint8_t p25CorrCount = (uint8_t)modemConf["p25CorrCount"].as(5U); - - bool ignoreModemConfigArea = modemConf["ignoreModemConfigArea"].as(false); - - yaml::Node modemProtocol = modemConf["protocol"]; - std::string portType = modemProtocol["type"].as("null"); - - yaml::Node uartProtocol = modemProtocol["uart"]; - std::string uartPort = uartProtocol["port"].as(); - uint32_t uartSpeed = uartProtocol["speed"].as(115200); - - port::IModemPort* modemPort = nullptr; - std::transform(portType.begin(), portType.end(), portType.begin(), ::tolower); - if (portType == NULL_PORT) { - ::LogError(LOG_HOST, "Calibration mode is unsupported with the null modem!"); - return 2; - } - else if (portType == UART_PORT) { - port::SERIAL_SPEED serialSpeed = port::SERIAL_115200; - switch (uartSpeed) { - case 1200: - serialSpeed = port::SERIAL_1200; - break; - case 2400: - serialSpeed = port::SERIAL_2400; - break; - case 4800: - serialSpeed = port::SERIAL_4800; - break; - case 9600: - serialSpeed = port::SERIAL_9600; - break; - case 19200: - serialSpeed = port::SERIAL_19200; - break; - case 38400: - serialSpeed = port::SERIAL_38400; - break; - case 76800: - serialSpeed = port::SERIAL_76800; - break; - case 230400: - serialSpeed = port::SERIAL_230400; - break; - case 460800: - serialSpeed = port::SERIAL_460800; - break; - default: - LogWarning(LOG_HOST, "Unsupported serial speed %u, defaulting to %u", uartSpeed, port::SERIAL_115200); - uartSpeed = 115200; - case 115200: - break; - } - - modemPort = new port::UARTPort(uartPort, serialSpeed, true); - LogInfo("Modem Parameters"); - LogInfo(" UART Port: %s", uartPort.c_str()); - LogInfo(" UART Speed: %u", uartSpeed); - LogInfo(" RX Invert: %s", rxInvert ? "yes" : "no"); - LogInfo(" TX Invert: %s", txInvert ? "yes" : "no"); - LogInfo(" PTT Invert: %s", pttInvert ? "yes" : "no"); - LogInfo(" DC Blocker: %s", dcBlocker ? "yes" : "no"); - LogInfo(" FDMA Preambles: %u (%.1fms)", fdmaPreamble, float(fdmaPreamble) * 0.2083F); - LogInfo(" DMR RX Delay: %u (%.1fms)", dmrRxDelay, float(dmrRxDelay) * 0.0416666F); - LogInfo(" P25 Corr. Count: %u (%.1fms)", p25CorrCount, float(p25CorrCount) * 0.667F); - LogInfo(" RX DC Offset: %d", rxDCOffset); - LogInfo(" TX DC Offset: %d", txDCOffset); - LogInfo(" RX Tuning Offset: %dhz", rxTuning); - LogInfo(" TX Tuning Offset: %dhz", txTuning); - LogInfo(" RX Effective Frequency: %uhz", m_rxAdjustedFreq); - LogInfo(" TX Effective Frequency: %uhz", m_txAdjustedFreq); - LogInfo(" RX Coarse: %u, Fine: %u", rxCoarsePot, rxFinePot); - LogInfo(" TX Coarse: %u, Fine: %u", txCoarsePot, txFinePot); - LogInfo(" RSSI Coarse: %u, Fine: %u", rssiCoarsePot, rssiFinePot); - LogInfo(" RX Level: %.1f%%", rxLevel); - LogInfo(" TX Level: %.1f%%", txLevel); - - if (ignoreModemConfigArea) { - LogInfo(" Ignore Modem Configuration Area: yes"); - } - } - else if (g_remoteModemMode) { - ::LogError(LOG_HOST, "Calibration mode is unsupported with a remote modem!"); - return 2; - } - - if (modemPort == nullptr) { - ::LogError(LOG_HOST, "Invalid modem port type, %s!", portType.c_str()); - return 2; + // initialize modem + if (!createModem()) { + return false; } - p25::lc::TSBK::setVerbose(true); - p25::lc::TSBK::setWarnCRC(true); - p25::lc::tsbk::TSBKFactory::setWarnCRC(true); - - m_modem = new Modem(modemPort, false, rxInvert, txInvert, pttInvert, dcBlocker, false, fdmaPreamble, dmrRxDelay, p25CorrCount, 3960U, 2592U, 1488U, false, ignoreModemConfigArea, false, false, false); - m_modem->setLevels(rxLevel, txLevel, txLevel, txLevel, txLevel); - m_modem->setSymbolAdjust(dmrSymLevel3Adj, dmrSymLevel1Adj, p25SymLevel3Adj, p25SymLevel1Adj, nxdnSymLevel3Adj, nxdnSymLevel1Adj); - m_modem->setDCOffsetParams(txDCOffset, rxDCOffset); - m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, 100U, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, p25PostBWAdj, nxdnPostBWAdj, adfGainMode, - afcEnable, afcKI, afcKP, afcRange); - m_modem->setSoftPot(rxCoarsePot, rxFinePot, txCoarsePot, txFinePot, rssiCoarsePot, rssiFinePot); - - m_modem->setOpenHandler(MODEM_OC_PORT_HANDLER_BIND(HostCal::portModemOpen, this)); - m_modem->setCloseHandler(MODEM_OC_PORT_HANDLER_BIND(HostCal::portModemClose, this)); - m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(HostCal::portModemHandler, this)); - // open modem and initialize ret = m_modem->open(); if (!ret) { @@ -448,6 +143,7 @@ int HostCal::run() readFlash(); + writeFifoLength(); writeConfig(); writeRFParams(); @@ -467,6 +163,7 @@ int HostCal::run() if (!m_hasFetchedStatus) { ::LogError(LOG_CAL, "Failed to get status from modem"); + m_isConnected = false; m_modem->close(); m_console.close(); return 2; @@ -565,65 +262,6 @@ int HostCal::run() } break; - case 'X': - { - char value[5] = { '\0' }; - ::fprintf(stdout, "> FDMA Preambles [%u] ? ", m_modem->m_fdmaPreamble); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - // bryanb: appease the compiler... - uint32_t fdmaPreamble = m_modem->m_fdmaPreamble; - sscanf(value, "%u", &fdmaPreamble); - - m_modem->m_fdmaPreamble = (uint8_t)fdmaPreamble; - - writeConfig(); - } - } - break; - - case 'W': - { - char value[5] = { '\0' }; - ::fprintf(stdout, "> DMR Rx Delay [%u] ? ", m_modem->m_dmrRxDelay); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - // bryanb: appease the compiler... - uint32_t dmrRxDelay = m_modem->m_dmrRxDelay; - sscanf(value, "%u", &dmrRxDelay); - - m_modem->m_dmrRxDelay = (uint8_t)dmrRxDelay; - - writeConfig(); - } - } - break; - - case 'w': - { - if (!m_isHotspot) { - char value[5] = { '\0' }; - ::fprintf(stdout, "> P25 Correlation Count [%u] ? ", m_modem->m_p25CorrCount); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - // bryanb: appease the compiler... - uint32_t p25CorrCount = m_modem->m_p25CorrCount; - sscanf(value, "%u", &p25CorrCount); - - m_modem->m_p25CorrCount = (uint8_t)p25CorrCount; - - writeConfig(); - } - } - } - break; - case 'F': { char value[10] = { '\0' }; @@ -671,84 +309,15 @@ int HostCal::run() case 'e': readFlash(); break; - case '-': - if (!m_isHotspot) - setDMRSymLevel3Adj(-1); - break; - case '=': - if (!m_isHotspot) - setDMRSymLevel3Adj(1); - break; - case '_': - if (!m_isHotspot) - setDMRSymLevel1Adj(-1); - break; - case '+': - if (!m_isHotspot) - setDMRSymLevel1Adj(1); - break; - - case '[': - if (!m_isHotspot) - setP25SymLevel3Adj(-1); - break; - case ']': - if (!m_isHotspot) - setP25SymLevel3Adj(1); - break; - case '{': - if (!m_isHotspot) - setP25SymLevel1Adj(-1); - break; - case '}': - if (!m_isHotspot) - setP25SymLevel1Adj(1); - break; - - case ';': - if (!m_isHotspot && m_modem->getVersion() >= 3U) - setNXDNSymLevel3Adj(-1); - else { - LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); - } - break; - case '\'': - if (!m_isHotspot && m_modem->getVersion() >= 3U) - setNXDNSymLevel3Adj(1); - else { - LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); - } - break; - case ':': - if (!m_isHotspot && m_modem->getVersion() >= 3U) - setNXDNSymLevel1Adj(-1); - else { - LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); - } - break; - case '"': - if (!m_isHotspot && m_modem->getVersion() >= 3U) - setNXDNSymLevel1Adj(1); - else { - LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); - } - break; case '1': { - if (m_isHotspot) { - char value[5] = { '\0' }; - ::fprintf(stdout, "> DMR Discriminator BW Offset [%d] ? ", m_modem->m_dmrDiscBWAdj); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - int bwAdj = m_modem->m_dmrDiscBWAdj; - sscanf(value, "%d", &bwAdj); - - m_modem->m_dmrDiscBWAdj = bwAdj; - - writeRFParams(); + if (!m_isHotspot) { + if (m_modem->getVersion() >= 3U) { + setRXCoarseLevel(1); + } + else { + LogWarning(LOG_CAL, "Rx Coarse Level adjustment is not supported on your firmware!"); } } } @@ -756,19 +325,12 @@ int HostCal::run() case '2': { - if (m_isHotspot) { - char value[5] = { '\0' }; - ::fprintf(stdout, "> P25 Discriminator BW Offset [%d] ? ", m_modem->m_p25DiscBWAdj); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - int bwAdj = m_modem->m_p25DiscBWAdj; - sscanf(value, "%d", &bwAdj); - - m_modem->m_p25DiscBWAdj = bwAdj; - - writeRFParams(); + if (!m_isHotspot) { + if (m_modem->getVersion() >= 3U) { + setRXCoarseLevel(-1); + } + else { + LogWarning(LOG_CAL, "Rx Coarse Level adjustment is not supported on your firmware!"); } } } @@ -776,42 +338,25 @@ int HostCal::run() case '3': { - if (m_isHotspot && m_modem->getVersion() >= 3U) { - char value[5] = { '\0' }; - ::fprintf(stdout, "> NXDN Discriminator BW Offset [%d] ? ", m_modem->m_nxdnDiscBWAdj); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - int bwAdj = m_modem->m_nxdnDiscBWAdj; - sscanf(value, "%d", &bwAdj); - - m_modem->m_nxdnDiscBWAdj = bwAdj; - - writeRFParams(); + if (!m_isHotspot) { + if (m_modem->getVersion() >= 3U) { + setRXFineLevel(1); + } + else { + LogWarning(LOG_CAL, "Rx Fine Level adjustment is not supported on your firmware!"); } - } - else { - LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); } } break; case '4': { - if (m_isHotspot) { - char value[5] = { '\0' }; - ::fprintf(stdout, "> DMR Post Demodulation BW Offset [%d] ? ", m_modem->m_dmrPostBWAdj); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - int bwAdj = m_modem->m_dmrPostBWAdj; - sscanf(value, "%d", &bwAdj); - - m_modem->m_dmrPostBWAdj = bwAdj; - - writeRFParams(); + if (!m_isHotspot) { + if (m_modem->getVersion() >= 3U) { + setRXFineLevel(-1); + } + else { + LogWarning(LOG_CAL, "Rx Fine Level adjustment is not supported on your firmware!"); } } } @@ -819,19 +364,12 @@ int HostCal::run() case '5': { - if (m_isHotspot) { - char value[5] = { '\0' }; - ::fprintf(stdout, "> P25 Post Demodulation BW Offset [%d] ? ", m_modem->m_p25PostBWAdj); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - int bwAdj = m_modem->m_p25PostBWAdj; - sscanf(value, "%d", &bwAdj); - - m_modem->m_p25PostBWAdj = bwAdj; - - writeRFParams(); + if (!m_isHotspot) { + if (m_modem->getVersion() >= 3U) { + setTXCoarseLevel(1); + } + else { + LogWarning(LOG_CAL, "Tx Coarse Level adjustment is not supported on your firmware!"); } } } @@ -839,112 +377,39 @@ int HostCal::run() case '6': { - if (m_isHotspot && m_modem->getVersion() >= 3U) { - char value[5] = { '\0' }; - ::fprintf(stdout, "> NXDN Post Demodulation BW Offset [%d] ? ", m_modem->m_nxdnPostBWAdj); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - int bwAdj = m_modem->m_nxdnPostBWAdj; - sscanf(value, "%d", &bwAdj); - - m_modem->m_nxdnPostBWAdj = bwAdj; - - writeRFParams(); + if (!m_isHotspot) { + if (m_modem->getVersion() >= 3U) { + setTXCoarseLevel(-1); + } + else { + LogWarning(LOG_CAL, "Tx Coarse Level adjustment is not supported on your firmware!"); } - } - else { - LogWarning(LOG_CAL, "NXDN is not supported on your firmware!", NXDN_FEC_STR); } } break; - case '7': + case '9': { - if (m_isHotspot) { - char value[2] = { '\0' }; - ::fprintf(stdout, "> ADF7021 Gain Mode (0 - Auto, 1 - Auto High Lin., 2 - Low, 3 - High) [%u] ? ", m_modem->m_adfGainMode); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (value[0] != '\0') { - uint32_t gainMode = (uint32_t)m_modem->m_adfGainMode; - sscanf(value, "%u", &gainMode); - - if (gainMode >= 0U && gainMode < 4U) - { - m_modem->m_adfGainMode = (ADF_GAIN_MODE)gainMode; - writeRFParams(); - } - else - { - m_modem->m_adfGainMode = ADF_GAIN_AUTO; - writeRFParams(); - } + if (!m_isHotspot) { + if (m_modem->getVersion() >= 3U) { + setRSSICoarseLevel(1); + } + else { + LogWarning(LOG_CAL, "RSSI Coarse Level adjustment is not supported on your firmware!"); } } } break; - case '8': + case '0': { - if (m_isHotspot && m_modem->getVersion() >= 3U) { - char value[5] = { '\0' }; - ::fprintf(stdout, "> ADF7021 AFC Enabled [%u] (Y/N) ? ", m_modem->m_afcEnable); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { - m_modem->m_afcEnable = value[0] == 'Y' ? true : false; - } - - ::fprintf(stdout, "> AFC Range [%u] ? ", m_modem->m_afcRange); - ::fflush(stdout); - - m_console.getLine(value, 4, 0); - if (value[0] != '\0') { - uint32_t afcRange = m_modem->m_afcRange; - sscanf(value, "%u", &afcRange); - - if (afcRange >= 0U && afcRange < 256U) - m_modem->m_afcRange = (uint8_t)afcRange; - else - m_modem->m_afcRange = 4U; - } - - ::fprintf(stdout, "> AFC KI Parameter [%u] ? ", m_modem->m_afcKI); - ::fflush(stdout); - - m_console.getLine(value, 3, 0); - if (value[0] != '\0') { - uint32_t afcKI = m_modem->m_afcKI; - sscanf(value, "%u", &afcKI); - - if (afcKI >= 0U && afcKI < 16U) - m_modem->m_afcKI = (uint8_t)afcKI; - else - m_modem->m_afcKI = 11U; + if (!m_isHotspot) { + if (m_modem->getVersion() >= 3U) { + setRSSICoarseLevel(-1); } - - ::fprintf(stdout, "> AFC KP Parameter [%u] ? ", m_modem->m_afcKP); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (value[0] != '\0') { - uint32_t afcKP = m_modem->m_afcKP; - sscanf(value, "%u", &afcKP); - - if (afcKP >= 0U && afcKP < 8U) - m_modem->m_afcKP = (uint8_t)afcKP; - else - m_modem->m_afcKP = 1U; + else { + LogWarning(LOG_CAL, "RSSI Coarse Level adjustment is not supported on your firmware!"); } - - writeRFParams(); - } - else { - LogWarning(LOG_CAL, "ADF7021 AFC alignment is not supported on your firmware!"); } } break; @@ -998,22 +463,6 @@ int HostCal::run() writeConfig(); } break; - case 'l': - { - m_mode = STATE_P25; - m_modeStr = P25_TDU_TEST_STR; - m_duplex = true; - m_dmrEnabled = false; - m_p25Enabled = true; - m_p25TduTest = true; - m_nxdnEnabled = false; - - m_queue.clear(); - - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); - writeConfig(); - } - break; case 'M': { m_mode = STATE_DMR_CAL_1K; @@ -1086,7 +535,7 @@ int HostCal::run() break; case 'B': case 'J': - case '0': + case ')': { m_mode = STATE_DMR; if (c == 'J') { @@ -1097,7 +546,7 @@ int HostCal::run() m_modeStr = DMR_FEC_STR; m_dmrRx1K = false; } - if (c == '0') { + if (c == ')') { m_duplex = true; } else { m_duplex = false; @@ -1190,20 +639,8 @@ int HostCal::run() break; case 'S': case 's': - { - m_mode = STATE_IDLE; - writeConfig(); - if (m_isHotspot) { - writeRFParams(); - } - writeSymbolAdjust(); - yaml::Serialize(m_conf, m_confFile.c_str(), yaml::SerializeConfig(4, 64, false, false)); - LogMessage(LOG_CAL, " - Saved configuration to %s", m_confFile.c_str()); - if (writeFlash()) { - LogMessage(LOG_CAL, " - Wrote configuration area on modem"); - } - } - break; + saveConfig(); + break; case 'U': m_updateConfigFromModem = true; readFlash(); @@ -1224,26 +661,6 @@ int HostCal::run() break; } - if (m_p25TduTest && m_queue.hasSpace(p25::P25_TDU_FRAME_LENGTH_BYTES + 2U)) { - uint8_t data[p25::P25_TDU_FRAME_LENGTH_BYTES + 2U]; - ::memset(data + 2U, 0x00U, p25::P25_TDU_FRAME_LENGTH_BYTES); - - // Generate Sync - p25::Sync::addP25Sync(data + 2U); - - // Generate NID - std::unique_ptr nid = new_unique(p25::NID, 1U); - nid->encode(data + 2U, p25::P25_DUID_TDU); - - // Add busy bits - p25::P25Utils::addBusyBits(data + 2U, p25::P25_TDU_FRAME_LENGTH_BITS, true, true); - - data[0U] = modem::TAG_EOT; - data[1U] = 0x00U; - - addFrame(data, p25::P25_TDU_FRAME_LENGTH_BYTES + 2U, p25::P25_LDU_FRAME_LENGTH_BYTES); - } - // ------------------------------------------------------ // -- Modem Clocking -- // ------------------------------------------------------ @@ -1262,6 +679,7 @@ int HostCal::run() if (m_transmit) setTransmit(); + m_isConnected = false; m_modem->close(); m_console.close(); return 0; @@ -1272,295 +690,27 @@ int HostCal::run() // --------------------------------------------------------------------------- /// -/// +/// Helper to print the calibration help to the console. /// -/// -bool HostCal::portModemOpen(Modem* modem) +void HostCal::displayHelp() { - sleep(2000U); - - bool ret = writeRFParams(); - if (!ret) { - ret = writeRFParams(); - if (!ret) { - LogError(LOG_MODEM, "Modem unresponsive to RF parameters set after 2 attempts. Stopping."); - m_modem->close(); - return false; - } + LogMessage(LOG_CAL, "General Commands:"); + LogMessage(LOG_CAL, " Toggle transmit"); + LogMessage(LOG_CAL, " ` Display current settings and operation mode"); + LogMessage(LOG_CAL, " V Display version of host"); + LogMessage(LOG_CAL, " v Display version of firmware"); + LogMessage(LOG_CAL, " H/h Display help"); + LogMessage(LOG_CAL, " S/s Save calibration settings to configuration file"); + if (!m_modem->m_flashDisabled) { + LogMessage(LOG_CAL, " U Read modem configuration area and reset local configuration"); } - - ret = writeConfig(); - if (!ret) { - ret = writeConfig(); - if (!ret) { - LogError(LOG_MODEM, "Modem unresponsive to configuration set after 2 attempts. Stopping."); - m_modem->close(); - return false; - } - } - - LogMessage(LOG_MODEM, "Modem Ready [Calibration Mode]"); - - // handled modem open - return true; -} - -/// -/// -/// -/// -bool HostCal::portModemClose(Modem* modem) -{ - // handled modem close - return true; -} - -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -bool HostCal::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspType, bool rspDblLen, const uint8_t* buffer, uint16_t len) -{ - switch (m_mode) { - case STATE_P25: - { - if (m_transmit) { - uint8_t dataLen = 0U; - m_queue.peek(&dataLen, 1U); - - if (!m_queue.isEmpty() && m_modem->m_p25Space >= dataLen) { - uint8_t data[p25::P25_LDU_FRAME_LENGTH_BYTES + 2U]; - - dataLen = 0U; - m_queue.getData(&dataLen, 1U); - m_queue.getData(data, dataLen); - - uint8_t buffer[250U]; - - buffer[0U] = DVM_FRAME_START; - buffer[1U] = dataLen + 2U; - buffer[2U] = CMD_P25_DATA; - - ::memcpy(buffer + 3U, data + 1U, dataLen - 1U); - - uint8_t len = dataLen + 2U; - - int ret = m_modem->write(buffer, len); - if (ret != int(len)) - LogError(LOG_MODEM, "Error writing P25 data"); - - m_modem->m_p25Space -= dataLen; - } - } - } - break; - - default: - break; - } - - if (rspType == RTM_OK && len > 0) { - switch (buffer[2U]) { - case CMD_CAL_DATA: - { - bool inverted = (buffer[3U] == 0x80U); - short high = buffer[4U] << 8 | buffer[5U]; - short low = buffer[6U] << 8 | buffer[7U]; - short diff = high - low; - short centre = (high + low) / 2; - LogMessage(LOG_CAL, "Levels: inverted: %s, max: %d, min: %d, diff: %d, centre: %d", inverted ? "yes" : "no", high, low, diff, centre); - } - break; - case CMD_RSSI_DATA: - { - uint16_t max = buffer[3U] << 8 | buffer[4U]; - uint16_t min = buffer[5U] << 8 | buffer[6U]; - uint16_t ave = buffer[7U] << 8 | buffer[8U]; - LogMessage(LOG_CAL, "RSSI: max: %u, min: %u, ave: %u", max, min, ave); - } - break; - - case CMD_DMR_DATA1: - case CMD_DMR_DATA2: - processDMRBER(buffer + 4U, buffer[3]); - break; - - case CMD_DMR_LOST1: - case CMD_DMR_LOST2: - { - LogMessage(LOG_CAL, "DMR Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); - - if (m_dmrEnabled) { - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - } - } - break; - - case CMD_P25_DATA: - processP25BER(buffer + 3U); - break; - - case CMD_P25_LOST: - { - LogMessage(LOG_CAL, "P25 Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); - - if (m_p25Enabled) { - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - } - } - break; - - case CMD_NXDN_DATA: - processNXDNBER(buffer + 3U); - break; - - case CMD_NXDN_LOST: - { - LogMessage(LOG_CAL, "NXDN Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); - - if (m_nxdnEnabled) { - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - } - } - break; - - case CMD_GET_STATUS: - { - m_isHotspot = (buffer[3U] & 0x01U) == 0x01U; - - uint8_t modemState = buffer[4U]; - - bool tx = (buffer[5U] & 0x01U) == 0x01U; - - bool adcOverflow = (buffer[5U] & 0x02U) == 0x02U; - bool rxOverflow = (buffer[5U] & 0x04U) == 0x04U; - bool txOverflow = (buffer[5U] & 0x08U) == 0x08U; - bool dacOverflow = (buffer[5U] & 0x20U) == 0x20U; - - // spaces from the modem are returned in "logical" frame count, not raw byte size - m_modem->m_dmrSpace1 = buffer[7U] * (dmr::DMR_FRAME_LENGTH_BYTES + 2U); - m_modem->m_dmrSpace2 = buffer[8U] * (dmr::DMR_FRAME_LENGTH_BYTES + 2U); - m_modem->m_p25Space = buffer[10U] * (p25::P25_LDU_FRAME_LENGTH_BYTES); - m_modem->m_nxdnSpace = buffer[11U] * (nxdn::NXDN_FRAME_LENGTH_BYTES); - - if (m_hasFetchedStatus && m_requestedStatus) { - LogMessage(LOG_CAL, " - Diagnostic Values [Modem State: %u, Transmitting: %d, ADC Overflow: %d, Rx Overflow: %d, Tx Overflow: %d, DAC Overflow: %d, HS: %u]", - modemState, tx, adcOverflow, rxOverflow, txOverflow, dacOverflow, m_isHotspot); - } - - m_hasFetchedStatus = true; - m_requestedStatus = false; - } - break; - - case CMD_FLSH_READ: - { - uint8_t len = buffer[1U]; - if (m_debug) { - Utils::dump(1U, "Modem Flash Contents", buffer + 3U, len - 3U); - } - if (len == 249U) { - bool ret = edac::CRC::checkCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); - if (!ret) { - LogWarning(LOG_CAL, "HostCal::portModemHandler(), clearing modem configuration area; first setup?"); - eraseFlash(); - } - else { - bool isErased = (buffer[DVM_CONF_AREA_LEN] & 0x80U) == 0x80U; - uint8_t confAreaVersion = buffer[DVM_CONF_AREA_LEN] & 0x7FU; - - if (!isErased) { - if (confAreaVersion != DVM_CONF_AREA_VER) { - LogError(LOG_MODEM, "HostCal::portModemHandler(), invalid version for configuration area, %02X != %02X", DVM_CONF_AREA_VER, confAreaVersion); - } - else { - processFlashConfig(buffer); - - // reset update config flag if its set - if (m_updateConfigFromModem) { - m_updateConfigFromModem = false; - } - } - } - else { - LogWarning(LOG_MODEM, "HostCal::portModemHandler(), modem configuration area was erased and does not contain active configuration!"); - } - } - } - else { - LogWarning(LOG_MODEM, "Incorrect length for configuration area! Ignoring."); - } - } - break; - - case CMD_GET_VERSION: - case CMD_ACK: - break; - - case CMD_NAK: - LogWarning(LOG_CAL, "NAK, command = 0x%02X, reason = %u", buffer[3U], buffer[4U]); - break; - - case CMD_DEBUG1: - case CMD_DEBUG2: - case CMD_DEBUG3: - case CMD_DEBUG4: - case CMD_DEBUG5: - case CMD_DEBUG_DUMP: - m_modem->printDebug(buffer, len); - break; - - default: - LogWarning(LOG_CAL, "Unknown message, type = %02X", buffer[2U]); - Utils::dump("Buffer dump", buffer, len); - break; - } - } - - // handled modem response - return true; -} - -/// -/// Helper to print the calibration help to the console. -/// -void HostCal::displayHelp() -{ - LogMessage(LOG_CAL, "General Commands:"); - LogMessage(LOG_CAL, " Toggle transmit"); - LogMessage(LOG_CAL, " ` Display current settings and operation mode"); - LogMessage(LOG_CAL, " V Display version of host"); - LogMessage(LOG_CAL, " v Display version of firmware"); - LogMessage(LOG_CAL, " H/h Display help"); - LogMessage(LOG_CAL, " S/s Save calibration settings to configuration file"); - if (!m_modem->m_flashDisabled) { - LogMessage(LOG_CAL, " U Read modem configuration area and reset local configuration"); - } - LogMessage(LOG_CAL, " Q/q Quit"); - LogMessage(LOG_CAL, "Level Adjustment Commands:"); - if (!m_isHotspot) { - LogMessage(LOG_CAL, " I Toggle transmit inversion"); - LogMessage(LOG_CAL, " i Toggle receive inversion"); - LogMessage(LOG_CAL, " p Toggle PTT inversion"); - LogMessage(LOG_CAL, " d Toggle DC blocker"); + LogMessage(LOG_CAL, " Q/q Quit"); + LogMessage(LOG_CAL, "Level Adjustment Commands:"); + if (!m_isHotspot) { + LogMessage(LOG_CAL, " I Toggle transmit inversion"); + LogMessage(LOG_CAL, " i Toggle receive inversion"); + LogMessage(LOG_CAL, " p Toggle PTT inversion"); + LogMessage(LOG_CAL, " d Toggle DC blocker"); } if (!m_isHotspot) { LogMessage(LOG_CAL, " R/r Increase/Decrease receive level"); @@ -1581,6 +731,12 @@ void HostCal::displayHelp() LogMessage(LOG_CAL, " F Set Rx Frequency Adjustment"); LogMessage(LOG_CAL, " f Set Tx Frequency Adjustment"); } + if (!m_isHotspot) { + LogMessage(LOG_CAL, " 1/2 Increase/Decrease receive coarse level"); + LogMessage(LOG_CAL, " 3/4 Increase/Decrease receive fine level"); + LogMessage(LOG_CAL, " 5/6 Increase/Decrease transmit coarse level"); + LogMessage(LOG_CAL, " 9/0 Increase/Decrease RSSI coarse level"); + } LogMessage(LOG_CAL, "Mode Commands:"); LogMessage(LOG_CAL, " Z %s", DMR_CAL_STR); LogMessage(LOG_CAL, " z %s", P25_CAL_STR); @@ -1588,7 +744,6 @@ void HostCal::displayHelp() LogMessage(LOG_CAL, " M %s", DMR_CAL_1K_STR); LogMessage(LOG_CAL, " m %s", DMR_DMO_CAL_1K_STR); LogMessage(LOG_CAL, " P %s", P25_CAL_1K_STR); - LogMessage(LOG_CAL, " l %s", P25_TDU_TEST_STR); LogMessage(LOG_CAL, " N %s", NXDN_CAL_1K_STR); LogMessage(LOG_CAL, " B %s", DMR_FEC_STR); LogMessage(LOG_CAL, " J %s", DMR_FEC_1K_STR); @@ -1596,29 +751,6 @@ void HostCal::displayHelp() LogMessage(LOG_CAL, " j %s", P25_FEC_1K_STR); LogMessage(LOG_CAL, " n %s", NXDN_FEC_STR); LogMessage(LOG_CAL, " x %s", RSSI_CAL_STR); - LogMessage(LOG_CAL, "Engineering Commands:"); - if (!m_isHotspot) { - LogMessage(LOG_CAL, " -/= Inc/Dec DMR +/- 3 Symbol Level"); - LogMessage(LOG_CAL, " _/+ Inc/Dec DMR +/- 1 Symbol Level"); - LogMessage(LOG_CAL, " [/] Inc/Dec P25 +/- 3 Symbol Level"); - LogMessage(LOG_CAL, " {/} Inc/Dec P25 +/- 1 Symbol Level"); - LogMessage(LOG_CAL, " ;/' Inc/Dec NXDN +/- 3 Symbol Level"); - LogMessage(LOG_CAL, " :/\" Inc/Dec NXDN +/- 1 Symbol Level"); - } - else { - LogMessage(LOG_CAL, " 1 Set DMR Disc. Bandwidth Offset"); - LogMessage(LOG_CAL, " 2 Set P25 Disc. Bandwidth Offset"); - LogMessage(LOG_CAL, " 3 Set NXDN Disc. Bandwidth Offset"); - LogMessage(LOG_CAL, " 4 Set DMR Post Demod Bandwidth Offset"); - LogMessage(LOG_CAL, " 5 Set P25 Post Demod Bandwidth Offset"); - LogMessage(LOG_CAL, " 6 Set NXDN Post Demod Bandwidth Offset"); - LogMessage(LOG_CAL, " 7 Set ADF7021 Rx Auto. Gain Mode"); - LogMessage(LOG_CAL, " 8 Set ADF7021 AFC Settings"); - } - if (!m_modem->m_flashDisabled) { - LogMessage(LOG_CAL, " E Erase modem configuration area"); - LogMessage(LOG_CAL, " e Read modem configuration area"); - } } /// @@ -1730,1273 +862,117 @@ bool HostCal::setRXDCOffset(int incr) } /// -/// Helper to change the DMR Symbol Level 3 adjust. +/// Helper to change the Rx coarse level. /// /// Amount to change. /// True, if setting was applied, otherwise false. -bool HostCal::setDMRSymLevel3Adj(int incr) +bool HostCal::setRXCoarseLevel(int incr) { - if (incr > 0 && m_modem->m_dmrSymLevel3Adj < 127) { - m_modem->m_dmrSymLevel3Adj++; - LogMessage(LOG_CAL, " - DMR Symbol Level +/- 3 Adjust: %d", m_modem->m_dmrSymLevel3Adj); - return writeSymbolAdjust(); - } - - if (incr < 0 && m_modem->m_dmrSymLevel3Adj > -127) { - m_modem->m_dmrSymLevel3Adj--; - LogMessage(LOG_CAL, " - DMR Symbol Level +/- 3 Adjust: %d", m_modem->m_dmrSymLevel3Adj); - return writeSymbolAdjust(); - } - - return true; -} - -/// -/// Helper to change the DMR Symbol Level 1 adjust. -/// -/// Amount to change. -/// True, if setting was applied, otherwise false. -bool HostCal::setDMRSymLevel1Adj(int incr) -{ - if (incr > 0 && m_modem->m_dmrSymLevel1Adj < 127) { - m_modem->m_dmrSymLevel1Adj++; - LogMessage(LOG_CAL, " - DMR Symbol Level +/- 1 Adjust: %d", m_modem->m_dmrSymLevel1Adj); - return writeSymbolAdjust(); - } + if (incr > 0 && m_modem->m_rxCoarsePot < 255U) { + if (m_modem->m_rxCoarsePot != 255U) { + m_modem->m_rxCoarsePot += 1U; + } - if (incr < 0 && m_modem->m_dmrSymLevel1Adj > -127) { - m_modem->m_dmrSymLevel1Adj--; - LogMessage(LOG_CAL, " - DMR Symbol Level +/- 1 Adjust: %d", m_modem->m_dmrSymLevel1Adj); - return writeSymbolAdjust(); + LogMessage(LOG_CAL, " - RX Coarse Level: %u", m_modem->m_rxCoarsePot); + return writeConfig(); } - return true; -} - -/// -/// Helper to change the P25 Symbol Level 3 adjust. -/// -/// Amount to change. -/// True, if setting was applied, otherwise false. -bool HostCal::setP25SymLevel3Adj(int incr) -{ - if (incr > 0 && m_modem->m_p25SymLevel3Adj < 127) { - m_modem->m_p25SymLevel3Adj++; - LogMessage(LOG_CAL, " - P25 Symbol Level +/- 3 Adjust: %d", m_modem->m_p25SymLevel3Adj); - return writeSymbolAdjust(); - } + if (incr < 0 && m_modem->m_rxCoarsePot > 0U) { + if (m_modem->m_rxCoarsePot != 0) { + m_modem->m_rxCoarsePot -= 1U; + } - if (incr < 0 && m_modem->m_p25SymLevel3Adj > -127) { - m_modem->m_p25SymLevel3Adj--; - LogMessage(LOG_CAL, " - P25 Symbol Level +/- 3 Adjust: %d", m_modem->m_p25SymLevel3Adj); - return writeSymbolAdjust(); + LogMessage(LOG_CAL, " - RX Coarse Level: %u", m_modem->m_rxCoarsePot); + return writeConfig(); } return true; } /// -/// Helper to change the P25 Symbol Level 1 adjust. +/// Helper to change the Rx fine level. /// /// Amount to change. /// True, if setting was applied, otherwise false. -bool HostCal::setP25SymLevel1Adj(int incr) +bool HostCal::setRXFineLevel(int incr) { - if (incr > 0 && m_modem->m_p25SymLevel1Adj < 127) { - m_modem->m_p25SymLevel1Adj++; - LogMessage(LOG_CAL, " - P25 Symbol Level +/- 1 Adjust: %d", m_modem->m_p25SymLevel1Adj); - return writeSymbolAdjust(); - } + if (incr > 0 && m_modem->m_rxFinePot < 255U) { + if (m_modem->m_rxFinePot != 255U) { + m_modem->m_rxFinePot += 1U; + } - if (incr < 0 && m_modem->m_p25SymLevel1Adj > -127) { - m_modem->m_p25SymLevel1Adj--; - LogMessage(LOG_CAL, " - P25 Symbol Level +/- 1 Adjust: %d", m_modem->m_p25SymLevel1Adj); - return writeSymbolAdjust(); + LogMessage(LOG_CAL, " - RX Fine Level: %u", m_modem->m_rxFinePot); + return writeConfig(); } - return true; -} - -/// -/// Helper to change the NXDN Symbol Level 3 adjust. -/// -/// Amount to change. -/// True, if setting was applied, otherwise false. -bool HostCal::setNXDNSymLevel3Adj(int incr) -{ - if (incr > 0 && m_modem->m_nxdnSymLevel3Adj < 127) { - m_modem->m_nxdnSymLevel3Adj++; - LogMessage(LOG_CAL, " - NXDN Symbol Level +/- 3 Adjust: %d", m_modem->m_nxdnSymLevel3Adj); - return writeSymbolAdjust(); - } + if (incr < 0 && m_modem->m_rxFinePot > 0U) { + if (m_modem->m_rxFinePot != 0) { + m_modem->m_rxFinePot -= 1U; + } - if (incr < 0 && m_modem->m_nxdnSymLevel3Adj > -127) { - m_modem->m_nxdnSymLevel3Adj--; - LogMessage(LOG_CAL, " - NXDN Symbol Level +/- 3 Adjust: %d", m_modem->m_nxdnSymLevel3Adj); - return writeSymbolAdjust(); + LogMessage(LOG_CAL, " - RX Fine Level: %u", m_modem->m_rxFinePot); + return writeConfig(); } return true; } /// -/// Helper to change the NXDN Symbol Level 1 adjust. +/// Helper to change the Tx coarse level. /// /// Amount to change. /// True, if setting was applied, otherwise false. -bool HostCal::setNXDNSymLevel1Adj(int incr) -{ - if (incr > 0 && m_modem->m_nxdnSymLevel1Adj < 127) { - m_modem->m_nxdnSymLevel1Adj++; - LogMessage(LOG_CAL, " - NXDN Symbol Level +/- 1 Adjust: %d", m_modem->m_nxdnSymLevel1Adj); - return writeSymbolAdjust(); - } - - if (incr < 0 && m_modem->m_nxdnSymLevel1Adj > -127) { - m_modem->m_nxdnSymLevel1Adj--; - LogMessage(LOG_CAL, " - NXDN Symbol Level +/- 1 Adjust: %d", m_modem->m_nxdnSymLevel1Adj); - return writeSymbolAdjust(); - } - - return true; -} - -/// -/// Helper to toggle modem transmit mode. -/// -/// True, if setting was applied, otherwise false. -bool HostCal::setTransmit() -{ - if (m_dmrEnabled || (m_p25Enabled && !m_p25TduTest) || m_nxdnEnabled) { - LogError(LOG_CAL, "No transmit allowed in a BER Test mode"); - return false; - } - - m_transmit = !m_transmit; - - if (m_p25Enabled && m_p25TduTest) { - if (m_transmit) - LogMessage(LOG_CAL, " - Modem start transmitting"); - else - LogMessage(LOG_CAL, " - Modem stop transmitting"); - - m_modem->clock(0U); - return true; - } - - uint8_t buffer[50U]; - - buffer[0U] = DVM_FRAME_START; - buffer[1U] = 4U; - buffer[2U] = CMD_CAL_DATA; - buffer[3U] = m_transmit ? 0x01U : 0x00U; - - int ret = m_modem->write(buffer, 4U); - if (ret <= 0) - return false; - - sleep(25U); - - if (m_transmit) - LogMessage(LOG_CAL, " - Modem start transmitting"); - else - LogMessage(LOG_CAL, " - Modem stop transmitting"); - - m_modem->clock(0U); - - return true; -} - -/// -/// Process DMR Rx BER. -/// -/// Buffer containing DMR data -/// DMR Audio Sequence -void HostCal::processDMRBER(const uint8_t* buffer, uint8_t seq) -{ - if (seq == 65U) { - timerStart(); - - LogMessage(LOG_CAL, "DMR voice header received"); - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - return; - } - else if (seq == 66U) { - if (m_berFrames != 0U) { - LogMessage(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); - } - - timerStop(); - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - return; - } - - timerStart(); - - uint32_t errs = m_fec.measureDMRBER(buffer); - - float ber = float(errs) / 1.41F; - if (ber < 10.0F) - LogMessage(LOG_CAL, "DMR audio seq. %d, FEC BER %% (errs): %.3f%% (%u/141)", seq & 0x0FU, ber, errs); - else { - LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); - m_berUncorrectable++; - } - - m_berBits += 141U; - m_berErrs += errs; - m_berFrames++; -} - -/// -/// Process DMR Rx 1011hz BER. -/// -/// Buffer containing DMR data -/// DMR Audio Sequence -void HostCal::processDMR1KBER(const uint8_t* buffer, uint8_t seq) -{ - uint32_t errs = 0U; - - if (seq == 65U) { - timerStart(); - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - - for (uint32_t i = 0U; i < 33U; i++) - errs += countErrs(buffer[i], VH_DMO1K[i]); - - m_berErrs += errs; - m_berBits += 264; - m_berFrames++; - LogMessage(LOG_CAL, "DMR voice header received, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", float(errs) / 2.64F, errs); - } - else if (seq == 66U) { - for (uint32_t i = 0U; i < 33U; i++) - errs += countErrs(buffer[i], VT_DMO1K[i]); - - m_berErrs += errs; - m_berBits += 264; - m_berFrames++; - - if (m_berFrames != 0U) { - LogMessage(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); - } - - timerStop(); - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - return; - } - - timerStart(); - - uint8_t dmrSeq = seq & 0x0FU; - if (dmrSeq > 5U) - dmrSeq = 5U; - - errs = 0U; - for (uint32_t i = 0U; i < 33U; i++) - errs += countErrs(buffer[i], VOICE_1K[i]); - - float ber = float(errs) / 2.64F; - - m_berErrs += errs; - m_berBits += 264; - m_berFrames++; - - if (ber < 10.0F) - LogMessage(LOG_CAL, "DMR audio seq. %d, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", seq & 0x0FU, ber, errs); - else { - LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); - m_berUncorrectable++; - } -} - -/// -/// Process P25 Rx BER. -/// -/// Buffer containing P25 data -void HostCal::processP25BER(const uint8_t* buffer) -{ - using namespace p25; - - uint8_t sync[P25_SYNC_LENGTH_BYTES]; - ::memcpy(sync, buffer + 1U, P25_SYNC_LENGTH_BYTES); - - uint8_t syncErrs = 0U; - for (uint8_t i = 0U; i < P25_SYNC_LENGTH_BYTES; i++) - syncErrs += Utils::countBits8(sync[i] ^ P25_SYNC_BYTES[i]); - - LogMessage(LOG_CAL, "P25, sync word, errs = %u, sync word = %02X %02X %02X %02X %02X %02X", syncErrs, - sync[0U], sync[1U], sync[2U], sync[3U], sync[4U], sync[5U]); - - uint8_t nid[P25_NID_LENGTH_BYTES]; - P25Utils::decode(buffer + 1U, nid, 48U, 114U); - uint8_t duid = nid[1U] & 0x0FU; - - uint32_t errs = 0U; - uint8_t imbe[18U]; - lc::LC lc = lc::LC(); - data::DataHeader dataHeader = data::DataHeader(); - - if (duid == P25_DUID_HDU) { - timerStart(); - - bool ret = lc.decodeHDU(buffer + 1U); - if (!ret) { - LogWarning(LOG_CAL, P25_HDU_STR ", undecodable LC"); - m_berUndecodableLC++; - } - else { - LogMessage(LOG_CAL, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); - } - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - } - else if (duid == P25_DUID_TDU) { - if (m_berFrames != 0U) { - LogMessage(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); - } - - timerStop(); - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - return; - } - else if (duid == P25_DUID_LDU1) { - timerStart(); - - bool ret = lc.decodeLDU1(buffer + 1U); - if (!ret) { - LogWarning(LOG_CAL, P25_LDU1_STR ", undecodable LC"); - m_berUndecodableLC++; - } - else { - LogMessage(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", - lc.getMFId(), lc.getLCO(), lc.getEmergency(), lc.getEncrypted(), lc.getPriority(), lc.getGroup(), lc.getSrcId(), lc.getDstId()); - } - - P25Utils::decode(buffer + 1U, imbe, 114U, 262U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 262U, 410U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 452U, 600U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 640U, 788U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 830U, 978U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 1020U, 1168U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 1208U, 1356U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 1398U, 1546U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 1578U, 1726U); - errs += m_fec.measureP25BER(imbe); - - float ber = float(errs) / 12.33F; - if (ber < 10.0F) - LogMessage(LOG_CAL, P25_LDU1_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); - else { - LogWarning(LOG_CAL, P25_LDU1_STR ", uncorrectable audio"); - m_berUncorrectable++; - } - - m_berBits += 1233U; - m_berErrs += errs; - m_berFrames++; - } - else if (duid == P25_DUID_LDU2) { - timerStart(); - - bool ret = lc.decodeLDU2(buffer + 1U); - if (!ret) { - LogWarning(LOG_CAL, P25_LDU2_STR ", undecodable LC"); - m_berUndecodableLC++; - } - else { - LogMessage(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", - lc.getMFId(), lc.getAlgId(), lc.getKId()); - } - - P25Utils::decode(buffer + 1U, imbe, 114U, 262U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 262U, 410U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 452U, 600U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 640U, 788U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 830U, 978U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 1020U, 1168U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 1208U, 1356U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 1398U, 1546U); - errs += m_fec.measureP25BER(imbe); - - P25Utils::decode(buffer + 1U, imbe, 1578U, 1726U); - errs += m_fec.measureP25BER(imbe); - - float ber = float(errs) / 12.33F; - if (ber < 10.0F) - LogMessage(LOG_CAL, P25_LDU2_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); - else { - LogWarning(LOG_CAL, P25_LDU2_STR ", uncorrectable audio"); - m_berUncorrectable++; - } - - m_berBits += 1233U; - m_berErrs += errs; - m_berFrames++; - } - else if (duid == P25_DUID_PDU) { - timerStop(); - - // note: for the calibrator we will only process the PDU header -- and not the PDU data - uint8_t pduBuffer[P25_LDU_FRAME_LENGTH_BYTES]; - uint32_t bits = P25Utils::decode(buffer + 1U, pduBuffer, 0, P25_LDU_FRAME_LENGTH_BITS); - - uint8_t* rfPDU = new uint8_t[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; - ::memset(rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); - uint32_t rfPDUBits = 0U; - - for (uint32_t i = 0U; i < bits; i++, rfPDUBits++) { - bool b = READ_BIT(buffer, i); - WRITE_BIT(rfPDU, rfPDUBits, b); - } - - bool ret = dataHeader.decode(rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES); - if (!ret) { - LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "Unfixable PDU Data", rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES, P25_PDU_HEADER_LENGTH_BYTES); - } - else { - LogMessage(LOG_CAL, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padCount = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u", - dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), - dataHeader.getBlocksToFollow(), dataHeader.getPadCount(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), - dataHeader.getHeaderOffset()); - } - - delete[] rfPDU; - } - else if (duid == P25_DUID_TSDU) { - timerStop(); - - std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(buffer + 1U); - - Utils::dump(1U, "Raw TSBK Dump", buffer + 1U, P25_TSDU_FRAME_LENGTH_BYTES); - - if (tsbk == nullptr) { - LogWarning(LOG_CAL, P25_TSDU_STR ", undecodable LC"); - m_berUndecodableLC++; - } - else { - LogMessage(LOG_CAL, P25_TSDU_STR ", mfId = $%02X, lco = $%02X, srcId = %u, dstId = %u, service = %u, netId = %u, sysId = %u", - tsbk->getMFId(), tsbk->getLCO(), tsbk->getSrcId(), tsbk->getDstId(), tsbk->getService(), tsbk->getNetId(), tsbk->getSysId()); - } - } -} - -/// -/// Process P25 Rx 1011hz BER. -/// -/// Buffer containing P25 data -void HostCal::processP251KBER(const uint8_t* buffer) +bool HostCal::setTXCoarseLevel(int incr) { - using namespace p25; - - uint8_t nid[P25_NID_LENGTH_BYTES]; - P25Utils::decode(buffer + 1U, nid, 48U, 114U); - uint8_t duid = nid[1U] & 0x0FU; - - uint32_t errs = 0U; - lc::LC lc = lc::LC(); - - if (duid == P25_DUID_HDU) { - timerStart(); - - bool ret = lc.decodeHDU(buffer + 1U); - if (!ret) { - LogWarning(LOG_CAL, P25_HDU_STR ", undecodable LC"); - m_berUndecodableLC++; - } - else { - LogMessage(LOG_RF, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); - } - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - } - else if (duid == P25_DUID_TDU) { - if (m_berFrames != 0U) { - LogMessage(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); - } - - timerStop(); - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - return; - } - else if (duid == P25_DUID_LDU1) { - timerStart(); - - bool ret = lc.decodeLDU1(buffer + 1U); - if (!ret) { - LogWarning(LOG_CAL, P25_LDU1_STR ", undecodable LC"); - m_berUndecodableLC++; - } - else { - LogMessage(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", - lc.getMFId(), lc.getLCO(), lc.getEmergency(), lc.getEncrypted(), lc.getPriority(), lc.getGroup(), lc.getSrcId(), lc.getDstId()); - } - - for (uint32_t i = 0U; i < 216U; i++) - errs += countErrs(buffer[i + 1U], LDU1_1K[i]); - - float ber = float(errs) / 12.33F; - if (ber < 10.0F) - LogMessage(LOG_CAL, P25_LDU1_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); - else { - LogWarning(LOG_CAL, P25_LDU1_STR ", uncorrectable audio"); - m_berUncorrectable++; + if (incr > 0 && m_modem->m_txCoarsePot < 255U) { + if (m_modem->m_txCoarsePot != 255U) { + m_modem->m_txCoarsePot += 1U; } - m_berBits += 1233U; - m_berErrs += errs; - m_berFrames++; + LogMessage(LOG_CAL, " - TX Coarse Level: %u", m_modem->m_txCoarsePot); + return writeConfig(); } - else if (duid == P25_DUID_LDU2) { - timerStart(); - - bool ret = lc.decodeLDU2(buffer + 1U); - if (!ret) { - LogWarning(LOG_CAL, P25_LDU2_STR ", undecodable LC"); - m_berUndecodableLC++; - } - else { - LogMessage(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", - lc.getMFId(), lc.getAlgId(), lc.getKId()); - } - for (uint32_t i = 0U; i < 216U; i++) - errs += countErrs(buffer[i + 1U], LDU2_1K[i]); - - float ber = float(errs) / 12.33F; - if (ber < 10.0F) - LogMessage(LOG_CAL, P25_LDU2_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); - else { - LogWarning(LOG_CAL, P25_LDU2_STR ", uncorrectable audio"); - m_berUncorrectable++; + if (incr < 0 && m_modem->m_txCoarsePot > 0U) { + if (m_modem->m_txCoarsePot != 0) { + m_modem->m_txCoarsePot -= 1U; } - m_berBits += 1233U; - m_berErrs += errs; - m_berFrames++; - } -} - -/// -/// Process NXDN Rx BER. -/// -/// Buffer containing NXDN data -void HostCal::processNXDNBER(const uint8_t* buffer) -{ - using namespace nxdn; - - unsigned char data[NXDN_FRAME_LENGTH_BYTES]; - ::memcpy(data, buffer, NXDN_FRAME_LENGTH_BYTES); - NXDNUtils::scrambler(data); - - channel::LICH lich; - bool valid = lich.decode(data); - - if (valid) { - uint8_t usc = lich.getFCT(); - uint8_t opt = lich.getOption(); - - if (usc == NXDN_LICH_USC_SACCH_NS) { - if (m_berFrames == 0U) { - LogMessage(LOG_CAL, "NXDN VCALL (Voice Call), 1031 Test Pattern Start"); - - timerStart(); - m_berErrs = 0U; - m_berBits = 0U; - m_berFrames = 0U; - return; - } else { - float ber = float(m_berErrs * 100U) / float(m_berBits); - LogMessage(LOG_CAL, "NXDN TX_REL (Transmission Release), 1031 Test Pattern BER, frames: %u, errs: %.3f%% (%u/%u)", m_berFrames, ber, m_berErrs, m_berBits); - - timerStop(); - m_berErrs = 0U; - m_berBits = 0U; - m_berFrames = 0U; - return; - } - } else if (opt == NXDN_LICH_STEAL_NONE) { - timerStart(); - - uint32_t errors = 0U; - - errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); - errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); - errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); - errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); - - m_berBits += 188U; - m_berErrs += errors; - m_berFrames++; - - float ber = float(errors) / 1.88F; - LogMessage(LOG_CAL, "NXDN VCALL (Voice Call), 1031 Test Pattern BER, (errs): %.3f%% (%u/188)", ber, errors); - } - } -} - -/// -/// Write configuration to the modem DSP. -/// -/// True, if configuration is written, otherwise false. -bool HostCal::writeConfig() -{ - return writeConfig(m_mode); -} - -/// -/// Write configuration to the modem DSP. -/// -/// -/// True, if configuration is written, otherwise false. -bool HostCal::writeConfig(uint8_t modeOverride) -{ - if (m_isHotspot && m_transmit) { - setTransmit(); - sleep(25U); - } - - uint8_t buffer[25U]; - ::memset(buffer, 0x00U, 25U); - uint8_t lengthToWrite = 17U; - - buffer[0U] = DVM_FRAME_START; - buffer[2U] = CMD_SET_CONFIG; - - buffer[3U] = 0x00U; - m_conf["system"]["modem"]["rxInvert"] = __BOOL_STR(m_modem->m_rxInvert); - if (m_modem->m_rxInvert) - buffer[3U] |= 0x01U; - m_conf["system"]["modem"]["txInvert"] = __BOOL_STR(m_modem->m_txInvert); - if (m_modem->m_txInvert) - buffer[3U] |= 0x02U; - m_conf["system"]["modem"]["pttInvert"] = __BOOL_STR(m_modem->m_pttInvert); - if (m_modem->m_pttInvert) - buffer[3U] |= 0x04U; - if (m_debug) - buffer[3U] |= 0x10U; - if (!m_duplex) - buffer[3U] |= 0x80U; - - buffer[4U] = 0x00U; - m_conf["system"]["modem"]["dcBlocker"] = __BOOL_STR(m_modem->m_dcBlocker); - if (m_modem->m_dcBlocker) - buffer[4U] |= 0x01U; - - if (m_dmrEnabled) - buffer[4U] |= 0x02U; - if (m_p25Enabled) - buffer[4U] |= 0x08U; - - if (m_modem->m_fdmaPreamble > MAX_FDMA_PREAMBLE) { - LogWarning(LOG_P25, "oversized FDMA preamble count, reducing to maximum %u", MAX_FDMA_PREAMBLE); - m_modem->m_fdmaPreamble = MAX_FDMA_PREAMBLE; - } - - m_conf["system"]["modem"]["fdmaPreamble"] = __INT_STR(m_modem->m_fdmaPreamble); - buffer[5U] = m_modem->m_fdmaPreamble; - - buffer[6U] = modeOverride; - - m_conf["system"]["modem"]["rxLevel"] = __FLOAT_STR(m_modem->m_rxLevel); - buffer[7U] = (uint8_t)(m_modem->m_rxLevel * 2.55F + 0.5F); - - m_conf["system"]["modem"]["txLevel"] = __FLOAT_STR(m_modem->m_cwIdTXLevel); - buffer[8U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); - - buffer[9U] = 1U; - - m_conf["system"]["modem"]["dmrRxDelay"] = __INT_STR(m_modem->m_dmrRxDelay); - buffer[10U] = m_modem->m_dmrRxDelay; - - uint32_t nac = 0xF7EU; - buffer[11U] = (nac >> 4) & 0xFFU; - buffer[12U] = (nac << 4) & 0xF0U; - - buffer[13U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); - buffer[15U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); - - m_conf["system"]["modem"]["txDCOffset"] = __INT_STR(m_modem->m_txDCOffset); - buffer[16U] = (uint8_t)(m_modem->m_txDCOffset + 128); - m_conf["system"]["modem"]["rxDCOffset"] = __INT_STR(m_modem->m_rxDCOffset); - buffer[17U] = (uint8_t)(m_modem->m_rxDCOffset + 128); - - m_conf["system"]["modem"]["p25CorrCount"] = __INT_STR(m_modem->m_p25CorrCount); - buffer[14U] = (uint8_t)m_modem->m_p25CorrCount; - - // are we on a protocol version 3 firmware? - if (m_modem->getVersion() >= 3U) { - lengthToWrite = 24U; - - if (m_nxdnEnabled) - buffer[4U] |= 0x10U; - - buffer[18U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); - - buffer[19U] = m_modem->m_rxCoarsePot; - buffer[20U] = m_modem->m_rxFinePot; - buffer[21U] = m_modem->m_txCoarsePot; - buffer[22U] = m_modem->m_txFinePot; - buffer[23U] = m_modem->m_rssiCoarsePot; - buffer[24U] = m_modem->m_rssiFinePot; - } - - buffer[1U] = lengthToWrite; - - int ret = m_modem->write(buffer, lengthToWrite); - if (ret != lengthToWrite) - return false; - - sleep(10U); - - m_modem->clock(0U); - return true; -} - -/// -/// Write RF parameters to the air interface modem. -/// -/// -bool HostCal::writeRFParams() -{ - uint8_t buffer[22U]; - ::memset(buffer, 0x00U, 22U); - uint8_t lengthToWrite = 18U; - - buffer[0U] = DVM_FRAME_START; - buffer[2U] = CMD_SET_RFPARAMS; - - buffer[3U] = 0x00U; - - buffer[4U] = (m_rxAdjustedFreq >> 0) & 0xFFU; - buffer[5U] = (m_rxAdjustedFreq >> 8) & 0xFFU; - buffer[6U] = (m_rxAdjustedFreq >> 16) & 0xFFU; - buffer[7U] = (m_rxAdjustedFreq >> 24) & 0xFFU; - - buffer[8U] = (m_txAdjustedFreq >> 0) & 0xFFU; - buffer[9U] = (m_txAdjustedFreq >> 8) & 0xFFU; - buffer[10U] = (m_txAdjustedFreq >> 16) & 0xFFU; - buffer[11U] = (m_txAdjustedFreq >> 24) & 0xFFU; - - buffer[12U] = (unsigned char)(100 * 2.55F + 0.5F); // cal sets power fixed to 100 - - m_conf["system"]["modem"]["hotspot"]["dmrDiscBWAdj"] = __INT_STR(m_modem->m_dmrDiscBWAdj); - buffer[13U] = (uint8_t)(m_modem->m_dmrDiscBWAdj + 128); - m_conf["system"]["modem"]["hotspot"]["p25DiscBWAdj"] = __INT_STR(m_modem->m_p25DiscBWAdj); - buffer[14U] = (uint8_t)(m_modem->m_p25DiscBWAdj + 128); - m_conf["system"]["modem"]["hotspot"]["dmrPostBWAdj"] = __INT_STR(m_modem->m_dmrPostBWAdj); - buffer[15U] = (uint8_t)(m_modem->m_dmrPostBWAdj + 128); - m_conf["system"]["modem"]["hotspot"]["p25PostBWAdj"] = __INT_STR(m_modem->m_p25PostBWAdj); - buffer[16U] = (uint8_t)(m_modem->m_p25PostBWAdj + 128); - - m_conf["system"]["modem"]["hotspot"]["adfGainMode"] = __INT_STR((int)m_modem->m_adfGainMode); - buffer[17U] = (uint8_t)m_modem->m_adfGainMode; - - // are we on a protocol version 3 firmware? - if (m_modem->getVersion() >= 3U) { - lengthToWrite = 22U; - - m_conf["system"]["modem"]["hotspot"]["nxdnDiscBWAdj"] = __INT_STR(m_modem->m_nxdnDiscBWAdj); - buffer[18U] = (uint8_t)(m_modem->m_nxdnDiscBWAdj + 128); - m_conf["system"]["modem"]["hotspot"]["nxdnPostBWAdj"] = __INT_STR(m_modem->m_nxdnPostBWAdj); - buffer[19U] = (uint8_t)(m_modem->m_nxdnPostBWAdj + 128); - - // support optional AFC parameters - m_conf["system"]["modem"]["hotspot"]["afcEnable"] = __BOOL_STR(m_modem->m_afcEnable); - m_conf["system"]["modem"]["hotspot"]["afcKI"] = __INT_STR(m_modem->m_afcKI); - m_conf["system"]["modem"]["hotspot"]["afcKP"] = __INT_STR(m_modem->m_afcKP); - buffer[20U] = (m_modem->m_afcEnable ? 0x80 : 0x00) + - (m_modem->m_afcKP << 4) + (m_modem->m_afcKI); - m_conf["system"]["modem"]["hotspot"]["afcRange"] = __INT_STR(m_modem->m_afcRange); - buffer[21U] = m_modem->m_afcRange; - } - - buffer[1U] = lengthToWrite; - - int ret = m_modem->write(buffer, lengthToWrite); - if (ret <= 0) - return false; - - sleep(10U); - - m_modem->clock(0U); - return true; -} - -/// -/// Write symbol level adjustments to the modem DSP. -/// -/// True, if level adjustments are written, otherwise false. -bool HostCal::writeSymbolAdjust() -{ - uint8_t buffer[20U]; - ::memset(buffer, 0x00U, 20U); - uint8_t lengthToWrite = 7U; - - buffer[0U] = DVM_FRAME_START; - buffer[2U] = CMD_SET_SYMLVLADJ; - - m_conf["system"]["modem"]["repeater"]["dmrSymLvl3Adj"] = __INT_STR(m_modem->m_dmrSymLevel3Adj); - buffer[3U] = (uint8_t)(m_modem->m_dmrSymLevel3Adj + 128); - m_conf["system"]["modem"]["repeater"]["dmrSymLvl1Adj"] = __INT_STR(m_modem->m_dmrSymLevel1Adj); - buffer[4U] = (uint8_t)(m_modem->m_dmrSymLevel1Adj + 128); - - m_conf["system"]["modem"]["repeater"]["p25SymLvl3Adj"] = __INT_STR(m_modem->m_p25SymLevel3Adj); - buffer[5U] = (uint8_t)(m_modem->m_p25SymLevel3Adj + 128); - m_conf["system"]["modem"]["repeater"]["p25SymLvl1Adj"] = __INT_STR(m_modem->m_p25SymLevel1Adj); - buffer[6U] = (uint8_t)(m_modem->m_p25SymLevel1Adj + 128); - - // are we on a protocol version 3 firmware? - if (m_modem->getVersion() >= 3U) { - lengthToWrite = 9U; - - m_conf["system"]["modem"]["repeater"]["nxdnSymLvl3Adj"] = __INT_STR(m_modem->m_nxdnSymLevel3Adj); - buffer[7U] = (uint8_t)(m_modem->m_nxdnSymLevel3Adj + 128); - m_conf["system"]["modem"]["repeater"]["nxdnSymLvl1Adj"] = __INT_STR(m_modem->m_nxdnSymLevel1Adj); - buffer[8U] = (uint8_t)(m_modem->m_nxdnSymLevel1Adj + 128); - } - - buffer[1U] = lengthToWrite; - - int ret = m_modem->write(buffer, lengthToWrite); - if (ret <= 0) - return false; - - sleep(10U); - - m_modem->clock(0U); - return true; -} - -/// -/// Helper to sleep the calibration thread. -/// -/// Milliseconds to sleep. -void HostCal::sleep(uint32_t ms) -{ -#if defined(_WIN32) || defined(_WIN64) - ::Sleep(ms); -#else - ::usleep(ms * 1000); -#endif -} - -/// -/// Read the configuration area on the air interface modem. -/// -bool HostCal::readFlash() -{ - if (m_modem->m_flashDisabled) { - return false; + LogMessage(LOG_CAL, " - TX Coarse Level: %u", m_modem->m_txCoarsePot); + return writeConfig(); } - uint8_t buffer[3U]; - ::memset(buffer, 0x00U, 3U); - - buffer[0U] = DVM_FRAME_START; - buffer[1U] = 3U; - buffer[2U] = CMD_FLSH_READ; - - int ret = m_modem->write(buffer, 3U); - if (ret <= 0) - return false; - - sleep(1000U); - - m_modem->clock(0U); return true; } /// -/// Process the configuration data from the air interface modem. +/// Helper to change the RSSI coarse level. /// -/// -void HostCal::processFlashConfig(const uint8_t *buffer) +/// Amount to change. +/// True, if setting was applied, otherwise false. +bool HostCal::setRSSICoarseLevel(int incr) { - if (m_updateConfigFromModem) { - LogMessage(LOG_CAL, " - Restoring local configuration from configuration area on modem"); - - // general config - m_modem->m_rxInvert = (buffer[3U] & 0x01U) == 0x01U; - m_conf["system"]["modem"]["rxInvert"] = __BOOL_STR(m_modem->m_rxInvert); - m_modem->m_txInvert = (buffer[3U] & 0x02U) == 0x02U; - m_conf["system"]["modem"]["txInvert"] = __BOOL_STR(m_modem->m_txInvert); - m_modem->m_pttInvert = (buffer[3U] & 0x04U) == 0x04U; - m_conf["system"]["modem"]["pttInvert"] = __BOOL_STR(m_modem->m_pttInvert); - - m_modem->m_dcBlocker = (buffer[4U] & 0x01U) == 0x01U; - m_conf["system"]["modem"]["dcBlocker"] = __BOOL_STR(m_modem->m_dcBlocker); - - m_modem->m_fdmaPreamble = buffer[5U]; - m_conf["system"]["modem"]["fdmaPreamble"] = __INT_STR(m_modem->m_fdmaPreamble); - - // levels - m_modem->m_rxLevel = (float(buffer[7U]) - 0.5F) / 2.55F; - m_conf["system"]["modem"]["rxLevel"] = __FLOAT_STR(m_modem->m_rxLevel); - m_modem->m_cwIdTXLevel = (float(buffer[8U]) - 0.5F) / 2.55F; - m_conf["system"]["modem"]["txLevel"] = __FLOAT_STR(m_modem->m_cwIdTXLevel); - - m_modem->m_dmrRxDelay = buffer[10U]; - m_conf["system"]["modem"]["dmrRxDelay"] = __INT_STR(m_modem->m_dmrRxDelay); - - m_modem->m_p25CorrCount = buffer[11U]; - m_conf["system"]["modem"]["p25CorrCount"] = __INT_STR(m_modem->m_p25CorrCount); - - m_modem->m_txDCOffset = int(buffer[16U]) - 128; - m_conf["system"]["modem"]["txDCOffset"] = __INT_STR(m_modem->m_txDCOffset); - m_modem->m_rxDCOffset = int(buffer[17U]) - 128; - m_conf["system"]["modem"]["rxDCOffset"] = __INT_STR(m_modem->m_rxDCOffset); - - writeConfig(); - sleep(500); - - // symbol adjust - m_modem->m_dmrSymLevel3Adj = int(buffer[35U]) - 128; - m_conf["system"]["modem"]["repeater"]["dmrSymLvl3Adj"] = __INT_STR(m_modem->m_dmrSymLevel3Adj); - m_modem->m_dmrSymLevel1Adj = int(buffer[36U]) - 128; - m_conf["system"]["modem"]["repeater"]["dmrSymLvl1Adj"] = __INT_STR(m_modem->m_dmrSymLevel1Adj); - - m_modem->m_p25SymLevel3Adj = int(buffer[37U]) - 128; - m_conf["system"]["modem"]["repeater"]["p25SymLvl3Adj"] = __INT_STR(m_modem->m_p25SymLevel3Adj); - m_modem->m_p25SymLevel1Adj = int(buffer[38U]) - 128; - m_conf["system"]["modem"]["repeater"]["p25SymLvl1Adj"] = __INT_STR(m_modem->m_p25SymLevel1Adj); - - // are we on a protocol version 3 firmware? - if (m_modem->getVersion() >= 3U) { - m_modem->m_nxdnSymLevel3Adj = int(buffer[41U]) - 128; - m_conf["system"]["modem"]["repeater"]["nxdnSymLvl3Adj"] = __INT_STR(m_modem->m_nxdnSymLevel3Adj); - m_modem->m_nxdnSymLevel1Adj = int(buffer[42U]) - 128; - m_conf["system"]["modem"]["repeater"]["nxdnSymLvl1Adj"] = __INT_STR(m_modem->m_nxdnSymLevel1Adj); - } - - writeSymbolAdjust(); - sleep(500); - - // RF parameters - m_modem->m_dmrDiscBWAdj = int8_t(buffer[20U]) - 128; - m_conf["system"]["modem"]["hotspot"]["dmrDiscBWAdj"] = __INT_STR(m_modem->m_dmrDiscBWAdj); - m_modem->m_p25DiscBWAdj = int8_t(buffer[21U]) - 128; - m_conf["system"]["modem"]["hotspot"]["p25DiscBWAdj"] = __INT_STR(m_modem->m_p25DiscBWAdj); - m_modem->m_dmrPostBWAdj = int8_t(buffer[22U]) - 128; - m_conf["system"]["modem"]["hotspot"]["dmrPostBWAdj"] = __INT_STR(m_modem->m_dmrPostBWAdj); - m_modem->m_p25PostBWAdj = int8_t(buffer[23U]) - 128; - m_conf["system"]["modem"]["hotspot"]["p25PostBWAdj"] = __INT_STR(m_modem->m_p25PostBWAdj); - - m_modem->m_adfGainMode = (ADF_GAIN_MODE)buffer[24U]; - m_conf["system"]["modem"]["hotspot"]["adfGainMode"] = __INT_STR((int)m_modem->m_adfGainMode); - - // are we on a protocol version 3 firmware? - if (m_modem->getVersion() >= 3U) { - m_modem->m_nxdnDiscBWAdj = int8_t(buffer[39U]) - 128; - m_conf["system"]["modem"]["repeater"]["nxdnDiscBWAdj"] = __INT_STR(m_modem->m_nxdnDiscBWAdj); - m_modem->m_nxdnPostBWAdj = int8_t(buffer[40U]) - 128; - m_conf["system"]["modem"]["repeater"]["nxdnPostBWAdj"] = __INT_STR(m_modem->m_nxdnPostBWAdj); - } - - m_modem->m_txTuning = int(buffer[25U]) - 128; - m_conf["system"]["modem"]["hotspot"]["txTuning"] = __INT_STR(m_modem->m_txTuning); - m_txAdjustedFreq = m_txFrequency + m_modem->m_txTuning; - m_modem->m_rxTuning = int(buffer[26U]) - 128; - m_conf["system"]["modem"]["hotspot"]["rxTuning"] = __INT_STR(m_modem->m_rxTuning); - m_rxAdjustedFreq = m_rxFrequency + m_modem->m_rxTuning; - - // are we on a protocol version 3 firmware? - if (m_modem->getVersion() >= 3U) { - m_modem->m_rxCoarsePot = buffer[43U]; - m_conf["system"]["modem"]["softpot"]["rxCoarse"] = __INT_STR(m_modem->m_rxCoarsePot); - m_modem->m_rxFinePot = buffer[44U]; - m_conf["system"]["modem"]["softpot"]["rxFine"] = __INT_STR(m_modem->m_rxFinePot); - - m_modem->m_txCoarsePot = buffer[45U]; - m_conf["system"]["modem"]["softpot"]["txCoarse"] = __INT_STR(m_modem->m_txCoarsePot); - m_modem->m_txFinePot = buffer[46U]; - m_conf["system"]["modem"]["softpot"]["txFine"] = __INT_STR(m_modem->m_txFinePot); - - m_modem->m_rssiCoarsePot = buffer[47U]; - m_conf["system"]["modem"]["softpot"]["rssiCoarse"] = __INT_STR(m_modem->m_rssiCoarsePot); - m_modem->m_rssiFinePot = buffer[48U]; - m_conf["system"]["modem"]["softpot"]["rssiFine"] = __INT_STR(m_modem->m_rssiFinePot); + if (incr > 0 && m_modem->m_rssiCoarsePot < 255U) { + if (m_modem->m_rssiCoarsePot != 255U) { + m_modem->m_rssiCoarsePot += 1U; } - writeRFParams(); - sleep(500); - } -} - -/// -/// Erase the configuration area on the air interface modem. -/// -bool HostCal::eraseFlash() -{ - if (m_modem->m_flashDisabled) { - return false; - } - - uint8_t buffer[249U]; - ::memset(buffer, 0x00U, 249U); - - buffer[0U] = DVM_FRAME_START; - buffer[1U] = 249U; - buffer[2U] = CMD_FLSH_WRITE; - - // configuration version - buffer[DVM_CONF_AREA_LEN] = DVM_CONF_AREA_VER & 0x7FU; - buffer[DVM_CONF_AREA_LEN] |= 0x80U; // flag erased - edac::CRC::addCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); - - int ret = m_modem->write(buffer, 249U); - if (ret <= 0) - return false; - - sleep(1000U); - - m_updateConfigFromModem = false; - LogMessage(LOG_CAL, " - Erased configuration area on modem"); - - m_modem->clock(0U); - return true; -} - -/// -/// Write the configuration area on the air interface modem. -/// -bool HostCal::writeFlash() -{ - if (m_modem->m_flashDisabled) { - return false; - } - - uint8_t buffer[249U]; - ::memset(buffer, 0x00U, 249U); - - buffer[0U] = DVM_FRAME_START; - buffer[1U] = 249U; - buffer[2U] = CMD_FLSH_WRITE; - - // general config - buffer[3U] = 0x00U; - if (m_modem->m_rxInvert) - buffer[3U] |= 0x01U; - if (m_modem->m_txInvert) - buffer[3U] |= 0x02U; - if (m_modem->m_pttInvert) - buffer[3U] |= 0x04U; - - buffer[4U] = 0x00U; - if (m_modem->m_dcBlocker) - buffer[4U] |= 0x01U; - - buffer[5U] = m_modem->m_fdmaPreamble; - - buffer[7U] = (uint8_t)(m_modem->m_rxLevel * 2.55F + 0.5F); - buffer[8U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); - - buffer[10U] = m_modem->m_dmrRxDelay; - buffer[11U] = (uint8_t)m_modem->m_p25CorrCount; - - buffer[13U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); - buffer[15U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); - - buffer[16U] = (uint8_t)(m_modem->m_txDCOffset + 128); - buffer[17U] = (uint8_t)(m_modem->m_rxDCOffset + 128); - - // RF parameters - buffer[20U] = (uint8_t)(m_modem->m_dmrDiscBWAdj + 128); - buffer[21U] = (uint8_t)(m_modem->m_p25DiscBWAdj + 128); - buffer[22U] = (uint8_t)(m_modem->m_dmrPostBWAdj + 128); - buffer[23U] = (uint8_t)(m_modem->m_p25PostBWAdj + 128); - - buffer[24U] = (uint8_t)m_modem->m_adfGainMode; - - uint32_t txTuning = (uint32_t)m_modem->m_txTuning; - __SET_UINT32(txTuning, buffer, 25U); - uint32_t rxTuning = (uint32_t)m_modem->m_rxTuning; - __SET_UINT32(rxTuning, buffer, 29U); - - // symbol adjust - buffer[35U] = (uint8_t)(m_modem->m_dmrSymLevel3Adj + 128); - buffer[36U] = (uint8_t)(m_modem->m_dmrSymLevel1Adj + 128); - - buffer[37U] = (uint8_t)(m_modem->m_p25SymLevel3Adj + 128); - buffer[38U] = (uint8_t)(m_modem->m_p25SymLevel1Adj + 128); - - // are we on a protocol version 3 firmware? - if (m_modem->getVersion() >= 3U) { - buffer[39U] = (uint8_t)(m_modem->m_nxdnDiscBWAdj + 128); - buffer[40U] = (uint8_t)(m_modem->m_nxdnPostBWAdj + 128); - - buffer[41U] = (uint8_t)(m_modem->m_nxdnSymLevel3Adj + 128); - buffer[42U] = (uint8_t)(m_modem->m_nxdnSymLevel1Adj + 128); + LogMessage(LOG_CAL, " - RSSI Coarse Level: %u", m_modem->m_rssiCoarsePot); + return writeConfig(); } - // are we on a protocol version 3 firmware? - if (m_modem->getVersion() >= 3U) { - buffer[43U] = m_modem->m_rxCoarsePot; - buffer[44U] = m_modem->m_rxFinePot; - - buffer[45U] = m_modem->m_txCoarsePot; - buffer[46U] = m_modem->m_txFinePot; - - buffer[47U] = m_modem->m_rssiCoarsePot; - buffer[48U] = m_modem->m_rssiFinePot; - } + if (incr < 0 && m_modem->m_rssiCoarsePot > 0U) { + if (m_modem->m_rssiCoarsePot != 0) { + m_modem->m_rssiCoarsePot -= 1U; + } - // software signature - std::string software; - software.append(__NET_NAME__ " " __VER__ " (built " __BUILD__ ")"); - for (uint8_t i = 0; i < software.length(); i++) { - buffer[176U + i] = software[i]; + LogMessage(LOG_CAL, " - RSSI Coarse Level: %u", m_modem->m_rssiCoarsePot); + return writeConfig(); } - // configuration version - buffer[DVM_CONF_AREA_LEN] = DVM_CONF_AREA_VER; - edac::CRC::addCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); - -#if DEBUG_MODEM_CAL - Utils::dump(1U, "HostCal::writeFlash(), Written", buffer, 249U); -#endif - - int ret = m_modem->write(buffer, 249U); - if (ret <= 0) - return false; - - sleep(1000U); - - m_updateConfigFromModem = false; - - m_modem->clock(0U); return true; } -/// -/// Helper to clock the calibration BER timer. -/// -void HostCal::timerClock() -{ - if (m_timer > 0U && m_timeout > 0U) { - m_timer += 1U; - - if (m_timer >= m_timeout) { - LogMessage(LOG_CAL, "Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); - - m_berBits = 0U; - m_berErrs = 0U; - m_berFrames = 0U; - m_berUndecodableLC = 0U; - m_berUncorrectable = 0U; - - timerStop(); - } - } -} - -/// -/// Helper to start the calibration BER timer. -/// -void HostCal::timerStart() -{ - if (m_timeout > 0U) - m_timer = 1U; -} - -/// -/// Helper to stop the calibration BER timer. -/// -void HostCal::timerStop() -{ - m_timer = 0U; -} - -/// -/// Retrieve the current status from the air interface modem. -/// -void HostCal::getStatus() -{ - uint8_t buffer[50U]; - - buffer[0U] = DVM_FRAME_START; - buffer[1U] = 4U; - buffer[2U] = CMD_GET_STATUS; - - int ret = m_modem->write(buffer, 4U); - if (ret <= 0) - return; - - sleep(25U); - - m_requestedStatus = true; - m_modem->clock(0U); -} - /// /// Prints the current status of the calibration. /// @@ -3023,6 +999,8 @@ void HostCal::printStatus() LogMessage(LOG_CAL, " - RX Level: %.1f%%, TX Level: %.1f%%, TX DC Offset: %d, RX DC Offset: %d", m_modem->m_rxLevel, m_modem->m_cwIdTXLevel, m_modem->m_txDCOffset, m_modem->m_rxDCOffset); if (!m_isHotspot) { + LogMessage(LOG_CAL, " - RX Coarse Level: %u, RX Fine Level: %u, TX Coarse Level: %u, RSSI Coarse Level: %u", + m_modem->m_rxCoarsePot, m_modem->m_rxFinePot, m_modem->m_txCoarsePot, m_modem->m_rssiCoarsePot); LogMessage(LOG_CAL, " - DMR Symbol +/- 3 Level Adj.: %d, DMR Symbol +/- 1 Level Adj.: %d, P25 Symbol +/- 3 Level Adj.: %d, P25 Symbol +/- 1 Level Adj.: %d", m_modem->m_dmrSymLevel3Adj, m_modem->m_dmrSymLevel1Adj, m_modem->m_p25SymLevel3Adj, m_modem->m_p25SymLevel1Adj); @@ -3069,44 +1047,3 @@ void HostCal::printStatus() getStatus(); } - -/// -/// Add data frame to the data ring buffer. -/// -/// -/// -/// -void HostCal::addFrame(const uint8_t* data, uint32_t length, uint32_t maxFrameSize) -{ - assert(data != nullptr); - - uint32_t space = m_queue.freeSpace(); - if (space < (length + 1U)) { - uint32_t queueLen = m_queue.length(); - m_queue.resize(queueLen + maxFrameSize); - LogError(LOG_CAL, "overflow in the frame queue while writing data; queue free is %u, needed %u; resized was %u is %u", space, length, queueLen, m_queue.length()); - return; - } - - uint8_t len = length; - m_queue.addData(&len, 1U); - m_queue.addData(data, len); -} - -/// -/// Counts the total number of bit errors between bytes. -/// -/// -/// -/// -uint8_t HostCal::countErrs(uint8_t a, uint8_t b) -{ - int cnt = 0; - uint8_t tmp = a ^ b; - while (tmp) { - if (tmp % 2 == 1) - cnt++; - tmp /= 2; - } - return cnt; -} diff --git a/src/host/calibrate/HostCal.h b/src/host/calibrate/HostCal.h index 4601fae5..304de791 100644 --- a/src/host/calibrate/HostCal.h +++ b/src/host/calibrate/HostCal.h @@ -13,7 +13,7 @@ /* * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX * Copyright (C) 2017,2018 by Andy Uribe CA6JAU -* Copyright (C) 2017-2021 by Bryan Biedenkapp N2PLL +* Copyright (C) 2017-2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,13 +33,8 @@ #define __HOST_CAL_H__ #include "Defines.h" -#include "edac/AMBEFEC.h" -#include "modem/Modem.h" +#include "host/setup/HostSetup.h" #include "host/Console.h" -#include "host/Host.h" -#include "lookups/IdenTableLookup.h" -#include "yaml/Yaml.h" -#include "RingBuffer.h" #include @@ -48,7 +43,7 @@ // This class implements the interactive calibration mode. // --------------------------------------------------------------------------- -class HOST_SW_API HostCal { +class HOST_SW_API HostCal : public HostSetup { public: /// Initializes a new instance of the HostCal class. HostCal(const std::string& confFile); @@ -56,64 +51,10 @@ public: ~HostCal(); /// Executes the calibration processing loop. - int run(); + int run(int argc, char **argv); private: - const std::string& m_confFile; - yaml::Node m_conf; - - modem::Modem* m_modem; - - RingBuffer m_queue; - Console m_console; - edac::AMBEFEC m_fec; - bool m_transmit; - - bool m_duplex; - - bool m_dmrEnabled; - bool m_dmrRx1K; - bool m_p25Enabled; - bool m_p25Rx1K; - bool m_p25TduTest; - bool m_nxdnEnabled; - - bool m_isHotspot; - - bool m_debug; - - uint8_t m_mode; - std::string m_modeStr; - - uint32_t m_rxFrequency; // hotspot modem - Rx Frequency - uint32_t m_rxAdjustedFreq; - uint32_t m_txFrequency; // hotspot modem - Tx Frequency - uint32_t m_txAdjustedFreq; - uint8_t m_channelId; - uint32_t m_channelNo; - - lookups::IdenTableLookup* m_idenTable; - - uint32_t m_berFrames; - uint32_t m_berBits; - uint32_t m_berErrs; - uint32_t m_berUndecodableLC; - uint32_t m_berUncorrectable; - - uint32_t m_timeout; - uint32_t m_timer; - - bool m_updateConfigFromModem; - bool m_hasFetchedStatus; - bool m_requestedStatus; - - /// Modem port open callback. - bool portModemOpen(modem::Modem* modem); - /// Modem port close callback. - bool portModemClose(modem::Modem* modem); - /// Modem clock callback. - bool portModemHandler(modem::Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM rspType, bool rspDblLen, const uint8_t* buffer, uint16_t len); /// Helper to print the calibration help to the console. void displayHelp(); @@ -126,70 +67,17 @@ private: bool setTXDCOffset(int incr); /// Helper to change the Rx DC offset. bool setRXDCOffset(int incr); - /// Helper to toggle modem transmit mode. - bool setTransmit(); - - /// Helper to change the DMR Symbol Level 3 adjust. - bool setDMRSymLevel3Adj(int incr); - /// Helper to change the DMR Symbol Level 1 adjust. - bool setDMRSymLevel1Adj(int incr); - /// Helper to change the P25 Symbol Level 3 adjust. - bool setP25SymLevel3Adj(int incr); - /// Helper to change the P25 Symbol Level 1 adjust. - bool setP25SymLevel1Adj(int incr); - /// Helper to change the NXDN Symbol Level 3 adjust. - bool setNXDNSymLevel3Adj(int incr); - /// Helper to change the NXDN Symbol Level 1 adjust. - bool setNXDNSymLevel1Adj(int incr); - - /// Process DMR Rx BER. - void processDMRBER(const uint8_t* buffer, uint8_t seq); - /// Process DMR Tx 1011hz BER. - void processDMR1KBER(const uint8_t* buffer, uint8_t seq); - /// Process P25 Rx BER. - void processP25BER(const uint8_t* buffer); - /// Process P25 Tx 1011hz BER. - void processP251KBER(const uint8_t* buffer); - /// Process NXDN Rx BER. - void processNXDNBER(const uint8_t* buffer); + /// Helper to change the Tx coarse level. + bool setTXCoarseLevel(int incr); + /// Helper to change the Rx coarse level. + bool setRXCoarseLevel(int incr); + /// Helper to change the Rx fine level. + bool setRXFineLevel(int incr); + /// Helper to change the RSSI level. + bool setRSSICoarseLevel(int incr); - /// Write configuration to the modem DSP. - bool writeConfig(); - /// Write configuration to the modem DSP. - bool writeConfig(uint8_t modeOverride); - /// Write RF parameters to the air interface modem. - bool writeRFParams(); - /// Write symbol level adjustments to the modem DSP. - bool writeSymbolAdjust(); - /// Helper to sleep the calibration thread. - void sleep(uint32_t ms); - - /// Read the configuration area on the air interface modem. - bool readFlash(); - /// Process the configuration data from the air interface modem. - void processFlashConfig(const uint8_t *buffer); - /// Erase the configuration area on the air interface modem. - bool eraseFlash(); - /// Write the configuration area on the air interface modem. - bool writeFlash(); - - /// Helper to clock the calibration BER timer. - void timerClock(); - /// Helper to start the calibration BER timer. - void timerStart(); - /// Helper to stop the calibration BER timer. - void timerStop(); - - /// Retrieve the current status from the air interface modem. - void getStatus(); /// Prints the current status of the calibration. void printStatus(); - - /// Add data frame to the data ring buffer. - void addFrame(const uint8_t* data, uint32_t length, uint32_t maxFrameSize); - - /// Counts the total number of bit errors between bytes. - uint8_t countErrs(uint8_t a, uint8_t b); }; #endif // __HOST_CAL_H__ diff --git a/src/host/fne/HostFNE.cpp b/src/host/fne/HostFNE.cpp new file mode 100644 index 00000000..901c0be2 --- /dev/null +++ b/src/host/fne/HostFNE.cpp @@ -0,0 +1,470 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "network/fne/TagDMRData.h" +#include "network/fne/TagP25Data.h" +#include "network/fne/TagNXDNData.h" +#include "network/UDPSocket.h" +#include "host/fne/HostFNE.h" +#include "HostMain.h" +#include "Log.h" +#include "StopWatch.h" +#include "Thread.h" +#include "Utils.h" + +using namespace network; +using namespace lookups; + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define IDLE_WARMUP_MS 5U + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the HostFNE class. +/// +/// Full-path to the configuration file. +HostFNE::HostFNE(const std::string& confFile) : + m_confFile(confFile), + m_conf(), + m_network(nullptr), + m_dmrEnabled(false), + m_p25Enabled(false), + m_nxdnEnabled(false), + m_ridLookup(nullptr), + m_tidLookup(nullptr), + m_peerNetworks(), + m_pingTime(5U), + m_maxMissedPings(5U), + m_updateLookupTime(10U), + m_allowActivityTransfer(false), + m_allowDiagnosticTransfer(false) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the HostFNE class. +/// +HostFNE::~HostFNE() +{ + /* stub */ +} + +/// +/// Executes the main FNE processing loop. +/// +/// Zero if successful, otherwise error occurred. +int HostFNE::run() +{ + bool ret = false; + try { + ret = yaml::Parse(m_conf, m_confFile.c_str()); + if (!ret) { + ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); + } + } + catch (yaml::OperationException const& e) { + ::fatal("cannot read the configuration file - %s (%s)", m_confFile.c_str(), e.message()); + } + + bool m_daemon = m_conf["daemon"].as(false); + if (m_daemon && g_foreground) + m_daemon = false; + + // initialize system logging + yaml::Node logConf = m_conf["log"]; + ret = ::LogInitialise(logConf["filePath"].as(), logConf["fileRoot"].as(), + logConf["fileLevel"].as(0U), logConf["displayLevel"].as(0U)); + if (!ret) { + ::fatal("unable to open the log file\n"); + } + + ret = ::ActivityLogInitialise(logConf["activityFilePath"].as(), logConf["fileRoot"].as()); + if (!ret) { + ::fatal("unable to open the activity log file\n"); + } + + // handle POSIX process forking + if (m_daemon) { + // create new process + pid_t pid = ::fork(); + if (pid == -1) { + ::fprintf(stderr, "%s: Couldn't fork() , exiting\n", g_progExe.c_str()); + ::LogFinalise(); + ::ActivityLogFinalise(); + return EXIT_FAILURE; + } + else if (pid != 0) { + ::LogFinalise(); + ::ActivityLogFinalise(); + exit(EXIT_SUCCESS); + } + + // create new session and process group + if (::setsid() == -1) { + ::fprintf(stderr, "%s: Couldn't setsid(), exiting\n", g_progExe.c_str()); + ::LogFinalise(); + ::ActivityLogFinalise(); + return EXIT_FAILURE; + } + + // set the working directory to the root directory + if (::chdir("/") == -1) { + ::fprintf(stderr, "%s: Couldn't cd /, exiting\n", g_progExe.c_str()); + ::LogFinalise(); + ::ActivityLogFinalise(); + return EXIT_FAILURE; + } + + ::close(STDIN_FILENO); + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); + } + + getHostVersion(); + ::LogInfo(">> Fixed Network Equipment"); + + // read base parameters from configuration + ret = readParams(); + if (!ret) + return EXIT_FAILURE; + + yaml::Node systemConf = m_conf["system"]; + + // try to load radio IDs table + std::string ridLookupFile = systemConf["radio_id"]["file"].as(); + uint32_t ridReloadTime = systemConf["radio_id"]["time"].as(0U); + + LogInfo("Radio Id Lookups"); + LogInfo(" File: %s", ridLookupFile.length() > 0U ? ridLookupFile.c_str() : "None"); + if (ridReloadTime > 0U) + LogInfo(" Reload: %u mins", ridReloadTime); + + m_ridLookup = new RadioIdLookup(ridLookupFile, ridReloadTime, true); + m_ridLookup->read(); + + // initialize master networking + ret = createMasterNetwork(); + if (!ret) + return EXIT_FAILURE; + + // initialize peer networking + ret = createPeerNetworks(); + if (!ret) + return EXIT_FAILURE; + + ::LogInfoEx(LOG_HOST, "FNE is up and running"); + + StopWatch stopWatch; + stopWatch.start(); + + // main execution loop + while (!g_killed) { + uint32_t ms = stopWatch.elapsed(); + + ms = stopWatch.elapsed(); + stopWatch.start(); + + // ------------------------------------------------------ + // -- Network Clocking -- + // ------------------------------------------------------ + + // clock master + if (m_network != nullptr) + m_network->clock(ms); + + // clock peers + for (auto network : m_peerNetworks) { + network::Network* peerNetwork = network.second; + if (peerNetwork != nullptr) { + peerNetwork->clock(ms); + + // process peer network traffic + processPeer(peerNetwork); + } + } + + if (ms < 2U) + Thread::sleep(1U); + } + + if (m_network != nullptr) { + m_network->close(); + delete m_network; + } + + for (auto network : m_peerNetworks) { + network::Network* peerNetwork = network.second; + if (peerNetwork != nullptr) + peerNetwork->close(); + } + m_peerNetworks.clear(); + + if (m_tidLookup != nullptr) { + m_tidLookup->stop(); + delete m_tidLookup; + } + if (m_ridLookup != nullptr) { + m_ridLookup->stop(); + delete m_ridLookup; + } + + return EXIT_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Reads basic configuration parameters from the YAML configuration file. +/// +bool HostFNE::readParams() +{ + yaml::Node systemConf = m_conf["system"]; + m_pingTime = systemConf["pingTime"].as(5U); + m_maxMissedPings = systemConf["maxMissedPings"].as(5U); + m_updateLookupTime = systemConf["tgRuleUpdateTime"].as(10U); + bool sendTalkgroups = systemConf["sendTalkgroups"].as(true); + + if (m_pingTime == 0U) { + m_pingTime = 5U; + } + + if (m_maxMissedPings == 0U) { + m_maxMissedPings = 5U; + } + + if (m_updateLookupTime == 0U) { + m_updateLookupTime = 10U; + } + + m_allowActivityTransfer = systemConf["allowActivityTransfer"].as(true); + m_allowDiagnosticTransfer = systemConf["allowDiagnosticTransfer"].as(true); + + LogInfo("General Parameters"); + LogInfo(" Peer Ping Time: %us", m_pingTime); + LogInfo(" Maximum Missed Pings: %u", m_maxMissedPings); + LogInfo(" Talkgroup Rule Update Time: %u mins", m_updateLookupTime); + + LogInfo(" Send Talkgroups: %s", sendTalkgroups ? "yes" : "no"); + + LogInfo(" Allow Activity Log Transfer: %s", m_allowActivityTransfer ? "yes" : "no"); + LogInfo(" Allow Diagnostic Log Transfer: %s", m_allowDiagnosticTransfer ? "yes" : "no"); + + // attempt to load and populate routing rules + yaml::Node masterConf = m_conf["master"]; + yaml::Node talkgroupRules = masterConf["talkgroup_rules"]; + std::string talkgroupConfig = talkgroupRules["file"].as(); + uint32_t talkgroupConfigReload = talkgroupRules["time"].as(30U); + + LogInfo("Talkgroup Rule Lookups"); + LogInfo(" File: %s", talkgroupConfig.length() > 0U ? talkgroupConfig.c_str() : "None"); + if (talkgroupConfigReload > 0U) + LogInfo(" Reload: %u mins", talkgroupConfigReload); + + m_tidLookup = new TalkgroupRulesLookup(talkgroupConfig, talkgroupConfigReload, true); + m_tidLookup->sendTalkgroups(sendTalkgroups); + m_tidLookup->read(); + + return true; +} + +/// +/// Initializes master FNE network connectivity. +/// +bool HostFNE::createMasterNetwork() +{ + yaml::Node masterConf = m_conf["master"]; + std::string address = masterConf["address"].as(); + uint16_t port = (uint16_t)masterConf["port"].as(TRAFFIC_DEFAULT_PORT); + uint32_t id = masterConf["peerId"].as(1001U); + std::string password = masterConf["password"].as(); + bool verbose = masterConf["verbose"].as(false); + bool debug = masterConf["debug"].as(false); + + m_dmrEnabled = masterConf["allowDMRTraffic"].as(true); + m_p25Enabled = masterConf["allowP25Traffic"].as(true); + m_nxdnEnabled = masterConf["allowNXDNTraffic"].as(true); + + uint32_t parrotDelay = masterConf["parrotDelay"].as(2500U); + if (m_pingTime * 1000U < parrotDelay) { + LogWarning(LOG_HOST, "Parrot delay cannot be longer then the ping time of a peer. Reducing parrot delay to half the ping time."); + parrotDelay = (m_pingTime * 1000U) / 2U; + } + + LogInfo("Network Parameters"); + LogInfo(" Peer ID: %u", id); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + LogInfo(" Allow DMR Traffic: %s", m_dmrEnabled ? "yes" : "no"); + LogInfo(" Allow P25 Traffic: %s", m_p25Enabled ? "yes" : "no"); + LogInfo(" Allow NXDN Traffic: %s", m_nxdnEnabled ? "yes" : "no"); + LogInfo(" Parrot Repeat Delay: %u ms", parrotDelay); + + if (verbose) { + LogInfo(" Verbose: yes"); + } + + if (debug) { + LogInfo(" Debug: yes"); + } + + // initialize networking + m_network = new FNENetwork(this, address, port, id, password, debug, verbose, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, parrotDelay, + m_allowActivityTransfer, m_allowDiagnosticTransfer, m_pingTime, m_updateLookupTime); + + m_network->setLookups(m_ridLookup, m_tidLookup); + + bool ret = m_network->open(); + if (!ret) { + delete m_network; + m_network = nullptr; + LogError(LOG_HOST, "failed to initialize traffic networking!"); + return false; + } + + return true; +} + +/// +/// Initializes peer FNE network connectivity. +/// +bool HostFNE::createPeerNetworks() +{ + yaml::Node& peerList = m_conf["peers"]; + if (peerList.size() > 0U) { + for (size_t i = 0; i < peerList.size(); i++) { + yaml::Node& peerConf = peerList[i]; + + bool enabled = peerConf["enabled"].as(false); + std::string address = peerConf["address"].as(); + uint16_t port = (uint16_t)peerConf["port"].as(TRAFFIC_DEFAULT_PORT); + std::string masterAddress = peerConf["masterAddress"].as(); + uint16_t masterPort = (uint16_t)peerConf["masterPort"].as(TRAFFIC_DEFAULT_PORT); + std::string password = peerConf["password"].as(); + uint32_t id = peerConf["peerId"].as(1001U); + bool debug = peerConf["debug"].as(false); + + std::string identity = peerConf["identity"].as(); + uint32_t rxFrequency = peerConf["rxFrequency"].as(0U); + uint32_t txFrequency = peerConf["txFrequency"].as(0U); + float latitude = peerConf["latitude"].as(0.0F); + float longitude = peerConf["longitude"].as(0.0F); + std::string location = peerConf["location"].as(); + + ::LogInfoEx(LOG_HOST, "Peer ID %u Master Address %s Master Port %u Identity %s Enabled %u", id, masterAddress.c_str(), masterPort, identity.c_str(), enabled); + + // initialize networking + network::Network* network = new Network(address, port, 0U, id, password, true, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, true, true, m_allowActivityTransfer, m_allowDiagnosticTransfer, false); + network->setMetadata(identity, rxFrequency, txFrequency, 0.0F, 0.0F, 0, 0, 0, latitude, longitude, 0, location); + + network->enable(enabled); + if (enabled) { + bool ret = network->open(); + if (!ret) { + LogError(LOG_HOST, "failed to initialize traffic networking for PEER %u", id); + network->enable(false); + network->close(); + } + } + + m_peerNetworks[identity] = network; + } + } + + return true; +} + +/// +/// Processes any peer network traffic. +/// +/// +void HostFNE::processPeer(network::Network* peerNetwork) +{ + if (peerNetwork == nullptr) + return; // this shouldn't happen... + if (peerNetwork->getStatus() != NET_STAT_RUNNING) + return; + + // process DMR data + if (peerNetwork->hasDMRData()) { + uint32_t length = 100U; + bool ret = false; + UInt8Array data = peerNetwork->readDMR(ret, length); + if (ret) { + uint32_t peerId = peerNetwork->getPeerId(); + uint32_t slotNo = (data[15U] & 0x80U) == 0x80U ? 2U : 1U; + uint32_t streamId = peerNetwork->getDMRStreamId(slotNo); + + m_network->dmrTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId); + } + } + + // process P25 data + if (peerNetwork->hasP25Data()) { + uint32_t length = 100U; + bool ret = false; + UInt8Array data = peerNetwork->readP25(ret, length); + if (ret) { + uint32_t peerId = peerNetwork->getPeerId(); + uint32_t streamId = peerNetwork->getP25StreamId(); + + m_network->p25TrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId); + } + } + + // process NXDN data + if (peerNetwork->hasNXDNData()) { + uint32_t length = 100U; + bool ret = false; + UInt8Array data = peerNetwork->readNXDN(ret, length); + if (ret) { + uint32_t peerId = peerNetwork->getPeerId(); + uint32_t streamId = peerNetwork->getNXDNStreamId(); + + m_network->nxdnTrafficHandler()->processFrame(data.get(), length, peerId, peerNetwork->pktLastSeq(), streamId); + } + } +} \ No newline at end of file diff --git a/src/host/fne/HostFNE.h b/src/host/fne/HostFNE.h new file mode 100644 index 00000000..20f3fc8c --- /dev/null +++ b/src/host/fne/HostFNE.h @@ -0,0 +1,90 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__HOST_FNE_H__) +#define __HOST_FNE_H__ + +#include "Defines.h" +#include "network/Network.h" +#include "network/FNENetwork.h" +#include "Timer.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" +#include "yaml/Yaml.h" + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the core FNE service logic. +// --------------------------------------------------------------------------- + +class HOST_SW_API HostFNE { +public: + /// Initializes a new instance of the HostFNE class. + HostFNE(const std::string& confFile); + /// Finalizes a instance of the HostFNE class. + ~HostFNE(); + + /// Executes the main FNE host processing loop. + int run(); + +private: + const std::string& m_confFile; + yaml::Node m_conf; + + friend class network::FNENetwork; + network::FNENetwork* m_network; + + bool m_dmrEnabled; + bool m_p25Enabled; + bool m_nxdnEnabled; + + lookups::RadioIdLookup* m_ridLookup; + lookups::TalkgroupRulesLookup* m_tidLookup; + + std::unordered_map m_peerNetworks; + + uint32_t m_pingTime; + uint32_t m_maxMissedPings; + uint32_t m_updateLookupTime; + + bool m_allowActivityTransfer; + bool m_allowDiagnosticTransfer; + + /// Reads basic configuration parameters from the INI. + bool readParams(); + /// Initializes master FNE network connectivity. + bool createMasterNetwork(); + /// Initializes peer FNE network connectivity. + bool createPeerNetworks(); + + /// Processes any peer network traffic. + void processPeer(network::Network* peerNetwork); +}; + +#endif // __HOST_FNE_H__ diff --git a/src/host/setup/AdjustWndBase.h b/src/host/setup/AdjustWndBase.h new file mode 100644 index 00000000..00d38b82 --- /dev/null +++ b/src/host/setup/AdjustWndBase.h @@ -0,0 +1,198 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__ADJUST_WND_BASE_H__) +#define __ADJUST_WND_BASE_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the base class for adjustment windows. +// --------------------------------------------------------------------------- + +class HOST_SW_API AdjustWndBase : public finalcut::FDialog +{ +public: + /// + /// Initializes a new instance of the AdjustWndBase class. + /// + /// + /// + explicit AdjustWndBase(HostSetup* setup, FWidget* widget = nullptr) : FDialog{widget}, + m_setup(setup) + { + /* stub */ + } + +protected: + HostSetup *m_setup; + + /// + /// + /// + virtual void initLayout() override + { + FDialog::setMinimizable(true); + FDialog::setShadow(); + + std::size_t maxWidth, maxHeight; + const auto& rootWidget = getRootWidget(); + + if (rootWidget) { + maxWidth = rootWidget->getClientWidth(); + maxHeight = rootWidget->getClientHeight(); + } + else { + // fallback to xterm default size + maxWidth = 80; + maxHeight = 24; + } + + const int x = 1 + int((maxWidth - getWidth()) / 2); + const int y = 1 + int((maxHeight - getHeight()) / 3); + FWindow::setPos(FPoint{x, y}, false); + FDialog::adjustSize(); + + FDialog::setModal(); + + initControls(); + + FDialog::initLayout(); + + rootWidget->redraw(); // bryanb: wtf? + redraw(); + } + + /// + /// + /// + virtual void initControls() + { + // transmit button and close button logic + m_txButton.setGeometry(FPoint(3, int(getHeight()) - 6), FSize(10, 3)); + if (!m_setup->m_isConnected) { + m_txButton.setDisable(); + } + + // set transmit button color state if connected + if (m_setup->m_isConnected) { + if (m_setup->m_transmit) { + m_txButton.setBackgroundColor(FColor::Red3); + m_txButton.setFocusBackgroundColor(FColor::Red3); + } + else { + m_txButton.resetColors(); + } + + m_txButton.redraw(); + } + + m_txButton.addCallback("clicked", [&]() { setTransmit(); }); + + + m_closeButton.setGeometry(FPoint(17, int(getHeight()) - 6), FSize(9, 3)); + m_closeButton.addCallback("clicked", [&]() { hide(); }); + + m_connectedLabel.setGeometry(FPoint(36, int(getHeight()) - 3), FSize(20, 3)); + if (m_setup->m_isConnected) { + m_connectedLabel.setText("Modem Connected"); + m_connectedLabel.setForegroundColor(FColor::Green3); + } + else { + m_connectedLabel.setText("Modem Disconnected"); + m_connectedLabel.setForegroundColor(FColor::Red3); + } + + focusFirstChild(); + } + + /// + /// + /// + virtual void adjustSize() override + { + FDialog::adjustSize(); + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + virtual void onKeyPress(finalcut::FKeyEvent* e) + { + const auto key = e->key(); + if (key == FKey::F12) { + setTransmit(); + } + else if (key == FKey::F2) { + m_setup->saveConfig(); + } + } + + /// + /// + /// + /// + virtual void onClose(FCloseEvent* e) override + { + hide(); + } + +private: + FLabel m_connectedLabel{"Modem Disconnected", this}; + + FButton m_txButton{"Transmit", this}; + FButton m_closeButton{"Close", this}; + + /// + /// + /// + void setTransmit() + { + if (!m_setup->setTransmit()) { + FMessageBox::error(this, "Failed to enable modem transmit!"); + } + if (m_setup->m_transmit) { + m_txButton.setBackgroundColor(FColor::Red3); + m_txButton.setFocusBackgroundColor(FColor::Red3); + } + else { + m_txButton.resetColors(); + } + + m_txButton.redraw(); + } +}; + +#endif // __ADJUST_WND_BASE_H__ \ No newline at end of file diff --git a/src/host/setup/BERDisplayWnd.h b/src/host/setup/BERDisplayWnd.h new file mode 100644 index 00000000..1fa36281 --- /dev/null +++ b/src/host/setup/BERDisplayWnd.h @@ -0,0 +1,232 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the finalcut project. (https://github.com/gansm/finalcut) +// Licensed under the LGPLv2 License (https://opensource.org/licenses/LGPL-2.0) +// +/* +* Copyright (C) 2012-2023 by Markus Gans +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__BER_DISPLAY_WND_H__) +#define __BER_DISPLAY_WND_H__ + +#include "host/setup/HostSetup.h" + +#include +#include +#include + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the bit error rate display window. +// --------------------------------------------------------------------------- + +class HOST_SW_API BERDisplayWnd final : public finalcut::FDialog +{ +public: + /// + /// Initializes a new instance of the BERDisplayWnd class. + /// + /// + explicit BERDisplayWnd(FWidget* widget = nullptr) : FDialog{widget} + { + m_code = { + /* + ** Segments are drawn as follows: + ** + ** H A I + ** F G B + ** E D C + */ + // h v v h v v h h v + // a b c d e f g h i + { '0', Segment{1, 1, 1, 1, 1, 1, 0, 1, 2} }, + { '1', Segment{0, 1, 1, 0, 0, 0, 0, 0, 2} }, + { '2', Segment{1, 1, 2, 1, 1, 2, 1, 1, 2} }, + { '3', Segment{1, 1, 1, 1, 2, 0, 1, 1, 2} }, + { '4', Segment{0, 1, 1, 0, 0, 1, 1, 1, 2} }, + { '5', Segment{1, 2, 1, 1, 2, 1, 1, 1, 2} }, + { '6', Segment{1, 2, 1, 1, 1, 1, 1, 1, 2} }, + { '7', Segment{1, 1, 1, 0, 0, 0, 0, 1, 2} }, + { '8', Segment{1, 1, 1, 1, 1, 1, 1, 1, 2} }, + { '9', Segment{1, 1, 1, 1, 2, 1, 1, 1, 2} }, + { 'A', Segment{1, 1, 1, 0, 1, 1, 1, 1, 2} }, + { 'B', Segment{0, 2, 1, 1, 1, 1, 1, 1, 0} }, + { 'C', Segment{1, 0, 2, 1, 1, 1, 0, 1, 2} }, + { 'D', Segment{0, 1, 1, 1, 1, 2, 1, 0, 2} }, + { 'E', Segment{1, 0, 2, 1, 1, 1, 1, 1, 2} }, + { 'F', Segment{1, 0, 0, 0, 1, 1, 1, 1, 2} } + }; + } + /// Copy constructor. + BERDisplayWnd(const BERDisplayWnd&) = delete; + /// Move constructor. + BERDisplayWnd(BERDisplayWnd&&) noexcept = delete; + /// Finalizes an instance of the ModemStatusWnd class. + ~BERDisplayWnd() noexcept override = default; + + /// Disable copy assignment operator (=). + auto operator= (const BERDisplayWnd&) -> BERDisplayWnd& = delete; + /// Disable move assignment operator (=). + auto operator= (BERDisplayWnd&&) noexcept -> BERDisplayWnd& = delete; + + /// Disable set X coordinate. + void setX(int, bool = true) override { } + /// Disable set Y coordinate. + void setY(int, bool = true) override { } + /// Disable set position. + void setPos(const FPoint&, bool = true) override { } + + /// + /// Helper to set the BER text. + /// + void ber(std::string str) + { + if (str.empty()) { + return; + } + + m_ber = str; + std::transform(m_ber.begin(), m_ber.end(), m_ber.begin(), ::toupper); + redraw(); + } + + /// Helper to set the segment color. + void segmentColor(FColor color) { m_segmentColor = color; } + +private: + std::string m_ber; + + struct Segment + { + unsigned char a : 2; + unsigned char b : 2; + unsigned char c : 2; + unsigned char d : 2; + unsigned char e : 2; + unsigned char f : 2; + unsigned char g : 2; + unsigned char h : 2; + unsigned char i : 2; + unsigned char : 2; // padding bit + }; + std::map m_code{}; + std::array m_line{}; + + FColor m_segmentColor{FColor::LightRed}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Receive BER"); + + const auto& rootWidget = getRootWidget(); + + FDialog::setGeometry(FPoint{(int)rootWidget->getClientWidth() - 26, 2}, FSize{25, 7}); + FDialog::setMinimumSize(FSize{25, 7}); + FDialog::setResizeable(false); + FDialog::setMinimizable(false); + FDialog::setTitlebarButtonVisibility(false); + FDialog::setShadow(false); + FDialog::setAlwaysOnTop(true); + + FDialog::initLayout(); + } + + /// + /// + /// + void draw() override + { + std::vector vtbuffer(3); + FDialog::draw(); + + setColor(FColor::LightGray, FColor::Black); + finalcut::drawBorder(this, FRect(FPoint{1, 2}, FPoint{25, 7})); + + for (const auto& ch : m_ber) { + const FColorPair color{m_segmentColor, FColor::Black}; + get7Segment(ch); + + for (std::size_t i = 0; i < 3; i++) + vtbuffer[i] << color << m_line[i] << " "; + } + + const std::size_t length = vtbuffer[0].getLength(); + const FVTermBuffer leftSpace = length < 23 ? FVTermBuffer() << FString(23 - length, ' ') : FVTermBuffer(); + print() << FPoint{2, 3} << leftSpace << vtbuffer[0] + << FPoint{2, 4} << leftSpace << vtbuffer[1] + << FPoint{2, 5} << leftSpace << vtbuffer[2] + << FPoint{2, 6} << FString{23, ' '}; + } + + /// + /// + /// + /// + void get7Segment(const wchar_t c) + { + for (std::size_t i = 0; i < 3; i++) + m_line[i].clear(); + + switch (c) { + case ':': + m_line[0] = ' '; + m_line[1] = '.'; + m_line[2] = '.'; + break; + + case '.': + m_line[0] = ' '; + m_line[1] = ' '; + m_line[2] = (wchar_t)(0x2584); + break; + + case '-': + m_line[0] << ' ' << ' ' << ' '; + m_line[1] << (wchar_t)(0x2584) << (wchar_t)(0x2584) << (wchar_t)(0x2584); + m_line[2] << ' ' << ' ' << ' '; + break; + + default: + // hexadecimal digit from 0 up to F + if (m_code.find(c) != m_code.end()) { + const Segment& s = m_code[c]; + constexpr std::array h{{0x20, 0x2584, 0x2588}}; + constexpr std::array v{{0x20, 0x2588, 0x2584}}; + + m_line[0] << h[s.h] << h[s.a] << v[s.i]; + m_line[1] << v[s.f] << h[s.g] << v[s.b]; + m_line[2] << v[s.e] << h[s.d] << v[s.c]; + } + } + } +}; + +#endif // __BER_DISPLAY_WND_H__ \ No newline at end of file diff --git a/src/host/setup/ChannelConfigSetWnd.h b/src/host/setup/ChannelConfigSetWnd.h new file mode 100644 index 00000000..7db4bed4 --- /dev/null +++ b/src/host/setup/ChannelConfigSetWnd.h @@ -0,0 +1,240 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__CHANNEL_CONFIG_SET_WND_H__) +#define __CHANNEL_CONFIG_SET_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/CloseWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the channel configuration window. +// --------------------------------------------------------------------------- + +class HOST_SW_API ChannelConfigSetWnd final : public CloseWndBase +{ +public: + /// + /// Initializes a new instance of the ChannelConfigSetWnd class. + /// + /// + /// + explicit ChannelConfigSetWnd(HostSetup* setup, FWidget* widget = nullptr) : CloseWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_channelIdLabel{"Channel ID: ", this}; + FSpinBox m_channelId{this}; + + FLabel m_baseFreqLabel{"Base Freq. (Hz): ", this}; + FLabel m_baseFreq{this}; + FLabel m_spaceHzLabel{"Spacing (Hz): ", this}; + FLabel m_spaceHz{this}; + + FButtonGroup m_chNoGroup{"Logical Channel Number", this}; + FRadioButton m_radioChNo{"Channel Number", &m_chNoGroup}; + FRadioButton m_radioChFreq{"Tx Frequency", &m_chNoGroup}; + + FLabel m_channelNoLabel{"Channel No.: ", this}; + FSpinBox m_channelNo{this}; + bool m_displayChannelFreq = false; + FLabel m_channelFreqLabel{"Tx Frequency: ", this}; + FSpinBox m_channelFreq{this}; + + FLabel m_hzLabel{"Hz", this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Channel Configuration"); + FDialog::setSize(FSize{60, 17}); + + m_enableSetButton = false; + CloseWndBase::initLayout(); + } + + /// + /// + /// + void initControls() + { + yaml::Node rfssConfig = m_setup->m_conf["system"]["config"]; + m_setup->m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); + IdenTable entry = m_setup->m_idenTable->find(m_setup->m_channelId); + + // channel ID and channel number type + { + m_channelIdLabel.setGeometry(FPoint(2, 2), FSize(20, 1)); + m_channelId.setGeometry(FPoint(23, 2), FSize(8, 1)); + m_channelId.setValue(m_setup->m_channelId); + m_channelId.setRange(0, 15); + m_channelId.setShadow(false); + m_channelId.addCallback("changed", [&]() { + uint8_t prevChannelId = m_setup->m_channelId; + m_setup->m_channelId = (uint8_t)(m_channelId.getValue()); + + entry = m_setup->m_idenTable->find(m_setup->m_channelId); + if (entry.baseFrequency() == 0U) { + std::stringstream ss; + ss << "Channel Id " << (uint32_t)(m_setup->m_channelId) << " has an invalid base frequency."; + FMessageBox::error(this, ss.str()); + m_setup->m_channelId = prevChannelId; + } + + entry = m_setup->m_idenTable->find(m_setup->m_channelId); + m_baseFreq.setText(__INT_STR(entry.baseFrequency())); + m_spaceHz.setText(__INT_STR(entry.chSpaceKhz() * 1000)); + + m_setup->m_conf["system"]["config"]["channelId"] = __INT_STR(m_setup->m_channelId); + m_setup->calculateRxTxFreq(); + if (m_setup->m_isConnected) { + m_setup->writeRFParams(); + } + }); + + m_baseFreqLabel.setGeometry(FPoint(2, 4), FSize(20, 1)); + m_baseFreq.setGeometry(FPoint(23, 4), FSize(20, 1)); + m_baseFreq.setText(__INT_STR(entry.baseFrequency())); + m_spaceHzLabel.setGeometry(FPoint(2, 5), FSize(20, 1)); + m_spaceHz.setGeometry(FPoint(23, 5), FSize(20, 1)); + m_spaceHz.setText(__INT_STR(entry.chSpaceKhz() * 1000)); + + m_chNoGroup.setGeometry(FPoint(2, 7), FSize(56, 2)); + m_radioChNo.setPos(FPoint(1, 1)); + m_radioChNo.addCallback("toggled", [&]() { + if (m_radioChNo.isChecked()) { + m_displayChannelFreq = false; + updateVisibleControls(); + } + }); + m_radioChFreq.setPos(FPoint(23, 1)); + m_radioChFreq.addCallback("toggled", [&]() { + if (m_radioChFreq.isChecked()) { + m_displayChannelFreq = true; + updateVisibleControls(); + } + }); + } + + // channel number + { + m_setup->m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); + + m_channelNoLabel.setGeometry(FPoint(2, 11), FSize(20, 1)); + m_channelNo.setGeometry(FPoint(23, 11), FSize(15, 1)); + m_channelNo.setValue(m_setup->m_channelNo); + m_channelNo.setRange(0, 4095); + m_channelNo.setShadow(false); + m_channelNo.addCallback("changed", [&]() { + m_setup->m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_channelNo.getValue()); + m_setup->calculateRxTxFreq(); + m_channelFreq.setValue(m_setup->m_txFrequency); + if (m_setup->m_isConnected) { + m_setup->writeRFParams(); + } + }); + } + + // channel frequency + { + m_setup->m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); + + m_channelFreqLabel.setGeometry(FPoint(2, 12), FSize(20, 1)); + m_channelFreq.setGeometry(FPoint(23, 12), FSize(15, 1)); + m_channelFreq.setValue(m_setup->m_txFrequency); + m_channelFreq.setShadow(false); + m_channelFreq.addCallback("changed", [&]() { + entry = m_setup->m_idenTable->find(m_setup->m_channelId); + + uint32_t txFrequency = m_channelFreq.getValue(); + + uint32_t prevTxFrequency = m_setup->m_txFrequency; + m_setup->m_txFrequency = txFrequency; + uint32_t prevRxFrequency = m_setup->m_rxFrequency; + m_setup->m_rxFrequency = m_setup->m_txFrequency + (uint32_t)(entry.txOffsetMhz() * 1000000); + + float spaceHz = entry.chSpaceKhz() * 1000; + + uint32_t rootFreq = m_setup->m_txFrequency - entry.baseFrequency(); + uint8_t prevChannelNo = m_setup->m_channelNo; + m_setup->m_channelNo = (uint32_t)(rootFreq / spaceHz); + + if (m_setup->m_channelNo < 0 || m_setup->m_channelNo > 4096) { + m_setup->m_channelNo = prevChannelNo; + m_setup->m_txFrequency = prevTxFrequency; + m_setup->m_rxFrequency = prevRxFrequency; + } + + m_setup->m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_setup->m_channelNo); + m_setup->calculateRxTxFreq(); + m_channelNo.setValue(m_setup->m_channelNo); + if (m_setup->m_isConnected) { + m_setup->writeRFParams(); + } + }); + m_hzLabel.setGeometry(FPoint(40, 12), FSize(5, 1)); + } + + updateVisibleControls(); + + CloseWndBase::initControls(); + } + + /// + /// + /// + void updateVisibleControls() + { + if (m_displayChannelFreq) { + m_channelNoLabel.setDisable(); + m_channelNo.setDisable(); + + m_channelFreqLabel.setEnable(); + m_channelFreq.setEnable(); + + redraw(); + return; + } + + m_channelNoLabel.setEnable(); + m_channelNo.setEnable(); + m_channelFreqLabel.setDisable(); + m_channelFreq.setDisable(); + + redraw(); + } +}; + +#endif // __CHANNEL_CONFIG_SET_WND_H__ \ No newline at end of file diff --git a/src/host/setup/CloseWndBase.h b/src/host/setup/CloseWndBase.h new file mode 100644 index 00000000..b9ed282c --- /dev/null +++ b/src/host/setup/CloseWndBase.h @@ -0,0 +1,152 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__CLOSE_WND_BASE_H__) +#define __CLOSE_WND_BASE_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the base class for windows with close buttons. +// --------------------------------------------------------------------------- + +class HOST_SW_API CloseWndBase : public finalcut::FDialog +{ +public: + /// + /// Initializes a new instance of the CloseWndBase class. + /// + /// + /// + explicit CloseWndBase(HostSetup* setup, FWidget* widget = nullptr) : FDialog{widget}, + m_setup(setup) + { + /* stub */ + } + +protected: + HostSetup *m_setup; + + bool m_enableSetButton; + FButton m_setButton{"Set", this}; + + /// + /// + /// + virtual void initLayout() override + { + FDialog::setMinimizable(true); + FDialog::setShadow(); + + std::size_t maxWidth, maxHeight; + const auto& rootWidget = getRootWidget(); + + if (rootWidget) { + maxWidth = rootWidget->getClientWidth(); + maxHeight = rootWidget->getClientHeight(); + } + else { + // fallback to xterm default size + maxWidth = 80; + maxHeight = 24; + } + + const int x = 1 + int((maxWidth - getWidth()) / 2); + const int y = 1 + int((maxHeight - getHeight()) / 3); + FWindow::setPos(FPoint{x, y}, false); + FDialog::adjustSize(); + + FDialog::setModal(); + + initControls(); + + FDialog::initLayout(); + + rootWidget->redraw(); // bryanb: wtf? + redraw(); + } + + /// + /// + /// + virtual void initControls() + { + m_closeButton.setGeometry(FPoint(int(getWidth()) - 12, int(getHeight()) - 6), FSize(9, 3)); + m_closeButton.addCallback("clicked", [&]() { hide(); }); + + m_setButton.setDisable(); + m_setButton.setVisible(false); + if (m_enableSetButton) { + m_setButton.setEnable(); + m_setButton.setVisible(true); + m_setButton.setGeometry(FPoint(int(getWidth()) - 24, int(getHeight()) - 6), FSize(9, 3)); + } + + focusFirstChild(); + } + + /// + /// + /// + virtual void adjustSize() override + { + FDialog::adjustSize(); + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + virtual void onKeyPress(finalcut::FKeyEvent* e) + { + const auto key = e->key(); + if (key == FKey::F2) { + m_setup->saveConfig(); + } + } + + /// + /// + /// + /// + virtual void onClose(FCloseEvent* e) override + { + hide(); + } + +private: + FButton m_closeButton{"Close", this}; +}; + +#endif // __CLOSE_WND_BASE_H__ \ No newline at end of file diff --git a/src/host/setup/FIFOBufferAdjustWnd.h b/src/host/setup/FIFOBufferAdjustWnd.h new file mode 100644 index 00000000..f92a0c6f --- /dev/null +++ b/src/host/setup/FIFOBufferAdjustWnd.h @@ -0,0 +1,125 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__FIFO_BUFFER_ADJUST_WND_H__) +#define __FIFO_BUFFER_ADJUST_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/CloseWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the FIFO buffer adjustment window. +// --------------------------------------------------------------------------- + +class HOST_SW_API FIFOBufferAdjustWnd final : public CloseWndBase +{ +public: + /// + /// Initializes a new instance of the FIFOBufferAdjustWnd class. + /// + /// + /// + explicit FIFOBufferAdjustWnd(HostSetup* setup, FWidget* widget = nullptr) : CloseWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_fifoBufferLabel{"FIFO Buffers", this}; + FLabel m_dmrBufferLabel{"DMR Buffer (bytes): ", this}; + FLabel m_p25BufferLabel{"P25 Buffer (bytes): ", this}; + FLabel m_nxdnBufferLabel{"NXDN Buffer (bytes): ", this}; + + FSpinBox m_dmrBuffer{this}; + FSpinBox m_p25Buffer{this}; + FSpinBox m_nxdnBuffer{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("FIFO Buffer Adjustment"); + FDialog::setSize(FSize{60, 13}); + + m_enableSetButton = true; + CloseWndBase::initLayout(); + } + + /// + /// + /// + void initControls() + { + // symbol levels + { + m_fifoBufferLabel.setGeometry(FPoint(2, 1), FSize(20, 2)); + m_fifoBufferLabel.setEmphasis(); + m_fifoBufferLabel.setAlignment(Align::Center); + + m_dmrBufferLabel.setGeometry(FPoint(2, 3), FSize(25, 1)); + m_dmrBuffer.setGeometry(FPoint(28, 3), FSize(10, 1)); + m_dmrBuffer.setRange(DMR_TX_BUFFER_LEN, 65535); + m_dmrBuffer.setValue(m_setup->m_modem->m_dmrFifoLength); + m_dmrBuffer.setShadow(false); + m_dmrBuffer.addCallback("changed", [&]() { + m_setup->m_modem->m_dmrFifoLength = m_dmrBuffer.getValue(); + }); + + m_p25BufferLabel.setGeometry(FPoint(2, 4), FSize(25, 1)); + m_p25Buffer.setGeometry(FPoint(28, 4), FSize(10, 1)); + m_p25Buffer.setRange(P25_TX_BUFFER_LEN, 65535); + m_p25Buffer.setValue(m_setup->m_modem->m_p25FifoLength); + m_p25Buffer.setShadow(false); + m_p25Buffer.addCallback("changed", [&]() { + m_setup->m_modem->m_p25FifoLength = m_p25Buffer.getValue(); + }); + + m_nxdnBufferLabel.setGeometry(FPoint(2, 5), FSize(25, 1)); + m_nxdnBuffer.setGeometry(FPoint(28, 5), FSize(10, 1)); + m_nxdnBuffer.setRange(NXDN_TX_BUFFER_LEN, 65535); + m_nxdnBuffer.setValue(m_setup->m_modem->m_nxdnFifoLength); + m_nxdnBuffer.setShadow(false); + m_nxdnBuffer.addCallback("changed", [&]() { + m_setup->m_modem->m_nxdnFifoLength = m_nxdnBuffer.getValue(); + }); + } + + m_setButton.addCallback("clicked", [&]() { + Thread::sleep(2); + m_setup->writeFifoLength(); + }); + + CloseWndBase::initControls(); + } +}; + +#endif // __FIFO_BUFFER_ADJUST_WND_H__ \ No newline at end of file diff --git a/src/host/setup/HSBandwidthAdjustWnd.h b/src/host/setup/HSBandwidthAdjustWnd.h new file mode 100644 index 00000000..432fef1b --- /dev/null +++ b/src/host/setup/HSBandwidthAdjustWnd.h @@ -0,0 +1,184 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__HS_BANDWIDTH_ADJUST_WND_H__) +#define __HS_BANDWIDTH_ADJUST_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/AdjustWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the hotspot bandwidth adjustment window. +// --------------------------------------------------------------------------- + +class HOST_SW_API HSBandwidthAdjustWnd final : public AdjustWndBase +{ +public: + /// + /// Initializes a new instance of the HSBandwidthAdjustWnd class. + /// + /// + /// + explicit HSBandwidthAdjustWnd(HostSetup* setup, FWidget* widget = nullptr) : AdjustWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_adjLevelLabel{"Bandwidth Adjustment", this}; + FLabel m_dmrDiscBWLabel{"DMR Disc. BW Offset: ", this}; + FLabel m_dmrPostBWLabel{"DMR Post Demod BW Offset: ", this}; + FLabel m_p25DiscBWLabel{"P25 Disc. BW Offset: ", this}; + FLabel m_p25PostBWLabel{"P25 Post Demod BW Offset: ", this}; + FLabel m_nxdnDiscBWLabel{"NXDN Disc. BW Offset: ", this}; + FLabel m_nxdnPostBWLabel{"NXDN Post Demod BW Offset: ", this}; + + FSpinBox m_dmrDiscBW{this}; + FSpinBox m_dmrPostBW{this}; + FSpinBox m_p25DiscBW{this}; + FSpinBox m_p25PostBW{this}; + FSpinBox m_nxdnDiscBW{this}; + FSpinBox m_nxdnPostBW{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Hotspot Bandwidth Adjustment"); + FDialog::setSize(FSize{60, 15}); + + AdjustWndBase::initLayout(); + } + + /// + /// + /// + void initControls() + { + // symbol levels + { + m_adjLevelLabel.setGeometry(FPoint(2, 1), FSize(30, 2)); + m_adjLevelLabel.setEmphasis(); + m_adjLevelLabel.setAlignment(Align::Center); + + m_dmrDiscBWLabel.setGeometry(FPoint(2, 3), FSize(30, 1)); + m_dmrDiscBW.setGeometry(FPoint(33, 3), FSize(10, 1)); + m_dmrDiscBW.setRange(-127, 127); + m_dmrDiscBW.setValue(m_setup->m_modem->m_dmrDiscBWAdj); + m_dmrDiscBW.setShadow(false); + m_dmrDiscBW.addCallback("changed", [&]() { + m_setup->m_modem->m_dmrDiscBWAdj = m_dmrDiscBW.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_dmrPostBWLabel.setGeometry(FPoint(2, 4), FSize(30, 1)); + m_dmrPostBW.setGeometry(FPoint(33, 4), FSize(10, 1)); + m_dmrPostBW.setRange(-127, 127); + m_dmrPostBW.setValue(m_setup->m_modem->m_dmrPostBWAdj); + m_dmrPostBW.setShadow(false); + m_dmrPostBW.addCallback("changed", [&]() { + m_setup->m_modem->m_dmrPostBWAdj = m_dmrPostBW.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_p25DiscBWLabel.setGeometry(FPoint(2, 5), FSize(30, 1)); + m_p25DiscBW.setGeometry(FPoint(33, 5), FSize(10, 1)); + m_p25DiscBW.setRange(-127, 127); + m_p25DiscBW.setValue(m_setup->m_modem->m_p25DiscBWAdj); + m_p25DiscBW.setShadow(false); + m_p25DiscBW.addCallback("changed", [&]() { + m_setup->m_modem->m_p25DiscBWAdj = m_p25DiscBW.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_p25PostBWLabel.setGeometry(FPoint(2, 6), FSize(30, 1)); + m_p25PostBW.setGeometry(FPoint(33, 6), FSize(10, 1)); + m_p25PostBW.setRange(-127, 127); + m_p25PostBW.setValue(m_setup->m_modem->m_p25PostBWAdj); + m_p25PostBW.setShadow(false); + m_p25PostBW.addCallback("changed", [&]() { + m_setup->m_modem->m_p25PostBWAdj = m_p25PostBW.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_nxdnDiscBWLabel.setGeometry(FPoint(2, 6), FSize(30, 1)); + m_nxdnDiscBW.setGeometry(FPoint(33, 6), FSize(10, 1)); + m_nxdnDiscBW.setRange(-127, 127); + m_nxdnDiscBW.setValue(m_setup->m_modem->m_nxdnDiscBWAdj); + m_nxdnDiscBW.setShadow(false); + m_nxdnDiscBW.addCallback("changed", [&]() { + m_setup->m_modem->m_nxdnDiscBWAdj = m_nxdnDiscBW.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_nxdnPostBWLabel.setGeometry(FPoint(2, 7), FSize(30, 1)); + m_nxdnPostBW.setGeometry(FPoint(33, 7), FSize(10, 1)); + m_nxdnPostBW.setRange(-127, 127); + m_nxdnPostBW.setValue(m_setup->m_modem->m_nxdnPostBWAdj); + m_nxdnPostBW.setShadow(false); + m_nxdnPostBW.addCallback("changed", [&]() { + m_setup->m_modem->m_nxdnPostBWAdj = m_nxdnPostBW.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + } + + // setup control states + if (m_setup->m_isConnected) { + if (m_setup->m_modem->m_isHotspot) { + m_dmrDiscBW.setEnable(); + m_dmrPostBW.setEnable(); + m_p25DiscBW.setEnable(); + m_p25PostBW.setEnable(); + m_nxdnDiscBW.setEnable(); + m_nxdnPostBW.setEnable(); + } + else { + m_dmrDiscBW.setDisable(); + m_dmrPostBW.setDisable(); + m_p25DiscBW.setDisable(); + m_p25PostBW.setDisable(); + m_nxdnDiscBW.setDisable(); + m_nxdnPostBW.setDisable(); + } + } + + AdjustWndBase::initControls(); + } +}; + +#endif // __HS_BANDWIDTH_ADJUST_WND_H__ \ No newline at end of file diff --git a/src/host/setup/HSGainAdjustWnd.h b/src/host/setup/HSGainAdjustWnd.h new file mode 100644 index 00000000..6a7c5de5 --- /dev/null +++ b/src/host/setup/HSGainAdjustWnd.h @@ -0,0 +1,179 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__HS_GAIN_ADJUST_WND_H__) +#define __HS_GAIN_ADJUST_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/AdjustWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the hotspot gain adjustment window. +// --------------------------------------------------------------------------- + +class HOST_SW_API HSGainAdjustWnd final : public AdjustWndBase +{ +public: + /// + /// Initializes a new instance of the HSGainAdjustWnd class. + /// + /// + /// + explicit HSGainAdjustWnd(HostSetup* setup, FWidget* widget = nullptr) : AdjustWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_adjGainLabel{"Gain Adjustment", this}; + + FButtonGroup m_gainButtonGroup{"Gain", this}; + FRadioButton m_gainAHL{"Auto High Linearity", &m_gainButtonGroup}; + FRadioButton m_gainLow{"Low", &m_gainButtonGroup}; + FRadioButton m_gainHigh{"High", &m_gainButtonGroup}; + FRadioButton m_gainAuto{"Auto", &m_gainButtonGroup}; + + FLabel m_adjAFCLabel{"AFC Adjustment", this}; + + FCheckBox m_afcEnabled{"Enabled", this}; + FLabel m_afcRangeLabel{"Range: ", this}; + FSpinBox m_afcRange{this}; + FLabel m_afcKILabel{"KI: ", this}; + FSpinBox m_afcKI{this}; + FLabel m_afcKPLabel{"KP: ", this}; + FSpinBox m_afcKP{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Hotspot Gain & AFC Adjustment"); + FDialog::setSize(FSize{50, 22}); + + AdjustWndBase::initLayout(); + } + + /// + /// + /// + void initControls() + { + // gain + { + m_adjGainLabel.setGeometry(FPoint(2, 1), FSize(30, 2)); + m_adjGainLabel.setEmphasis(); + m_adjGainLabel.setAlignment(Align::Center); + + m_gainButtonGroup.setGeometry(FPoint(2, 3), FSize(30, 6)); + m_gainAHL.setPos(FPoint(1, 1)); + m_gainAHL.addCallback("toggled", [&]() { + if (m_gainAHL.isChecked()) { + m_setup->m_modem->m_adfGainMode = ADF_GAIN_AUTO_LIN; + m_setup->writeRFParams(); + } + }); + m_gainLow.setPos(FPoint(1, 2)); + m_gainLow.addCallback("toggled", [&]() { + if (m_gainAuto.isChecked()) { + m_setup->m_modem->m_adfGainMode = ADF_GAIN_LOW; + m_setup->writeRFParams(); + } + }); + m_gainHigh.setPos(FPoint(1, 3)); + m_gainHigh.addCallback("toggled", [&]() { + if (m_gainAuto.isChecked()) { + m_setup->m_modem->m_adfGainMode = ADF_GAIN_HIGH; + m_setup->writeRFParams(); + } + }); + m_gainAuto.setPos(FPoint(1, 4)); + m_gainAuto.addCallback("toggled", [&]() { + if (m_gainAuto.isChecked()) { + m_setup->m_modem->m_adfGainMode = ADF_GAIN_AUTO; + m_setup->writeRFParams(); + } + }); + } + + // afc + { + m_adjAFCLabel.setGeometry(FPoint(2, 10), FSize(30, 2)); + m_adjAFCLabel.setEmphasis(); + m_adjAFCLabel.setAlignment(Align::Center); + + m_afcEnabled.setGeometry(FPoint(2, 12), FSize(10, 1)); + m_afcEnabled.setChecked(m_setup->m_modem->m_afcEnable); + m_afcEnabled.addCallback("toggled", [&]() { + m_setup->m_modem->m_afcEnable = m_afcEnabled.isChecked(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_afcRangeLabel.setGeometry(FPoint(24, 12), FSize(10, 1)); + m_afcRange.setGeometry(FPoint(33, 12), FSize(10, 1)); + m_afcRange.setRange(0, 256); + m_afcRange.setValue(m_setup->m_modem->m_afcRange); + m_afcRange.setShadow(false); + m_afcRange.addCallback("changed", [&]() { + m_setup->m_modem->m_afcRange = m_afcRange.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_afcKILabel.setGeometry(FPoint(2, 13), FSize(10, 1)); + m_afcKI.setGeometry(FPoint(10, 13), FSize(10, 1)); + m_afcKI.setRange(0, 16); + m_afcKI.setValue(m_setup->m_modem->m_afcKI); + m_afcKI.setShadow(false); + m_afcKI.addCallback("changed", [&]() { + m_setup->m_modem->m_afcKI = m_afcKI.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_afcKPLabel.setGeometry(FPoint(24, 13), FSize(10, 1)); + m_afcKP.setGeometry(FPoint(33, 13), FSize(10, 1)); + m_afcKP.setRange(0, 8); + m_afcKP.setValue(m_setup->m_modem->m_afcKP); + m_afcKP.setShadow(false); + m_afcKP.addCallback("changed", [&]() { + m_setup->m_modem->m_afcKP = m_afcKP.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + } + + AdjustWndBase::initControls(); + } +}; + +#endif // __HS_GAIN_ADJUST_WND_H__ \ No newline at end of file diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index 3ea96530..bc11450f 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2021 by Bryan Biedenkapp N2PLL +* Copyright (C) 2021-2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,21 +23,92 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "dmr/DMRDefines.h" #include "dmr/DMRUtils.h" +#include "modem/port/ModemNullPort.h" +#include "modem/port/UARTPort.h" +#include "p25/P25Defines.h" +#include "p25/data/DataHeader.h" +#include "p25/lc/LC.h" +#include "p25/lc/tsbk/TSBKFactory.h" +#include "p25/NID.h" +#include "p25/Sync.h" #include "p25/P25Utils.h" +#include "nxdn/NXDNDefines.h" +#include "nxdn/channel/LICH.h" +#include "nxdn/NXDNUtils.h" +#include "edac/CRC.h" #include "host/setup/HostSetup.h" #include "HostMain.h" #include "Log.h" #include "Utils.h" +using namespace modem; using namespace lookups; #include #include -#if !defined(_WIN32) && !defined(_WIN64) #include -#endif + +#if defined(ENABLE_SETUP_TUI) +#include "host/setup/SetupApplication.h" +#include "host/setup/SetupMainWnd.h" + +#include +#endif // defined(ENABLE_SETUP_TUI) + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +// Voice LC MS Header, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VH_DMO1K[] = { + 0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U, + 0x60U, 0x84U, 0x6DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x7EU, 0x30U, 0x30U, + 0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU }; + +// Voice Term MS with LC, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VT_DMO1K[] = { + 0x00U, 0x4FU, 0x08U, 0xDCU, 0x02U, 0x88U, 0x15U, 0x78U, 0x2CU, 0xD0U, 0x14U, + 0xC0U, 0x84U, 0xADU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x79U, 0x65U, 0x24U, + 0x02U, 0x28U, 0x06U, 0x20U, 0x0FU, 0x80U, 0x1BU, 0xC1U, 0x07U, 0x80U, 0x5CU }; + +// Voice coding data + FEC, 1031 Hz Test Pattern +const uint8_t VOICE_1K[] = { + 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU, 0xCEU, 0xA8U, + 0xFEU, 0x83U, 0xA0U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0CU, 0xC4U, 0x58U, + 0x20U, 0x0AU, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU }; + +// Recommended 1011 Hz test pattern for P25 Phase 1 (ANSI/TIA-102.CAAA) +// NAC: 0x293, srcID: 1, dstID: TG1 +const uint8_t LDU1_1K[] = { + 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x35, 0x54, 0x7B, 0xCB, 0x19, 0x4D, 0x0D, 0xCE, 0x24, 0xA1, 0x24, + 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4, 0xE2, 0x4A, 0x10, + 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, + 0x89, 0xD8, 0x39, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, + 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x70, 0xE2, 0x4A, 0x12, 0x40, + 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x6D, 0xE5, 0xD5, 0x48, + 0xAD, 0xE3, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, + 0xD8, 0x3B, 0xA1, 0x41, 0xC2, 0xD2, 0xBA, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, + 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x94, 0xC8, 0xFB, 0x02, 0x35, 0xA4, 0xE2, 0x4A, 0x12, 0x43, 0x50, + 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, 0x00, 0x0C, + 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4 }; + +const uint8_t LDU2_1K[] = { + 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x3A, 0xB8, 0xA4, 0xEF, 0xB0, 0x9A, 0x8A, 0xCE, 0x24, 0xA1, 0x24, + 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC, 0xE2, 0x4A, 0x10, + 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, + 0x89, 0xD8, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, + 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE2, 0x4A, 0x12, 0x40, + 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xE0, 0xE0, 0x00, 0x00, + 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, + 0xD8, 0x39, 0xAE, 0x8B, 0x48, 0xB6, 0x49, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, + 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB9, 0xA8, 0xF4, 0xF1, 0xFD, 0x60, 0xE2, 0x4A, 0x12, 0x43, 0x50, + 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, 0x00, 0x0C, + 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC }; // --------------------------------------------------------------------------- // Public Class Members @@ -50,13 +121,39 @@ using namespace lookups; HostSetup::HostSetup(const std::string& confFile) : m_confFile(confFile), m_conf(), - m_console(), + m_stopWatch(), + m_modem(nullptr), + m_queue(4096U, "Frame Buffer"), + m_fec(), + m_transmit(false), m_duplex(true), + m_dmrEnabled(false), + m_dmrRx1K(false), + m_p25Enabled(false), + m_p25Rx1K(false), + m_p25TduTest(false), + m_nxdnEnabled(false), + m_isHotspot(false), + m_isConnected(false), + m_debug(false), + m_mode(STATE_DMR_CAL_1K), + m_modeStr(DMR_CAL_1K_STR), m_rxFrequency(0U), + m_rxAdjustedFreq(0U), m_txFrequency(0U), + m_txAdjustedFreq(0U), m_channelId(0U), m_channelNo(0U), - m_idenTable(nullptr) + m_idenTable(nullptr), + m_berFrames(0U), + m_berBits(0U), + m_berErrs(0U), + m_berUndecodableLC(0U), + m_berUncorrectable(0U), + m_timeout(300U), + m_timer(0U), + m_updateConfigFromModem(false), + m_hasFetchedStatus(false) { /* stub */ } @@ -66,18 +163,27 @@ HostSetup::HostSetup(const std::string& confFile) : /// HostSetup::~HostSetup() { - /* stub */ + delete m_modem; } +#if defined(ENABLE_SETUP_TUI) /// /// Executes the processing loop. /// +/// +/// /// Zero if successful, otherwise error occurred. -int HostSetup::run() +int HostSetup::run(int argc, char** argv) { - bool ret = yaml::Parse(m_conf, m_confFile.c_str()); - if (!ret) { - ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); + bool ret = false; + try { + ret = yaml::Parse(m_conf, m_confFile.c_str()); + if (!ret) { + ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); + } + } + catch (yaml::OperationException const& e) { + ::fatal("cannot read the configuration file - %s (%s)", m_confFile.c_str(), e.message()); } // initialize system logging @@ -90,6 +196,13 @@ int HostSetup::run() getHostVersion(); ::LogInfo(">> Modem Setup"); + // setup the finalcut tui + SetupApplication app{this, argc, argv}; + + SetupMainWnd setupWnd{this, &app}; + finalcut::FWidget::setMainWidget(&setupWnd); + m_setupWnd = &setupWnd; + yaml::Node logConf = m_conf["log"]; yaml::Node systemConf = m_conf["system"]; yaml::Node modemConfig = systemConf["modem"]; @@ -104,6 +217,8 @@ int HostSetup::run() return 1; } + g_logDisplayLevel = 0U; + LogInfo("Iden Table Lookups"); LogInfo(" File: %s", idenLookupFile.length() > 0U ? idenLookupFile.c_str() : "None"); if (idenReloadTime > 0U) @@ -112,7 +227,10 @@ int HostSetup::run() m_idenTable = new IdenTableLookup(idenLookupFile, idenReloadTime); m_idenTable->read(); - yaml::Node cwId = systemConf["cwId"]; + LogInfo("General Parameters"); + + std::string identity = systemConf["identity"].as(); + ::LogInfo(" Identity: %s", identity.c_str()); yaml::Node rfssConfig = systemConf["config"]; m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); @@ -120,648 +238,357 @@ int HostSetup::run() m_channelId = 15U; } - m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); - if (m_channelNo == 0U) { // clamp to 1 - m_channelNo = 1U; - } - if (m_channelNo > 4095U) { // clamp to 4095 - m_channelNo = 4095U; - } - - if (!calculateRxTxFreq()) { - return false; - } - - IdenTable entry = m_idenTable->find(m_channelId); - if (entry.baseFrequency() == 0U) { - ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); - return false; + if (!calculateRxTxFreq(true)) { + return 1; } - // open terminal console - ret = m_console.open(); - if (!ret) { + // initialize modem + if (!createModem(true)) { return 1; } - displayHelp(); + p25::lc::TSBK::setVerbose(true); + p25::lc::TSBK::setWarnCRC(true); + p25::lc::tsbk::TSBKFactory::setWarnCRC(true); - printStatus(); + setupWnd.setMenuStates(); - bool end = false; - while (!end) { - int c = m_console.getChar(); - switch (c) { + // show and start the application + setupWnd.show(); - /** Setup Commands */ - case 'L': - { - logConf = m_conf["log"]; - uint32_t logLevel = logConf["fileLevel"].as(1U); - std::string logFilePath = logConf["filePath"].as(); - std::string logActFilePath = logConf["activityFilePath"].as(); - - char value[128] = { '\0' }; - ::fprintf(stdout, "> Log File Path [%s] ? ", logFilePath.c_str()); - ::fflush(stdout); - - m_console.getLine(value, 128, 0); - logFilePath = std::string(value); - if (logFilePath.length() > 0) { - m_conf["log"]["filePath"] = logFilePath; - } - - ::fprintf(stdout, "> Activity File Path [%s] ? ", logActFilePath.c_str()); - ::fflush(stdout); - - m_console.getLine(value, 128, 0); - logActFilePath = std::string(value); - if (logActFilePath.length() > 0) { - m_conf["log"]["activityFilePath"] = logActFilePath; - } + finalcut::FApplication::setDarkTheme(); + app.resetColors(); + app.redraw(); + return app.exec(); +} +#endif // defined(ENABLE_SETUP_TUI) - ::fprintf(stdout, "> Logging Level [%u] (1-6 lowest) ? ", logLevel); - ::fflush(stdout); +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- - m_console.getLine(value, 1, 0); - uint32_t level = logLevel; - sscanf(value, "%u", &level); - if (level > 0) { - m_conf["log"]["displayLevel"] = __INT_STR(level); - m_conf["log"]["fileLevel"] = __INT_STR(level); - } +/// +/// +/// +/// +bool HostSetup::portModemOpen(Modem* modem) +{ + sleep(2000U); - printStatus(); + bool ret = writeRFParams(); + if (!ret) { + ret = writeRFParams(); + if (!ret) { + LogError(LOG_MODEM, "Modem unresponsive to RF parameters set after 2 attempts. Stopping."); + m_modem->close(); + m_isConnected = false; + return false; } - break; - - case 'F': - { - yaml::Node idenTable = m_conf["system"]["iden_table"]; - std::string idenFilePath = idenTable["file"].as(); - yaml::Node radioId = m_conf["system"]["radio_id"]; - std::string ridFilePath = radioId["file"].as(); - yaml::Node talkgroupId = m_conf["system"]["talkgroup_id"]; - std::string tgidFilePath = talkgroupId["file"].as(); - - char value[128] = { '\0' }; - ::fprintf(stdout, "> Channel Identity Table File Path [%s] ? ", idenFilePath.c_str()); - ::fflush(stdout); - - m_console.getLine(value, 128, 0); - idenFilePath = std::string(value); - if (idenFilePath.length() > 0) { - m_conf["system"]["iden_table"]["file"] = idenFilePath; - } - - ::fprintf(stdout, "> RID ACL Table File Path [%s] ? ", ridFilePath.c_str()); - ::fflush(stdout); - - m_console.getLine(value, 128, 0); - ridFilePath = std::string(value); - if (ridFilePath.length() > 0) { - m_conf["system"]["radio_id"]["file"] = ridFilePath; - } - - ::fprintf(stdout, "> TGID ACL Table File Path [%s] ? ", tgidFilePath.c_str()); - ::fflush(stdout); + } - m_console.getLine(value, 128, 0); - tgidFilePath = std::string(value); - if (tgidFilePath.length() > 0) { - m_conf["system"]["talkgroup_id"]["file"] = tgidFilePath; - } + ret = writeConfig(); + if (!ret) { + ret = writeConfig(); + if (!ret) { + LogError(LOG_MODEM, "Modem unresponsive to configuration set after 2 attempts. Stopping."); + m_modem->close(); + m_isConnected = false; + return false; } - break; - - case 'M': - { - modemConfig = m_conf["system"]["modem"]; - m_conf["system"]["modem"]["protocol"]["type"] = std::string("uart"); // configuring modem, always sets type to UART - - yaml::Node uartConfig = modemConfig["protocol"]["uart"]; - - std::string modemPort = uartConfig["port"].as("/dev/ttyUSB0"); - uint32_t portSpeed = uartConfig["speed"].as(115200); - - char value[21] = { '\0' }; - ::fprintf(stdout, "> Modem UART Port [%s] ? ", modemPort.c_str()); - ::fflush(stdout); - - m_console.getLine(value, 21, 0); - if (value[0] != '\0') { - modemPort = std::string(value); - m_conf["system"]["modem"]["protocol"]["uart"]["port"] = modemPort; - } + } - ::fprintf(stdout, "> Port Speed [%u] ? ", portSpeed); - ::fflush(stdout); + LogMessage(LOG_MODEM, "Modem Ready [Calibration Mode]"); - m_console.getLine(value, 7, 0); - if (value[0] != '\0') { - sscanf(value, "%u", &portSpeed); - m_conf["system"]["modem"]["protocol"]["uart"]["speed"] = __INT_STR(portSpeed); - } + // handled modem open + return true; +} - printStatus(); - } - break; +/// +/// +/// +/// +bool HostSetup::portModemClose(Modem* modem) +{ + // handled modem close + return true; +} - case 's': +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspType, bool rspDblLen, const uint8_t* buffer, uint16_t len) +{ + switch (m_mode) { + case STATE_P25: { - std::string identity = m_conf["system"]["identity"].as(); - uint32_t timeout = m_conf["system"]["timeout"].as(); - bool duplex = m_conf["system"]["duplex"].as(true); - bool simplexSameFrequency = m_conf["system"]["simplexSameFrequency"].as(false); - uint32_t modeHang = m_conf["system"]["modeHang"].as(); - uint32_t rfTalkgroupHang = m_conf["system"]["rfTalkgroupHang"].as(); - bool fixedMode = m_conf["system"]["fixedMode"].as(false); - - char value[9] = { '\0' }; - ::fprintf(stdout, "> Identity [%s] ? ", identity.c_str()); - ::fflush(stdout); - - m_console.getLine(value, 9, 0); - identity = std::string(value); - if (identity.length() > 0) { - m_conf["system"]["identity"] = identity; - } - - ::fprintf(stdout, "> Duplex Enabled [%u] (Y/N) ? ", duplex); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { - duplex = value[0] == 'Y' ? true : false; - } - - m_conf["system"]["duplex"] = __BOOL_STR(duplex); - - ::fprintf(stdout, "> Simplex Frequency [%u] (Y/N) ? ", simplexSameFrequency); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { - duplex = value[0] == 'Y' ? true : false; - } - - m_conf["system"]["simplexSameFrequency"] = __BOOL_STR(simplexSameFrequency); - - ::fprintf(stdout, "> Timeout [%u] ? ", timeout); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - sscanf(value, "%u", &timeout); - m_conf["system"]["timeout"] = __INT_STR(timeout); - } - - ::fprintf(stdout, "> Mode Hangtime [%u] ? ", modeHang); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - sscanf(value, "%u", &modeHang); - m_conf["system"]["modeHang"] = __INT_STR(modeHang); - } + if (m_transmit) { + uint8_t dataLen = 0U; + m_queue.peek(&dataLen, 1U); - ::fprintf(stdout, "> RF Talkgroup Hangtime [%u] ? ", rfTalkgroupHang); - ::fflush(stdout); + if (!m_queue.isEmpty() && m_modem->m_p25Space >= dataLen) { + uint8_t data[p25::P25_LDU_FRAME_LENGTH_BYTES + 2U]; - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - sscanf(value, "%u", &rfTalkgroupHang); - m_conf["system"]["rfTalkgroupHang"] = __INT_STR(rfTalkgroupHang); - } - - ::fprintf(stdout, "> Fixed Mode [%u] (Y/N) ? ", fixedMode); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { - fixedMode = value[0] == 'Y' ? true : false; - } - - m_conf["system"]["fixedMode"] = __BOOL_STR(fixedMode); - -#if defined(ENABLE_DMR) - bool dmrEnabled = m_conf["protocols"]["dmr"]["enable"].as(true); - - ::fprintf(stdout, "> DMR Enabled [%u] (Y/N) ? ", dmrEnabled); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { - dmrEnabled = value[0] == 'Y' ? true : false; - } + dataLen = 0U; + m_queue.getData(&dataLen, 1U); + m_queue.getData(data, dataLen); - m_conf["protocols"]["dmr"]["enable"] = __BOOL_STR(dmrEnabled); -#endif // defined(ENABLE_DMR) -#if defined(ENABLE_P25) - bool p25Enabled = m_conf["protocols"]["p25"]["enable"].as(true); + uint8_t buffer[250U]; - ::fprintf(stdout, "> P25 Enabled [%u] (Y/N) ? ", p25Enabled); - ::fflush(stdout); + buffer[0U] = DVM_FRAME_START; + buffer[1U] = dataLen + 2U; + buffer[2U] = CMD_P25_DATA; - m_console.getLine(value, 2, 0); - if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { - p25Enabled = value[0] == 'Y' ? true : false; - } + ::memcpy(buffer + 3U, data + 1U, dataLen - 1U); - m_conf["protocols"]["p25"]["enable"] = __BOOL_STR(p25Enabled); -#endif // defined(ENABLE_P25) -#if defined(ENABLE_NXDN) - bool nxdnEnabled = m_conf["protocols"]["nxdn"]["enable"].as(true); + uint8_t len = dataLen + 2U; - ::fprintf(stdout, "> NXDN Enabled [%u] (Y/N) ? ", nxdnEnabled); - ::fflush(stdout); + int ret = m_modem->write(buffer, len); + if (ret != int(len)) + LogError(LOG_MODEM, "Error writing P25 data"); - m_console.getLine(value, 2, 0); - if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { - nxdnEnabled = value[0] == 'Y' ? true : false; + m_modem->m_p25Space -= dataLen; + } } - - m_conf["protocols"]["nxdn"]["enable"] = __BOOL_STR(nxdnEnabled); -#endif // defined(ENABLE_NXDN) - - printStatus(); } break; - case 'C': - { - cwId = m_conf["system"]["cwId"]; - bool enabled = cwId["enable"].as(false); - uint32_t cwTime = cwId["time"].as(10U); - std::string callsign = cwId["callsign"].as(); - - char value[9] = { '\0' }; - ::fprintf(stdout, "> Callsign [%s] ? ", callsign.c_str()); - ::fflush(stdout); - - m_console.getLine(value, 9, 0); - callsign = std::string(value); - if (callsign.length() > 0) { - m_conf["system"]["cwId"]["callsign"] = callsign; - } - - ::fprintf(stdout, "> CW Enabled [%u] (Y/N) ? ", enabled); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { - enabled = value[0] == 'Y' ? true : false; - } - - m_conf["system"]["cwId"]["enable"] = __BOOL_STR(enabled); - - ::fprintf(stdout, "> CW Interval [%u] (minutes) ? ", cwTime); - ::fflush(stdout); - - m_console.getLine(value, 4, 0); - uint32_t time = cwTime; - sscanf(value, "%u", &time); - if (time > 0) { - m_conf["system"]["cwId"]["time"] = __INT_STR(time); - } + default: + break; + } - printStatus(); + if (rspType == RTM_OK && len > 0) { + switch (buffer[2U]) { + case CMD_CAL_DATA: + { + bool inverted = (buffer[3U] == 0x80U); + short high = buffer[4U] << 8 | buffer[5U]; + short low = buffer[6U] << 8 | buffer[7U]; + short diff = high - low; + short centre = (high + low) / 2; + LogMessage(LOG_CAL, "Levels: inverted: %s, max: %d, min: %d, diff: %d, centre: %d", inverted ? "yes" : "no", high, low, diff, centre); } break; - - case 'N': + case CMD_RSSI_DATA: { - rfssConfig = m_conf["system"]["config"]; - uint32_t siteId = (uint8_t)::strtoul(rfssConfig["siteId"].as("1").c_str(), NULL, 16); - - char value[6] = { '\0' }; - ::fprintf(stdout, "> Site ID [$%02X] ? ", siteId); - ::fflush(stdout); - - m_console.getLine(value, 3, 0); - if (value[0] != '\0') { - siteId = (uint32_t)::strtoul(std::string(value).c_str(), NULL, 16); - siteId = p25::P25Utils::siteId(siteId); - - m_conf["system"]["config"]["siteId"] = __INT_HEX_STR(siteId); - } - -#if defined(ENABLE_DMR) - uint32_t dmrNetId = (uint32_t)::strtoul(rfssConfig["dmrNetId"].as("1").c_str(), NULL, 16); - - ::fprintf(stdout, "> DMR Network ID [$%05X] ? ", dmrNetId); - ::fflush(stdout); - - m_console.getLine(value, 6, 0); - if (value[0] != '\0') { - dmrNetId = (uint32_t)::strtoul(std::string(value).c_str(), NULL, 16); - dmrNetId = dmr::DMRUtils::netId(dmrNetId, dmr::SITE_MODEL_TINY); - - m_conf["system"]["config"]["dmrNetId"] = __INT_HEX_STR(dmrNetId); - } -#else - m_conf["system"]["config"]["dmrNetId"] = __INT_HEX_STR(1U); -#endif // defined(ENABLE_DMR) -#if defined(ENABLE_P25) - uint32_t p25NetId = (uint32_t)::strtoul(rfssConfig["netId"].as("BB800").c_str(), NULL, 16); - uint32_t p25SysId = (uint32_t)::strtoul(rfssConfig["sysId"].as("001").c_str(), NULL, 16); - uint32_t p25RfssId = (uint8_t)::strtoul(rfssConfig["rfssId"].as("1").c_str(), NULL, 16); - - ::fprintf(stdout, "> P25 Network ID [$%05X] ? ", p25NetId); - ::fflush(stdout); - - m_console.getLine(value, 6, 0); - if (value[0] != '\0') { - p25NetId = (uint32_t)::strtoul(std::string(value).c_str(), NULL, 16); - p25NetId = p25::P25Utils::netId(p25NetId); - - m_conf["system"]["config"]["netId"] = __INT_HEX_STR(p25NetId); - } - - ::fprintf(stdout, "> P25 System ID [$%03X] ? ", p25SysId); - ::fflush(stdout); - - m_console.getLine(value, 4, 0); - if (value[0] != '\0') { - p25SysId = (uint32_t)::strtoul(std::string(value).c_str(), NULL, 16); - p25SysId = p25::P25Utils::sysId(p25SysId); - - m_conf["system"]["config"]["sysId"] = __INT_HEX_STR(p25SysId); - } - - ::fprintf(stdout, "> P25 RFSS ID [$%02X] ? ", p25RfssId); - ::fflush(stdout); - - m_console.getLine(value, 3, 0); - if (value[0] != '\0') { - p25RfssId = (uint8_t)::strtoul(std::string(value).c_str(), NULL, 16); - p25RfssId = p25::P25Utils::rfssId(p25RfssId); - - m_conf["system"]["config"]["rfssId"] = __INT_HEX_STR(p25RfssId); - } -#else - m_conf["system"]["config"]["netId"] = __INT_HEX_STR(0xBB800U); - m_conf["system"]["config"]["sysId"] = __INT_HEX_STR(1U); - m_conf["system"]["config"]["rfssId"] = __INT_HEX_STR(1U); -#endif // defined(ENABLE_P25) - - printStatus(); + uint16_t max = buffer[3U] << 8 | buffer[4U]; + uint16_t min = buffer[5U] << 8 | buffer[6U]; + uint16_t ave = buffer[7U] << 8 | buffer[8U]; + LogMessage(LOG_CAL, "RSSI: max: %u, min: %u, ave: %u", max, min, ave); } break; - case 'a': - { - rfssConfig = m_conf["system"]["config"]; - - char value[6] = { '\0' }; - -#if defined(ENABLE_DMR) - uint32_t dmrColorCode = rfssConfig["colorCode"].as(2U); - - ::fprintf(stdout, "> DMR Color Code [%u] ? ", dmrColorCode); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (value[0] != '\0') { - sscanf(value, "%u", &dmrColorCode); - dmrColorCode = dmr::DMRUtils::colorCode(dmrColorCode); - - m_conf["system"]["config"]["colorCode"] = __INT_STR(dmrColorCode); - } -#else - m_conf["system"]["config"]["colorCode"] = __INT_STR(2U); -#endif // defined(ENABLE_DMR) -#if defined(ENABLE_P25) - uint32_t p25NAC = (uint32_t)::strtoul(rfssConfig["nac"].as("293").c_str(), NULL, 16); - - ::fprintf(stdout, "> P25 NAC [$%03X] ? ", p25NAC); - ::fflush(stdout); - - m_console.getLine(value, 4, 0); - if (value[0] != '\0') { - p25NAC = (uint32_t)::strtoul(std::string(value).c_str(), NULL, 16); - p25NAC = p25::P25Utils::nac(p25NAC); - - m_conf["system"]["config"]["nac"] = __INT_HEX_STR(p25NAC); - } -#else - m_conf["system"]["config"]["nac"] = __INT_HEX_STR(1U); -#endif // defined(ENABLE_P25) -#if defined(ENABLE_NXDN) - uint32_t nxdnRAN = rfssConfig["ran"].as(1U); - - ::fprintf(stdout, "> NXDN RAN [%u] ? ", nxdnRAN); - ::fflush(stdout); - - m_console.getLine(value, 2, 0); - if (value[0] != '\0') { - sscanf(value, "%u", &nxdnRAN); + case CMD_DMR_DATA1: + case CMD_DMR_DATA2: + processDMRBER(buffer + 4U, buffer[3]); + break; - m_conf["system"]["config"]["ran"] = __INT_STR(nxdnRAN); + case CMD_DMR_LOST1: + case CMD_DMR_LOST2: + { + LogMessage(LOG_CAL, "DMR Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + + if (m_dmrEnabled) { + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; } -#else - m_conf["system"]["config"]["ran"] = __INT_STR(1U); -#endif // defined(ENABLE_NXDN) - - printStatus(); } break; - case 'i': - { - rfssConfig = m_conf["system"]["config"]; - m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); - - char value[3] = { '\0' }; - ::fprintf(stdout, "> Channel ID [%u] ? ", m_channelId); - ::fflush(stdout); - - m_console.getLine(value, 3, 0); - if (value[0] != '\0') { - uint8_t prevChannelId = m_channelId; - - // bryanb: appease the compiler... - uint32_t channelId = m_channelId; - sscanf(value, "%u", &channelId); - - m_channelId = (uint8_t)channelId; - - IdenTable entry = m_idenTable->find(m_channelId); - if (entry.baseFrequency() == 0U) { - ::LogError(LOG_SETUP, "Channel Id %u has an invalid base frequency.", m_channelId); - m_channelId = prevChannelId; - } + case CMD_P25_DATA: + processP25BER(buffer + 3U); + break; - m_conf["system"]["config"]["channelId"] = __INT_STR(m_channelId); + case CMD_P25_LOST: + { + LogMessage(LOG_CAL, "P25 Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + + if (m_p25Enabled) { + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; } - - printStatus(); } break; - case 'c': - { - rfssConfig = m_conf["system"]["config"]; - m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); - - char value[5] = { '\0' }; - ::fprintf(stdout, "> Channel No [%u] ? ", m_channelNo); - ::fflush(stdout); - - m_console.getLine(value, 5, 0); - if (value[0] != '\0') { - uint8_t prevChannelNo = m_channelNo; - sscanf(value, "%u", &m_channelNo); - - if (m_channelNo < 0 || m_channelNo > 4096) { - ::LogError(LOG_SETUP, "Channel No %u is invalid.", m_channelNo); - m_channelNo = prevChannelNo; - } + case CMD_NXDN_DATA: + processNXDNBER(buffer + 3U); + break; - m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_channelNo); + case CMD_NXDN_LOST: + { + LogMessage(LOG_CAL, "NXDN Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + + if (m_nxdnEnabled) { + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; } - - printStatus(); } break; - case 'f': + case CMD_GET_STATUS: { - rfssConfig = m_conf["system"]["config"]; - m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); + m_isHotspot = (buffer[3U] & 0x01U) == 0x01U; + m_modem->m_isHotspot = (buffer[3U] & 0x01U) == 0x01U; - char value[10] = { '\0' }; - ::fprintf(stdout, "> Tx Frequency [%uHz] (Hz) ? ", m_txFrequency); - ::fflush(stdout); - - m_console.getLine(value, 10, 0); - if (value[0] != '\0') { - uint32_t txFrequency = m_txFrequency; - sscanf(value, "%u", &txFrequency); - - IdenTable entry = m_idenTable->find(m_channelId); - if (txFrequency < entry.baseFrequency()) { - ::LogError(LOG_SETUP, "Tx Frequency %uHz is out of band range for base frequency %uHz. Tx Frequency must be greater then base frequency!", txFrequency, entry.baseFrequency()); - break; - } - - if (txFrequency > entry.baseFrequency() + 25500000) { - ::LogError(LOG_SETUP, "Tx Frequency %uHz is out of band range for base frequency %uHz. Tx Frequency must be no more then 25.5 Mhz higher then base frequency!", txFrequency, entry.baseFrequency()); - break; - } + // override hotspot flag if we're forcing hotspot + if (m_modem->m_forceHotspot) { + m_isHotspot = m_modem->m_forceHotspot; + m_modem->m_isHotspot = m_modem->m_forceHotspot; + } - uint32_t prevTxFrequency = m_txFrequency; - m_txFrequency = txFrequency; - uint32_t prevRxFrequency = m_rxFrequency; - m_rxFrequency = m_txFrequency + (uint32_t)(entry.txOffsetMhz() * 1000000); + uint8_t modemState = buffer[4U]; - float spaceHz = entry.chSpaceKhz() * 1000; + bool tx = (buffer[5U] & 0x01U) == 0x01U; - uint32_t rootFreq = m_txFrequency - entry.baseFrequency(); - uint8_t prevChannelNo = m_channelNo; - m_channelNo = (uint32_t)(rootFreq / spaceHz); + bool adcOverflow = (buffer[5U] & 0x02U) == 0x02U; + bool rxOverflow = (buffer[5U] & 0x04U) == 0x04U; + bool txOverflow = (buffer[5U] & 0x08U) == 0x08U; + bool dacOverflow = (buffer[5U] & 0x20U) == 0x20U; - if (m_channelNo < 0 || m_channelNo > 4096) { - ::LogError(LOG_SETUP, "Channel No %u is invalid.", m_channelNo); - m_channelNo = prevChannelNo; - m_txFrequency = prevTxFrequency; - m_rxFrequency = prevRxFrequency; - break; - } + // spaces from the modem are returned in "logical" frame count, not raw byte size + m_modem->m_dmrSpace1 = buffer[7U] * (dmr::DMR_FRAME_LENGTH_BYTES + 2U); + m_modem->m_dmrSpace2 = buffer[8U] * (dmr::DMR_FRAME_LENGTH_BYTES + 2U); + m_modem->m_p25Space = buffer[10U] * (p25::P25_LDU_FRAME_LENGTH_BYTES); + m_modem->m_nxdnSpace = buffer[11U] * (nxdn::NXDN_FRAME_LENGTH_BYTES); - m_conf["system"]["config"]["channelNo"] = __INT_HEX_STR(m_channelNo); + if (m_hasFetchedStatus && m_requestedStatus) { + LogMessage(LOG_CAL, "Diagnostic Values [Modem State: %u, Transmitting: %d, ADC Overflow: %d, Rx Overflow: %d, Tx Overflow: %d, DAC Overflow: %d, HS: %u]", + modemState, tx, adcOverflow, rxOverflow, txOverflow, dacOverflow, m_isHotspot); } - printStatus(); + m_hasFetchedStatus = true; + m_requestedStatus = false; } break; - case '!': + case CMD_FLSH_READ: { - modemConfig = m_conf["system"]["modem"]; - m_conf["system"]["modem"]["protocol"]["type"] = std::string("null"); // configuring modem, always sets type to UART - printStatus(); + uint8_t len = buffer[1U]; + if (m_debug) { + Utils::dump(1U, "Modem Flash Contents", buffer + 3U, len - 3U); + } + if (len == 249U) { + bool ret = edac::CRC::checkCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); + if (!ret) { + LogWarning(LOG_CAL, "HostSetup::portModemHandler(), clearing modem configuration area; first setup?"); + eraseFlash(); + } + else { + bool isErased = (buffer[DVM_CONF_AREA_LEN] & 0x80U) == 0x80U; + uint8_t confAreaVersion = buffer[DVM_CONF_AREA_LEN] & 0x7FU; + + if (!isErased) { + if (confAreaVersion != DVM_CONF_AREA_VER) { + LogError(LOG_MODEM, "HostSetup::portModemHandler(), invalid version for configuration area, %02X != %02X", DVM_CONF_AREA_VER, confAreaVersion); + } + else { + processFlashConfig(buffer); + + // reset update config flag if its set + if (m_updateConfigFromModem) { + m_updateConfigFromModem = false; + } + } + } + else { + LogWarning(LOG_MODEM, "HostSetup::portModemHandler(), modem configuration area was erased and does not contain active configuration!"); + } + } + } + else { + LogWarning(LOG_MODEM, "Incorrect length for configuration area! Ignoring."); + } } break; - /** General Commands */ - case '`': - printStatus(); - break; - case 'V': - getHostVersion(); + case CMD_GET_VERSION: + case CMD_ACK: break; - case 'H': - case 'h': - displayHelp(); - break; - case 'S': - { - yaml::Serialize(m_conf, m_confFile.c_str(), yaml::SerializeConfig(4, 64, false, false)); - LogMessage(LOG_SETUP, " - Saved configuration to %s", m_confFile.c_str()); - } - break; - case 'Q': - case 'q': - end = true; + + case CMD_NAK: + LogWarning(LOG_CAL, "NAK, command = 0x%02X (%s), reason = %u (%s)", buffer[3U], + m_modem->cmdToString(buffer[3U]).c_str(), buffer[4U], m_modem->rsnToString(buffer[4U]).c_str()); break; - case 13: - case 10: - case -1: + case CMD_DEBUG1: + case CMD_DEBUG2: + case CMD_DEBUG3: + case CMD_DEBUG4: + case CMD_DEBUG5: + case CMD_DEBUG_DUMP: + m_modem->printDebug(buffer, len); break; + default: - LogError(LOG_SETUP, "Unknown command - %c (H/h for help)", c); + LogWarning(LOG_CAL, "Unknown message, type = %02X", buffer[2U]); + Utils::dump("Buffer dump", buffer, len); break; } - - sleep(5U); } - m_console.close(); - return 0; + // handled modem response + return true; } -// --------------------------------------------------------------------------- -// Private Class Members -// --------------------------------------------------------------------------- - /// -/// Helper to print the help to the console. +/// Helper to save configuration. /// -void HostSetup::displayHelp() +void HostSetup::saveConfig() { - LogMessage(LOG_SETUP, "General Commands:"); - LogMessage(LOG_SETUP, " ` Display current settings"); - LogMessage(LOG_SETUP, " V Display version of host"); - LogMessage(LOG_SETUP, " H/h Display help"); - LogMessage(LOG_SETUP, " ! Set \"null\" modem (disables modem communication)"); - LogMessage(LOG_SETUP, " S Save settings to configuration file"); - LogMessage(LOG_SETUP, " Q/q Quit"); - LogMessage(LOG_SETUP, "Setup Commands:"); - LogMessage(LOG_SETUP, " L Set DVM logging configuration"); - LogMessage(LOG_SETUP, " F Set DVM data file paths"); - LogMessage(LOG_SETUP, " M Set modem port and speed"); - LogMessage(LOG_SETUP, " s Set system configuration"); - LogMessage(LOG_SETUP, " C Set callsign and CW configuration"); - LogMessage(LOG_SETUP, " N Set site and network configuration"); - LogMessage(LOG_SETUP, " a Set NAC, Color Code and RAN"); - LogMessage(LOG_SETUP, " i Set logical channel ID"); - LogMessage(LOG_SETUP, " c Set logical channel number (by channel number)"); - LogMessage(LOG_SETUP, " f Set logical channel number (by Tx frequency)"); + if (m_isConnected) { + if (m_transmit) { + setTransmit(); + } + + writeConfig(); + writeRFParams(); + writeSymbolAdjust(); + } + + yaml::Serialize(m_conf, m_confFile.c_str(), yaml::SerializeConfig(4, 64, false, false)); + LogMessage(LOG_CAL, " - Saved configuration to %s", m_confFile.c_str()); + if (m_isConnected) { + if (writeFlash()) { + LogMessage(LOG_CAL, " - Wrote configuration area on modem"); + } + } } /// /// Helper to calculate the Rx/Tx frequencies. /// -bool HostSetup::calculateRxTxFreq() +/// +/// +bool HostSetup::calculateRxTxFreq(bool consoleDisplay) { IdenTable entry = m_idenTable->find(m_channelId); if (entry.baseFrequency() == 0U) { + if (consoleDisplay) { + g_logDisplayLevel = 1U; + } + ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); return false; } + yaml::Node systemConf = m_conf["system"]; + yaml::Node rfssConfig = systemConf["config"]; + m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); if (m_channelNo == 0U) { // clamp to 1 m_channelNo = 1U; } @@ -771,6 +598,10 @@ bool HostSetup::calculateRxTxFreq() if (m_duplex) { if (entry.txOffsetMhz() == 0U) { + if (consoleDisplay) { + g_logDisplayLevel = 1U; + } + ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId); return false; } @@ -792,78 +623,1543 @@ bool HostSetup::calculateRxTxFreq() } /// -/// Helper to sleep the thread. +/// Helper to log the system configuration parameters. /// -/// Milliseconds to sleep. -void HostSetup::sleep(uint32_t ms) +void HostSetup::displayConfigParams() { -#if defined(_WIN32) || defined(_WIN64) - ::Sleep(ms); -#else - ::usleep(ms * 1000); -#endif + IdenTable entry = m_idenTable->find(m_channelId); + LogInfo("System Config Parameters"); + LogInfo(" RX Frequency: %uHz", m_rxFrequency); + LogInfo(" TX Frequency: %uHz", m_txFrequency); + LogInfo(" Base Frequency: %uHz", entry.baseFrequency()); + LogInfo(" TX Offset: %fMHz", entry.txOffsetMhz()); } /// -/// Prints the current status of the calibration. +/// Initializes the modem DSP. /// -void HostSetup::printStatus() +/// +/// +bool HostSetup::createModem(bool consoleDisplay) { + if (g_remoteModemMode) { + if (consoleDisplay) { + g_logDisplayLevel = 1U; + } + + ::LogError(LOG_HOST, "Setup mode is unsupported with a remote modem!"); + return false; + } +#if defined(ENABLE_SETUP_TUI) + if (m_isConnected) { + if (m_setupWnd != nullptr) { + FMessageBox::error(m_setupWnd, "Cannot change modem configuration while connected!"); + } + return false; + } +#endif // defined(ENABLE_SETUP_TUI) + + displayConfigParams(); + yaml::Node systemConf = m_conf["system"]; - { - yaml::Node modemConfig = m_conf["system"]["modem"]; - std::string type = modemConfig["protocol"]["type"].as(); + yaml::Node modemConf = systemConf["modem"]; - yaml::Node uartConfig = modemConfig["protocol"]["uart"]; - std::string modemPort = uartConfig["port"].as(); - uint32_t portSpeed = uartConfig["speed"].as(115200U); + bool rxInvert = modemConf["rxInvert"].as(false); + bool txInvert = modemConf["txInvert"].as(false); + bool pttInvert = modemConf["pttInvert"].as(false); + bool dcBlocker = modemConf["dcBlocker"].as(true); - LogMessage(LOG_SETUP, " - Port Type: %s, Modem Port: %s, Port Speed: %u", type.c_str(), modemPort.c_str(), portSpeed); - } + int rxDCOffset = modemConf["rxDCOffset"].as(0); + int txDCOffset = modemConf["txDCOffset"].as(0); + float rxLevel = modemConf["rxLevel"].as(50.0F); + float txLevel = modemConf["txLevel"].as(50.0F); - { - std::string identity = systemConf["identity"].as(); - uint32_t timeout = systemConf["timeout"].as(); - bool duplex = systemConf["duplex"].as(true); - bool simplexSameFrequency = systemConf["simplexSameFrequency"].as(false); - uint32_t modeHang = systemConf["modeHang"].as(); - uint32_t rfTalkgroupHang = systemConf["rfTalkgroupHang"].as(); - bool fixedMode = systemConf["fixedMode"].as(false); - bool dmrEnabled = m_conf["protocols"]["dmr"]["enable"].as(true); - bool p25Enabled = m_conf["protocols"]["p25"]["enable"].as(true); + yaml::Node hotspotParams = modemConf["hotspot"]; - IdenTable entry = m_idenTable->find(m_channelId); - if (entry.baseFrequency() == 0U) { - ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); - } + int dmrDiscBWAdj = hotspotParams["dmrDiscBWAdj"].as(0); + int p25DiscBWAdj = hotspotParams["p25DiscBWAdj"].as(0); + int nxdnDiscBWAdj = hotspotParams["nxdnDiscBWAdj"].as(0); + int dmrPostBWAdj = hotspotParams["dmrPostBWAdj"].as(0); + int p25PostBWAdj = hotspotParams["p25PostBWAdj"].as(0); + int nxdnPostBWAdj = hotspotParams["nxdnPostBWAdj"].as(0); - calculateRxTxFreq(); + ADF_GAIN_MODE adfGainMode = (ADF_GAIN_MODE)hotspotParams["adfGainMode"].as(0U); - LogMessage(LOG_SETUP, " - Identity: %s, Duplex: %u, Simplex Frequency: %u, Timeout: %u", identity.c_str(), duplex, simplexSameFrequency, timeout); - LogMessage(LOG_SETUP, " - Mode Hang: %u, RF Talkgroup Hang: %u, Fixed Mode: %u, DMR Enabled: %u, P25 Enabled: %u", modeHang, rfTalkgroupHang, fixedMode, dmrEnabled, p25Enabled); - LogMessage(LOG_SETUP, " - Channel Id: %u, Channel No: %u", m_channelId, m_channelNo); - LogMessage(LOG_SETUP, " - Base Freq: %uHz, TX Offset: %fMHz, Bandwidth: %fKHz, Channel Spacing: %fKHz", entry.baseFrequency(), entry.txOffsetMhz(), entry.chBandwidthKhz(), entry.chSpaceKhz()); - LogMessage(LOG_SETUP, " - Rx Freq: %uHz, Tx Freq: %uHz", m_rxFrequency, m_txFrequency); - } + bool afcEnable = hotspotParams["afcEnable"].as(false); + uint8_t afcKI = (uint8_t)hotspotParams["afcKI"].as(11U); + uint8_t afcKP = (uint8_t)hotspotParams["afcKP"].as(4U); + uint8_t afcRange = (uint8_t)hotspotParams["afcRange"].as(1U); - { + int rxTuning = hotspotParams["rxTuning"].as(0); + int txTuning = hotspotParams["txTuning"].as(0); + + // apply the frequency tuning offsets + m_rxAdjustedFreq = m_rxFrequency + rxTuning; + m_txAdjustedFreq = m_txFrequency + txTuning; + + yaml::Node repeaterParams = modemConf["repeater"]; + + int dmrSymLevel3Adj = repeaterParams["dmrSymLvl3Adj"].as(0); + int dmrSymLevel1Adj = repeaterParams["dmrSymLvl1Adj"].as(0); + int p25SymLevel3Adj = repeaterParams["p25SymLvl3Adj"].as(0); + int p25SymLevel1Adj = repeaterParams["p25SymLvl1Adj"].as(0); + int nxdnSymLevel3Adj = repeaterParams["nxdnSymLvl3Adj"].as(0); + int nxdnSymLevel1Adj = repeaterParams["nxdnSymLvl1Adj"].as(0); + + yaml::Node softpotParams = modemConf["softpot"]; + + uint8_t rxCoarsePot = (uint8_t)softpotParams["rxCoarse"].as(127U); + uint8_t rxFinePot = (uint8_t)softpotParams["rxFine"].as(127U); + uint8_t txCoarsePot = (uint8_t)softpotParams["txCoarse"].as(127U); + uint8_t txFinePot = (uint8_t)softpotParams["txFine"].as(127U); + uint8_t rssiCoarsePot = (uint8_t)softpotParams["rssiCoarse"].as(127U); + uint8_t rssiFinePot = (uint8_t)softpotParams["rssiFine"].as(127U); + + uint16_t dmrFifoLength = (uint16_t)modemConf["dmrFifoLength"].as(DMR_TX_BUFFER_LEN); + uint16_t p25FifoLength = (uint16_t)modemConf["p25FifoLength"].as(P25_TX_BUFFER_LEN); + uint16_t nxdnFifoLength = (uint16_t)modemConf["nxdnFifoLength"].as(NXDN_TX_BUFFER_LEN); + + uint8_t fdmaPreamble = (uint8_t)modemConf["fdmaPreamble"].as(80U); + uint8_t dmrRxDelay = (uint8_t)modemConf["dmrRxDelay"].as(7U); + uint8_t p25CorrCount = (uint8_t)modemConf["p25CorrCount"].as(5U); + + bool ignoreModemConfigArea = modemConf["ignoreModemConfigArea"].as(false); + + yaml::Node modemProtocol = modemConf["protocol"]; + std::string portType = modemProtocol["type"].as("null"); + + yaml::Node uartProtocol = modemProtocol["uart"]; + std::string uartPort = uartProtocol["port"].as(); + uint32_t uartSpeed = uartProtocol["speed"].as(115200); + + // delete existing modem instance if necessary + if (m_modem != nullptr) { + delete m_modem; + } + + LogInfo("Modem Parameters"); + LogInfo(" Port Type: %s", portType.c_str()); + + port::IModemPort* modemPort = nullptr; + std::transform(portType.begin(), portType.end(), portType.begin(), ::tolower); + if (portType == NULL_PORT) { + modemPort = new port::ModemNullPort(); + } + else if (portType == UART_PORT) { + port::SERIAL_SPEED serialSpeed = port::SERIAL_115200; + switch (uartSpeed) { + case 1200: + serialSpeed = port::SERIAL_1200; + break; + case 2400: + serialSpeed = port::SERIAL_2400; + break; + case 4800: + serialSpeed = port::SERIAL_4800; + break; + case 9600: + serialSpeed = port::SERIAL_9600; + break; + case 19200: + serialSpeed = port::SERIAL_19200; + break; + case 38400: + serialSpeed = port::SERIAL_38400; + break; + case 76800: + serialSpeed = port::SERIAL_76800; + break; + case 230400: + serialSpeed = port::SERIAL_230400; + break; + case 460800: + serialSpeed = port::SERIAL_460800; + break; + default: + LogWarning(LOG_HOST, "Unsupported serial speed %u, defaulting to %u", uartSpeed, port::SERIAL_115200); + uartSpeed = 115200; + case 115200: + break; + } + + modemPort = new port::UARTPort(uartPort, serialSpeed, true); + LogInfo(" UART Port: %s", uartPort.c_str()); + LogInfo(" UART Speed: %u", uartSpeed); + } + + LogInfo(" RX Invert: %s", rxInvert ? "yes" : "no"); + LogInfo(" TX Invert: %s", txInvert ? "yes" : "no"); + LogInfo(" PTT Invert: %s", pttInvert ? "yes" : "no"); + LogInfo(" DC Blocker: %s", dcBlocker ? "yes" : "no"); + LogInfo(" FDMA Preambles: %u (%.1fms)", fdmaPreamble, float(fdmaPreamble) * 0.2083F); + LogInfo(" DMR RX Delay: %u (%.1fms)", dmrRxDelay, float(dmrRxDelay) * 0.0416666F); + LogInfo(" P25 Corr. Count: %u (%.1fms)", p25CorrCount, float(p25CorrCount) * 0.667F); + LogInfo(" RX DC Offset: %d", rxDCOffset); + LogInfo(" TX DC Offset: %d", txDCOffset); + LogInfo(" RX Tuning Offset: %dhz", rxTuning); + LogInfo(" TX Tuning Offset: %dhz", txTuning); + LogInfo(" RX Effective Frequency: %uhz", m_rxAdjustedFreq); + LogInfo(" TX Effective Frequency: %uhz", m_txAdjustedFreq); + LogInfo(" RX Coarse: %u, Fine: %u", rxCoarsePot, rxFinePot); + LogInfo(" TX Coarse: %u, Fine: %u", txCoarsePot, txFinePot); + LogInfo(" RSSI Coarse: %u, Fine: %u", rssiCoarsePot, rssiFinePot); + LogInfo(" RX Level: %.1f%%", rxLevel); + LogInfo(" TX Level: %.1f%%", txLevel); + LogInfo(" DMR FIFO Size: %u bytes", dmrFifoLength); + LogInfo(" P25 FIFO Size: %u bytes", p25FifoLength); + LogInfo(" NXDN FIFO Size: %u bytes", nxdnFifoLength); + + if (ignoreModemConfigArea) { + LogInfo(" Ignore Modem Configuration Area: yes"); + } + + if (modemPort == nullptr) { + if (consoleDisplay) { + g_logDisplayLevel = 1U; + } + + ::LogError(LOG_HOST, "Invalid modem port type, %s!", portType.c_str()); + return false; + } + + m_modem = new Modem(modemPort, false, rxInvert, txInvert, pttInvert, dcBlocker, false, fdmaPreamble, dmrRxDelay, p25CorrCount, 3960U, 2592U, 1488U, false, ignoreModemConfigArea, false, false, false); + m_modem->setLevels(rxLevel, txLevel, txLevel, txLevel, txLevel); + m_modem->setSymbolAdjust(dmrSymLevel3Adj, dmrSymLevel1Adj, p25SymLevel3Adj, p25SymLevel1Adj, nxdnSymLevel3Adj, nxdnSymLevel1Adj); + m_modem->setDCOffsetParams(txDCOffset, rxDCOffset); + m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, 100U, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, p25PostBWAdj, nxdnPostBWAdj, adfGainMode, + afcEnable, afcKI, afcKP, afcRange); + m_modem->setSoftPot(rxCoarsePot, rxFinePot, txCoarsePot, txFinePot, rssiCoarsePot, rssiFinePot); + + m_modem->setOpenHandler(MODEM_OC_PORT_HANDLER_BIND(HostSetup::portModemOpen, this)); + m_modem->setCloseHandler(MODEM_OC_PORT_HANDLER_BIND(HostSetup::portModemClose, this)); + m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(HostSetup::portModemHandler, this)); + + m_modem->m_dmrFifoLength = dmrFifoLength; + m_modem->m_p25FifoLength = p25FifoLength; + m_modem->m_nxdnFifoLength = nxdnFifoLength; + + return true; +} + +/// +/// Helper to toggle modem transmit mode. +/// +/// True, if setting was applied, otherwise false. +bool HostSetup::setTransmit() +{ + if (m_dmrEnabled || (m_p25Enabled && !m_p25TduTest) || m_nxdnEnabled) { + LogError(LOG_CAL, "No transmit allowed in a BER Test mode"); + return false; + } + + m_transmit = !m_transmit; + + if (m_p25Enabled && m_p25TduTest) { + if (m_transmit) + LogMessage(LOG_CAL, " - Modem start transmitting"); + else + LogMessage(LOG_CAL, " - Modem stop transmitting"); + + m_modem->clock(0U); + return true; + } + + uint8_t buffer[50U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = CMD_CAL_DATA; + buffer[3U] = m_transmit ? 0x01U : 0x00U; + + int ret = m_modem->write(buffer, 4U); + if (ret <= 0) + return false; + + sleep(25U); + + if (m_transmit) + LogMessage(LOG_CAL, " - Modem start transmitting"); + else + LogMessage(LOG_CAL, " - Modem stop transmitting"); + + m_modem->clock(0U); + + return true; +} + +/// +/// Helper to update BER display window. +/// +/// Bit Error Rate Percentage +void HostSetup::updateTUIBER(float ber) +{ +#if defined(ENABLE_SETUP_TUI) + if (m_setupWnd == nullptr) { + return; + } + + // handle displaying TUI + std::stringstream ss; + ss << std::fixed << std::setprecision(3) << ber; + if (ber < 5.0F) { + m_setupWnd->m_berWnd.segmentColor(finalcut::FColor::LightGreen3); + } + else if (ber > 5.0F && ber < 8.0F) { + m_setupWnd->m_berWnd.segmentColor(finalcut::FColor::LightGoldenrod3); + } + else { + m_setupWnd->m_berWnd.segmentColor(finalcut::FColor::LightRed); + } + + m_setupWnd->m_berWnd.ber(ss.str()); +#endif // defined(ENABLE_SETUP_TUI) +} + +/// +/// Process DMR Rx BER. +/// +/// Buffer containing DMR data +/// DMR Audio Sequence +void HostSetup::processDMRBER(const uint8_t* buffer, uint8_t seq) +{ + if (seq == 65U) { + timerStart(); + + LogMessage(LOG_CAL, "DMR voice header received"); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + else if (seq == 66U) { + if (m_berFrames != 0U) { + LogMessage(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + } + + timerStop(); + + // handle displaying TUI + float ber = (float)(float(m_berErrs * 100U) / float(m_berBits)); + updateTUIBER(ber); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + + timerStart(); + + uint32_t errs = m_fec.measureDMRBER(buffer); + + float ber = float(errs) / 1.41F; + if (ber < 10.0F) + LogMessage(LOG_CAL, "DMR audio seq. %d, FEC BER %% (errs): %.3f%% (%u/141)", seq & 0x0FU, ber, errs); + else { + LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); + m_berUncorrectable++; + } + + // handle displaying TUI + updateTUIBER(ber); + + m_berBits += 141U; + m_berErrs += errs; + m_berFrames++; +} + +/// +/// Process DMR Rx 1011hz BER. +/// +/// Buffer containing DMR data +/// DMR Audio Sequence +void HostSetup::processDMR1KBER(const uint8_t* buffer, uint8_t seq) +{ + uint32_t errs = 0U; + + if (seq == 65U) { + timerStart(); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + + for (uint32_t i = 0U; i < 33U; i++) + errs += countErrs(buffer[i], VH_DMO1K[i]); + + m_berErrs += errs; + m_berBits += 264; + m_berFrames++; + LogMessage(LOG_CAL, "DMR voice header received, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", float(errs) / 2.64F, errs); + } + else if (seq == 66U) { + for (uint32_t i = 0U; i < 33U; i++) + errs += countErrs(buffer[i], VT_DMO1K[i]); + + m_berErrs += errs; + m_berBits += 264; + m_berFrames++; + + if (m_berFrames != 0U) { + LogMessage(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + } + + timerStop(); + + // handle displaying TUI + float ber = (float)(float(m_berErrs * 100U) / float(m_berBits)); + updateTUIBER(ber); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + + timerStart(); + + uint8_t dmrSeq = seq & 0x0FU; + if (dmrSeq > 5U) + dmrSeq = 5U; + + errs = 0U; + for (uint32_t i = 0U; i < 33U; i++) + errs += countErrs(buffer[i], VOICE_1K[i]); + + float ber = float(errs) / 2.64F; + + m_berErrs += errs; + m_berBits += 264; + m_berFrames++; + + if (ber < 10.0F) + LogMessage(LOG_CAL, "DMR audio seq. %d, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", seq & 0x0FU, ber, errs); + else { + LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); + m_berUncorrectable++; + } + + // handle displaying TUI + updateTUIBER(ber); +} + +/// +/// Process P25 Rx BER. +/// +/// Buffer containing P25 data +void HostSetup::processP25BER(const uint8_t* buffer) +{ + using namespace p25; + + uint8_t sync[P25_SYNC_LENGTH_BYTES]; + ::memcpy(sync, buffer + 1U, P25_SYNC_LENGTH_BYTES); + + uint8_t syncErrs = 0U; + for (uint8_t i = 0U; i < P25_SYNC_LENGTH_BYTES; i++) + syncErrs += Utils::countBits8(sync[i] ^ P25_SYNC_BYTES[i]); + + LogMessage(LOG_CAL, "P25, sync word, errs = %u, sync word = %02X %02X %02X %02X %02X %02X", syncErrs, + sync[0U], sync[1U], sync[2U], sync[3U], sync[4U], sync[5U]); + + uint8_t nid[P25_NID_LENGTH_BYTES]; + P25Utils::decode(buffer + 1U, nid, 48U, 114U); + uint8_t duid = nid[1U] & 0x0FU; + + uint32_t errs = 0U; + uint8_t imbe[18U]; + lc::LC lc = lc::LC(); + data::DataHeader dataHeader = data::DataHeader(); + + if (duid == P25_DUID_HDU) { + timerStart(); + + bool ret = lc.decodeHDU(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, P25_HDU_STR ", undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); + } + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + } + else if (duid == P25_DUID_TDU) { + if (m_berFrames != 0U) { + LogMessage(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + } + + timerStop(); + + // handle displaying TUI + float ber = (float)(float(m_berErrs * 100U) / float(m_berBits)); + updateTUIBER(ber); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + else if (duid == P25_DUID_LDU1) { + timerStart(); + + bool ret = lc.decodeLDU1(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, P25_LDU1_STR ", undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", + lc.getMFId(), lc.getLCO(), lc.getEmergency(), lc.getEncrypted(), lc.getPriority(), lc.getGroup(), lc.getSrcId(), lc.getDstId()); + } + + P25Utils::decode(buffer + 1U, imbe, 114U, 262U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 262U, 410U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 452U, 600U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 640U, 788U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 830U, 978U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1020U, 1168U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1208U, 1356U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1398U, 1546U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1578U, 1726U); + errs += m_fec.measureP25BER(imbe); + + float ber = float(errs) / 12.33F; + if (ber < 10.0F) + LogMessage(LOG_CAL, P25_LDU1_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); + else { + LogWarning(LOG_CAL, P25_LDU1_STR ", uncorrectable audio"); + m_berUncorrectable++; + } + + // handle displaying TUI + updateTUIBER(ber); + + m_berBits += 1233U; + m_berErrs += errs; + m_berFrames++; + } + else if (duid == P25_DUID_LDU2) { + timerStart(); + + bool ret = lc.decodeLDU2(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, P25_LDU2_STR ", undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", + lc.getMFId(), lc.getAlgId(), lc.getKId()); + } + + P25Utils::decode(buffer + 1U, imbe, 114U, 262U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 262U, 410U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 452U, 600U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 640U, 788U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 830U, 978U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1020U, 1168U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1208U, 1356U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1398U, 1546U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1578U, 1726U); + errs += m_fec.measureP25BER(imbe); + + float ber = float(errs) / 12.33F; + if (ber < 10.0F) + LogMessage(LOG_CAL, P25_LDU2_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); + else { + LogWarning(LOG_CAL, P25_LDU2_STR ", uncorrectable audio"); + m_berUncorrectable++; + } + + // handle displaying TUI + updateTUIBER(ber); + + m_berBits += 1233U; + m_berErrs += errs; + m_berFrames++; + } + else if (duid == P25_DUID_PDU) { + timerStop(); + + // note: for the calibrator we will only process the PDU header -- and not the PDU data + uint8_t pduBuffer[P25_LDU_FRAME_LENGTH_BYTES]; + uint32_t bits = P25Utils::decode(buffer + 1U, pduBuffer, 0, P25_LDU_FRAME_LENGTH_BITS); + + uint8_t* rfPDU = new uint8_t[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + uint32_t rfPDUBits = 0U; + + for (uint32_t i = 0U; i < bits; i++, rfPDUBits++) { + bool b = READ_BIT(buffer, i); + WRITE_BIT(rfPDU, rfPDUBits, b); + } + + bool ret = dataHeader.decode(rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES); + if (!ret) { + LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate header data"); + Utils::dump(1U, "Unfixable PDU Data", rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES, P25_PDU_HEADER_LENGTH_BYTES); + } + else { + LogMessage(LOG_CAL, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padCount = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u", + dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), + dataHeader.getBlocksToFollow(), dataHeader.getPadCount(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), + dataHeader.getHeaderOffset()); + } + + delete[] rfPDU; + } + else if (duid == P25_DUID_TSDU) { + timerStop(); + + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(buffer + 1U); + + Utils::dump(1U, "Raw TSBK Dump", buffer + 1U, P25_TSDU_FRAME_LENGTH_BYTES); + + if (tsbk == nullptr) { + LogWarning(LOG_CAL, P25_TSDU_STR ", undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, P25_TSDU_STR ", mfId = $%02X, lco = $%02X, srcId = %u, dstId = %u, service = %u, netId = %u, sysId = %u", + tsbk->getMFId(), tsbk->getLCO(), tsbk->getSrcId(), tsbk->getDstId(), tsbk->getService(), tsbk->getNetId(), tsbk->getSysId()); + } + } +} + +/// +/// Process P25 Rx 1011hz BER. +/// +/// Buffer containing P25 data +void HostSetup::processP251KBER(const uint8_t* buffer) +{ + using namespace p25; + + uint8_t nid[P25_NID_LENGTH_BYTES]; + P25Utils::decode(buffer + 1U, nid, 48U, 114U); + uint8_t duid = nid[1U] & 0x0FU; + + uint32_t errs = 0U; + lc::LC lc = lc::LC(); + + if (duid == P25_DUID_HDU) { + timerStart(); + + bool ret = lc.decodeHDU(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, P25_HDU_STR ", undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_RF, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); + } + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + } + else if (duid == P25_DUID_TDU) { + if (m_berFrames != 0U) { + LogMessage(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + } + + timerStop(); + + // handle displaying TUI + float ber = (float)(float(m_berErrs * 100U) / float(m_berBits)); + updateTUIBER(ber); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + else if (duid == P25_DUID_LDU1) { + timerStart(); + + bool ret = lc.decodeLDU1(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, P25_LDU1_STR ", undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", + lc.getMFId(), lc.getLCO(), lc.getEmergency(), lc.getEncrypted(), lc.getPriority(), lc.getGroup(), lc.getSrcId(), lc.getDstId()); + } + + for (uint32_t i = 0U; i < 216U; i++) + errs += countErrs(buffer[i + 1U], LDU1_1K[i]); + + float ber = float(errs) / 12.33F; + if (ber < 10.0F) + LogMessage(LOG_CAL, P25_LDU1_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); + else { + LogWarning(LOG_CAL, P25_LDU1_STR ", uncorrectable audio"); + m_berUncorrectable++; + } + + // handle displaying TUI + updateTUIBER(ber); + + m_berBits += 1233U; + m_berErrs += errs; + m_berFrames++; + } + else if (duid == P25_DUID_LDU2) { + timerStart(); + + bool ret = lc.decodeLDU2(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, P25_LDU2_STR ", undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", + lc.getMFId(), lc.getAlgId(), lc.getKId()); + } + + for (uint32_t i = 0U; i < 216U; i++) + errs += countErrs(buffer[i + 1U], LDU2_1K[i]); + + float ber = float(errs) / 12.33F; + if (ber < 10.0F) + LogMessage(LOG_CAL, P25_LDU2_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); + else { + LogWarning(LOG_CAL, P25_LDU2_STR ", uncorrectable audio"); + m_berUncorrectable++; + } + + // handle displaying TUI + updateTUIBER(ber); + + m_berBits += 1233U; + m_berErrs += errs; + m_berFrames++; + } +} + +/// +/// Process NXDN Rx BER. +/// +/// Buffer containing NXDN data +void HostSetup::processNXDNBER(const uint8_t* buffer) +{ + using namespace nxdn; + + unsigned char data[NXDN_FRAME_LENGTH_BYTES]; + ::memcpy(data, buffer, NXDN_FRAME_LENGTH_BYTES); + NXDNUtils::scrambler(data); + + channel::LICH lich; + bool valid = lich.decode(data); + + if (valid) { + uint8_t usc = lich.getFCT(); + uint8_t opt = lich.getOption(); + + if (usc == NXDN_LICH_USC_SACCH_NS) { + if (m_berFrames == 0U) { + LogMessage(LOG_CAL, "NXDN VCALL (Voice Call), 1031 Test Pattern Start"); + + timerStart(); + m_berErrs = 0U; + m_berBits = 0U; + m_berFrames = 0U; + return; + } else { + float ber = float(m_berErrs * 100U) / float(m_berBits); + LogMessage(LOG_CAL, "NXDN TX_REL (Transmission Release), 1031 Test Pattern BER, frames: %u, errs: %.3f%% (%u/%u)", m_berFrames, ber, m_berErrs, m_berBits); + + // handle displaying TUI + updateTUIBER(ber); + + timerStop(); + m_berErrs = 0U; + m_berBits = 0U; + m_berFrames = 0U; + return; + } + } else if (opt == NXDN_LICH_STEAL_NONE) { + timerStart(); + + uint32_t errors = 0U; + + errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); + errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + errors += m_fec.regenerateNXDN(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + + m_berBits += 188U; + m_berErrs += errors; + m_berFrames++; + + float ber = float(errors) / 1.88F; + LogMessage(LOG_CAL, "NXDN VCALL (Voice Call), 1031 Test Pattern BER, (errs): %.3f%% (%u/188)", ber, errors); + + // handle displaying TUI + updateTUIBER(ber); + } + } +} + +/// +/// Write configuration to the modem DSP. +/// +/// True, if configuration is written, otherwise false. +bool HostSetup::writeConfig() +{ + return writeConfig(m_mode); +} + +/// +/// Write configuration to the modem DSP. +/// +/// +/// True, if configuration is written, otherwise false. +bool HostSetup::writeConfig(uint8_t modeOverride) +{ + if (m_isHotspot && m_transmit) { + setTransmit(); + sleep(25U); + } + + uint8_t buffer[25U]; + ::memset(buffer, 0x00U, 25U); + uint8_t lengthToWrite = 17U; + + buffer[0U] = DVM_FRAME_START; + buffer[2U] = CMD_SET_CONFIG; + + buffer[3U] = 0x00U; + m_conf["system"]["modem"]["rxInvert"] = __BOOL_STR(m_modem->m_rxInvert); + if (m_modem->m_rxInvert) + buffer[3U] |= 0x01U; + m_conf["system"]["modem"]["txInvert"] = __BOOL_STR(m_modem->m_txInvert); + if (m_modem->m_txInvert) + buffer[3U] |= 0x02U; + m_conf["system"]["modem"]["pttInvert"] = __BOOL_STR(m_modem->m_pttInvert); + if (m_modem->m_pttInvert) + buffer[3U] |= 0x04U; + if (m_debug) + buffer[3U] |= 0x10U; + if (!m_duplex) + buffer[3U] |= 0x80U; + + buffer[4U] = 0x00U; + m_conf["system"]["modem"]["dcBlocker"] = __BOOL_STR(m_modem->m_dcBlocker); + if (m_modem->m_dcBlocker) + buffer[4U] |= 0x01U; + + if (m_dmrEnabled) + buffer[4U] |= 0x02U; + if (m_p25Enabled) + buffer[4U] |= 0x08U; + + if (m_modem->m_fdmaPreamble > MAX_FDMA_PREAMBLE) { + LogWarning(LOG_P25, "oversized FDMA preamble count, reducing to maximum %u", MAX_FDMA_PREAMBLE); + m_modem->m_fdmaPreamble = MAX_FDMA_PREAMBLE; + } + + m_conf["system"]["modem"]["fdmaPreamble"] = __INT_STR(m_modem->m_fdmaPreamble); + buffer[5U] = m_modem->m_fdmaPreamble; + + buffer[6U] = modeOverride; + + m_conf["system"]["modem"]["rxLevel"] = __FLOAT_STR(m_modem->m_rxLevel); + buffer[7U] = (uint8_t)(m_modem->m_rxLevel * 2.55F + 0.5F); + + m_conf["system"]["modem"]["txLevel"] = __FLOAT_STR(m_modem->m_cwIdTXLevel); + buffer[8U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); + + buffer[9U] = 1U; + + m_conf["system"]["modem"]["dmrRxDelay"] = __INT_STR(m_modem->m_dmrRxDelay); + buffer[10U] = m_modem->m_dmrRxDelay; + + uint32_t nac = 0xF7EU; + buffer[11U] = (nac >> 4) & 0xFFU; + buffer[12U] = (nac << 4) & 0xF0U; + + buffer[13U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); + buffer[15U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); + + m_conf["system"]["modem"]["txDCOffset"] = __INT_STR(m_modem->m_txDCOffset); + buffer[16U] = (uint8_t)(m_modem->m_txDCOffset + 128); + m_conf["system"]["modem"]["rxDCOffset"] = __INT_STR(m_modem->m_rxDCOffset); + buffer[17U] = (uint8_t)(m_modem->m_rxDCOffset + 128); + + m_conf["system"]["modem"]["p25CorrCount"] = __INT_STR(m_modem->m_p25CorrCount); + buffer[14U] = (uint8_t)m_modem->m_p25CorrCount; + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + lengthToWrite = 24U; + + if (m_nxdnEnabled) + buffer[4U] |= 0x10U; + + buffer[18U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); + + m_conf["system"]["modem"]["softpot"]["rxCoarse"] = __INT_STR(m_modem->m_rxCoarsePot); + buffer[19U] = m_modem->m_rxCoarsePot; + m_conf["system"]["modem"]["softpot"]["rxFine"] = __INT_STR(m_modem->m_rxFinePot); + buffer[20U] = m_modem->m_rxFinePot; + m_conf["system"]["modem"]["softpot"]["txCoarse"] = __INT_STR(m_modem->m_txCoarsePot); + buffer[21U] = m_modem->m_txCoarsePot; + m_conf["system"]["modem"]["softpot"]["txFine"] = __INT_STR(m_modem->m_txFinePot); + buffer[22U] = m_modem->m_txFinePot; + m_conf["system"]["modem"]["softpot"]["rssiCoarse"] = __INT_STR(m_modem->m_rssiCoarsePot); + buffer[23U] = m_modem->m_rssiCoarsePot; + m_conf["system"]["modem"]["softpot"]["rssiFine"] = __INT_STR(m_modem->m_rssiFinePot); + buffer[24U] = m_modem->m_rssiFinePot; + } + + buffer[1U] = lengthToWrite; + + int ret = m_modem->write(buffer, lengthToWrite); + if (ret != lengthToWrite) + return false; + + sleep(10U); + + m_modem->clock(0U); + return true; +} + +/// +/// Write RF parameters to the air interface modem. +/// +/// +bool HostSetup::writeRFParams() +{ + uint8_t buffer[22U]; + ::memset(buffer, 0x00U, 22U); + uint8_t lengthToWrite = 18U; + + buffer[0U] = DVM_FRAME_START; + buffer[2U] = CMD_SET_RFPARAMS; + + buffer[3U] = 0x00U; + + buffer[4U] = (m_rxAdjustedFreq >> 0) & 0xFFU; + buffer[5U] = (m_rxAdjustedFreq >> 8) & 0xFFU; + buffer[6U] = (m_rxAdjustedFreq >> 16) & 0xFFU; + buffer[7U] = (m_rxAdjustedFreq >> 24) & 0xFFU; + + buffer[8U] = (m_txAdjustedFreq >> 0) & 0xFFU; + buffer[9U] = (m_txAdjustedFreq >> 8) & 0xFFU; + buffer[10U] = (m_txAdjustedFreq >> 16) & 0xFFU; + buffer[11U] = (m_txAdjustedFreq >> 24) & 0xFFU; + + buffer[12U] = (unsigned char)(100 * 2.55F + 0.5F); // cal sets power fixed to 100 + + m_conf["system"]["modem"]["hotspot"]["dmrDiscBWAdj"] = __INT_STR(m_modem->m_dmrDiscBWAdj); + buffer[13U] = (uint8_t)(m_modem->m_dmrDiscBWAdj + 128); + m_conf["system"]["modem"]["hotspot"]["p25DiscBWAdj"] = __INT_STR(m_modem->m_p25DiscBWAdj); + buffer[14U] = (uint8_t)(m_modem->m_p25DiscBWAdj + 128); + m_conf["system"]["modem"]["hotspot"]["dmrPostBWAdj"] = __INT_STR(m_modem->m_dmrPostBWAdj); + buffer[15U] = (uint8_t)(m_modem->m_dmrPostBWAdj + 128); + m_conf["system"]["modem"]["hotspot"]["p25PostBWAdj"] = __INT_STR(m_modem->m_p25PostBWAdj); + buffer[16U] = (uint8_t)(m_modem->m_p25PostBWAdj + 128); + + m_conf["system"]["modem"]["hotspot"]["adfGainMode"] = __INT_STR((int)m_modem->m_adfGainMode); + buffer[17U] = (uint8_t)m_modem->m_adfGainMode; + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + lengthToWrite = 22U; + + m_conf["system"]["modem"]["hotspot"]["nxdnDiscBWAdj"] = __INT_STR(m_modem->m_nxdnDiscBWAdj); + buffer[18U] = (uint8_t)(m_modem->m_nxdnDiscBWAdj + 128); + m_conf["system"]["modem"]["hotspot"]["nxdnPostBWAdj"] = __INT_STR(m_modem->m_nxdnPostBWAdj); + buffer[19U] = (uint8_t)(m_modem->m_nxdnPostBWAdj + 128); + + // support optional AFC parameters + m_conf["system"]["modem"]["hotspot"]["afcEnable"] = __BOOL_STR(m_modem->m_afcEnable); + m_conf["system"]["modem"]["hotspot"]["afcKI"] = __INT_STR(m_modem->m_afcKI); + m_conf["system"]["modem"]["hotspot"]["afcKP"] = __INT_STR(m_modem->m_afcKP); + buffer[20U] = (m_modem->m_afcEnable ? 0x80 : 0x00) + + (m_modem->m_afcKP << 4) + (m_modem->m_afcKI); + m_conf["system"]["modem"]["hotspot"]["afcRange"] = __INT_STR(m_modem->m_afcRange); + buffer[21U] = m_modem->m_afcRange; + } + + buffer[1U] = lengthToWrite; + + int ret = m_modem->write(buffer, lengthToWrite); + if (ret <= 0) + return false; + + sleep(10U); + + m_modem->clock(0U); + return true; +} + +/// +/// Write symbol level adjustments to the modem DSP. +/// +/// True, if level adjustments are written, otherwise false. +bool HostSetup::writeSymbolAdjust() +{ + uint8_t buffer[20U]; + ::memset(buffer, 0x00U, 20U); + uint8_t lengthToWrite = 7U; + + buffer[0U] = DVM_FRAME_START; + buffer[2U] = CMD_SET_SYMLVLADJ; + + m_conf["system"]["modem"]["repeater"]["dmrSymLvl3Adj"] = __INT_STR(m_modem->m_dmrSymLevel3Adj); + buffer[3U] = (uint8_t)(m_modem->m_dmrSymLevel3Adj + 128); + m_conf["system"]["modem"]["repeater"]["dmrSymLvl1Adj"] = __INT_STR(m_modem->m_dmrSymLevel1Adj); + buffer[4U] = (uint8_t)(m_modem->m_dmrSymLevel1Adj + 128); + + m_conf["system"]["modem"]["repeater"]["p25SymLvl3Adj"] = __INT_STR(m_modem->m_p25SymLevel3Adj); + buffer[5U] = (uint8_t)(m_modem->m_p25SymLevel3Adj + 128); + m_conf["system"]["modem"]["repeater"]["p25SymLvl1Adj"] = __INT_STR(m_modem->m_p25SymLevel1Adj); + buffer[6U] = (uint8_t)(m_modem->m_p25SymLevel1Adj + 128); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + lengthToWrite = 9U; + + m_conf["system"]["modem"]["repeater"]["nxdnSymLvl3Adj"] = __INT_STR(m_modem->m_nxdnSymLevel3Adj); + buffer[7U] = (uint8_t)(m_modem->m_nxdnSymLevel3Adj + 128); + m_conf["system"]["modem"]["repeater"]["nxdnSymLvl1Adj"] = __INT_STR(m_modem->m_nxdnSymLevel1Adj); + buffer[8U] = (uint8_t)(m_modem->m_nxdnSymLevel1Adj + 128); + } + + buffer[1U] = lengthToWrite; + + int ret = m_modem->write(buffer, lengthToWrite); + if (ret <= 0) + return false; + + sleep(10U); + + m_modem->clock(0U); + return true; +} + +/// +/// Write transmit FIFO buffer lengths. +/// +/// True, if level adjustments are written, otherwise false. +bool HostSetup::writeFifoLength() +{ + uint8_t buffer[9U]; + ::memset(buffer, 0x00U, 9U); + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 9U; + buffer[2U] = CMD_SET_BUFFERS; + + m_conf["system"]["modem"]["dmrFifoLength"] = __INT_STR(m_modem->m_dmrFifoLength); + buffer[3U] = (uint8_t)(m_modem->m_dmrFifoLength >> 8) & 0xFFU; + buffer[4U] = (uint8_t)(m_modem->m_dmrFifoLength & 0xFFU); + m_conf["system"]["modem"]["p25FifoLength"] = __INT_STR(m_modem->m_p25FifoLength); + buffer[5U] = (uint8_t)(m_modem->m_p25FifoLength >> 8) & 0xFFU; + buffer[6U] = (uint8_t)(m_modem->m_p25FifoLength & 0xFFU); + m_conf["system"]["modem"]["nxdnFifoLenth"] = __INT_STR(m_modem->m_nxdnFifoLength); + buffer[7U] = (uint8_t)(m_modem->m_nxdnFifoLength >> 8) & 0xFFU; + buffer[8U] = (uint8_t)(m_modem->m_nxdnFifoLength & 0xFFU); + + int ret = m_modem->write(buffer, 9U); + if (ret <= 0) + return false; + + sleep(10U); + + m_modem->clock(0U); + return true; +} + +/// +/// Helper to sleep the calibration thread. +/// +/// Milliseconds to sleep. +void HostSetup::sleep(uint32_t ms) +{ + ::usleep(ms * 1000); +} + +/// +/// Read the configuration area on the air interface modem. +/// +bool HostSetup::readFlash() +{ + if (m_modem->m_flashDisabled) { + return false; + } + + uint8_t buffer[3U]; + ::memset(buffer, 0x00U, 3U); + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = CMD_FLSH_READ; + + int ret = m_modem->write(buffer, 3U); + if (ret <= 0) + return false; + + sleep(1000U); + + m_modem->clock(0U); + return true; +} + +/// +/// Process the configuration data from the air interface modem. +/// +/// +void HostSetup::processFlashConfig(const uint8_t *buffer) +{ + if (m_updateConfigFromModem) { + LogMessage(LOG_CAL, " - Restoring local configuration from configuration area on modem"); + + // general config + m_modem->m_rxInvert = (buffer[3U] & 0x01U) == 0x01U; + m_conf["system"]["modem"]["rxInvert"] = __BOOL_STR(m_modem->m_rxInvert); + m_modem->m_txInvert = (buffer[3U] & 0x02U) == 0x02U; + m_conf["system"]["modem"]["txInvert"] = __BOOL_STR(m_modem->m_txInvert); + m_modem->m_pttInvert = (buffer[3U] & 0x04U) == 0x04U; + m_conf["system"]["modem"]["pttInvert"] = __BOOL_STR(m_modem->m_pttInvert); + + m_modem->m_dcBlocker = (buffer[4U] & 0x01U) == 0x01U; + m_conf["system"]["modem"]["dcBlocker"] = __BOOL_STR(m_modem->m_dcBlocker); + + m_modem->m_fdmaPreamble = buffer[5U]; + m_conf["system"]["modem"]["fdmaPreamble"] = __INT_STR(m_modem->m_fdmaPreamble); + + // levels + m_modem->m_rxLevel = (float(buffer[7U]) - 0.5F) / 2.55F; + m_conf["system"]["modem"]["rxLevel"] = __FLOAT_STR(m_modem->m_rxLevel); + m_modem->m_cwIdTXLevel = (float(buffer[8U]) - 0.5F) / 2.55F; + m_conf["system"]["modem"]["txLevel"] = __FLOAT_STR(m_modem->m_cwIdTXLevel); + + m_modem->m_dmrRxDelay = buffer[10U]; + m_conf["system"]["modem"]["dmrRxDelay"] = __INT_STR(m_modem->m_dmrRxDelay); + + m_modem->m_p25CorrCount = buffer[11U]; + m_conf["system"]["modem"]["p25CorrCount"] = __INT_STR(m_modem->m_p25CorrCount); + + m_modem->m_txDCOffset = int(buffer[16U]) - 128; + m_conf["system"]["modem"]["txDCOffset"] = __INT_STR(m_modem->m_txDCOffset); + m_modem->m_rxDCOffset = int(buffer[17U]) - 128; + m_conf["system"]["modem"]["rxDCOffset"] = __INT_STR(m_modem->m_rxDCOffset); + + writeConfig(); + sleep(500); + + // symbol adjust + m_modem->m_dmrSymLevel3Adj = int(buffer[35U]) - 128; + m_conf["system"]["modem"]["repeater"]["dmrSymLvl3Adj"] = __INT_STR(m_modem->m_dmrSymLevel3Adj); + m_modem->m_dmrSymLevel1Adj = int(buffer[36U]) - 128; + m_conf["system"]["modem"]["repeater"]["dmrSymLvl1Adj"] = __INT_STR(m_modem->m_dmrSymLevel1Adj); + + m_modem->m_p25SymLevel3Adj = int(buffer[37U]) - 128; + m_conf["system"]["modem"]["repeater"]["p25SymLvl3Adj"] = __INT_STR(m_modem->m_p25SymLevel3Adj); + m_modem->m_p25SymLevel1Adj = int(buffer[38U]) - 128; + m_conf["system"]["modem"]["repeater"]["p25SymLvl1Adj"] = __INT_STR(m_modem->m_p25SymLevel1Adj); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + m_modem->m_nxdnSymLevel3Adj = int(buffer[41U]) - 128; + m_conf["system"]["modem"]["repeater"]["nxdnSymLvl3Adj"] = __INT_STR(m_modem->m_nxdnSymLevel3Adj); + m_modem->m_nxdnSymLevel1Adj = int(buffer[42U]) - 128; + m_conf["system"]["modem"]["repeater"]["nxdnSymLvl1Adj"] = __INT_STR(m_modem->m_nxdnSymLevel1Adj); + } + + writeSymbolAdjust(); + sleep(500); + + // RF parameters + m_modem->m_dmrDiscBWAdj = int8_t(buffer[20U]) - 128; + m_conf["system"]["modem"]["hotspot"]["dmrDiscBWAdj"] = __INT_STR(m_modem->m_dmrDiscBWAdj); + m_modem->m_p25DiscBWAdj = int8_t(buffer[21U]) - 128; + m_conf["system"]["modem"]["hotspot"]["p25DiscBWAdj"] = __INT_STR(m_modem->m_p25DiscBWAdj); + m_modem->m_dmrPostBWAdj = int8_t(buffer[22U]) - 128; + m_conf["system"]["modem"]["hotspot"]["dmrPostBWAdj"] = __INT_STR(m_modem->m_dmrPostBWAdj); + m_modem->m_p25PostBWAdj = int8_t(buffer[23U]) - 128; + m_conf["system"]["modem"]["hotspot"]["p25PostBWAdj"] = __INT_STR(m_modem->m_p25PostBWAdj); + + m_modem->m_adfGainMode = (ADF_GAIN_MODE)buffer[24U]; + m_conf["system"]["modem"]["hotspot"]["adfGainMode"] = __INT_STR((int)m_modem->m_adfGainMode); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + m_modem->m_nxdnDiscBWAdj = int8_t(buffer[39U]) - 128; + m_conf["system"]["modem"]["repeater"]["nxdnDiscBWAdj"] = __INT_STR(m_modem->m_nxdnDiscBWAdj); + m_modem->m_nxdnPostBWAdj = int8_t(buffer[40U]) - 128; + m_conf["system"]["modem"]["repeater"]["nxdnPostBWAdj"] = __INT_STR(m_modem->m_nxdnPostBWAdj); + } + + m_modem->m_txTuning = int(buffer[25U]) - 128; + m_conf["system"]["modem"]["hotspot"]["txTuning"] = __INT_STR(m_modem->m_txTuning); + m_txAdjustedFreq = m_txFrequency + m_modem->m_txTuning; + m_modem->m_rxTuning = int(buffer[26U]) - 128; + m_conf["system"]["modem"]["hotspot"]["rxTuning"] = __INT_STR(m_modem->m_rxTuning); + m_rxAdjustedFreq = m_rxFrequency + m_modem->m_rxTuning; + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + m_modem->m_rxCoarsePot = buffer[43U]; + m_conf["system"]["modem"]["softpot"]["rxCoarse"] = __INT_STR(m_modem->m_rxCoarsePot); + m_modem->m_rxFinePot = buffer[44U]; + m_conf["system"]["modem"]["softpot"]["rxFine"] = __INT_STR(m_modem->m_rxFinePot); + + m_modem->m_txCoarsePot = buffer[45U]; + m_conf["system"]["modem"]["softpot"]["txCoarse"] = __INT_STR(m_modem->m_txCoarsePot); + m_modem->m_txFinePot = buffer[46U]; + m_conf["system"]["modem"]["softpot"]["txFine"] = __INT_STR(m_modem->m_txFinePot); + + m_modem->m_rssiCoarsePot = buffer[47U]; + m_conf["system"]["modem"]["softpot"]["rssiCoarse"] = __INT_STR(m_modem->m_rssiCoarsePot); + m_modem->m_rssiFinePot = buffer[48U]; + m_conf["system"]["modem"]["softpot"]["rssiFine"] = __INT_STR(m_modem->m_rssiFinePot); + } + + writeRFParams(); + sleep(500); + } +} + +/// +/// Erase the configuration area on the air interface modem. +/// +bool HostSetup::eraseFlash() +{ + if (m_modem->m_flashDisabled) { + return false; + } + + uint8_t buffer[249U]; + ::memset(buffer, 0x00U, 249U); + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 249U; + buffer[2U] = CMD_FLSH_WRITE; + + // configuration version + buffer[DVM_CONF_AREA_LEN] = DVM_CONF_AREA_VER & 0x7FU; + buffer[DVM_CONF_AREA_LEN] |= 0x80U; // flag erased + edac::CRC::addCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); + + int ret = m_modem->write(buffer, 249U); + if (ret <= 0) + return false; + + sleep(1000U); + + m_updateConfigFromModem = false; + LogMessage(LOG_CAL, " - Erased configuration area on modem"); + + m_modem->clock(0U); + return true; +} + +/// +/// Write the configuration area on the air interface modem. +/// +bool HostSetup::writeFlash() +{ + if (m_modem->m_flashDisabled) { + return false; + } + + uint8_t buffer[249U]; + ::memset(buffer, 0x00U, 249U); + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 249U; + buffer[2U] = CMD_FLSH_WRITE; + + // general config + buffer[3U] = 0x00U; + if (m_modem->m_rxInvert) + buffer[3U] |= 0x01U; + if (m_modem->m_txInvert) + buffer[3U] |= 0x02U; + if (m_modem->m_pttInvert) + buffer[3U] |= 0x04U; + + buffer[4U] = 0x00U; + if (m_modem->m_dcBlocker) + buffer[4U] |= 0x01U; + + buffer[5U] = m_modem->m_fdmaPreamble; + + buffer[7U] = (uint8_t)(m_modem->m_rxLevel * 2.55F + 0.5F); + buffer[8U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); + + buffer[10U] = m_modem->m_dmrRxDelay; + buffer[11U] = (uint8_t)m_modem->m_p25CorrCount; + + buffer[13U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); + buffer[15U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); + + buffer[16U] = (uint8_t)(m_modem->m_txDCOffset + 128); + buffer[17U] = (uint8_t)(m_modem->m_rxDCOffset + 128); + + // RF parameters + buffer[20U] = (uint8_t)(m_modem->m_dmrDiscBWAdj + 128); + buffer[21U] = (uint8_t)(m_modem->m_p25DiscBWAdj + 128); + buffer[22U] = (uint8_t)(m_modem->m_dmrPostBWAdj + 128); + buffer[23U] = (uint8_t)(m_modem->m_p25PostBWAdj + 128); + + buffer[24U] = (uint8_t)m_modem->m_adfGainMode; + + uint32_t txTuning = (uint32_t)m_modem->m_txTuning; + __SET_UINT32(txTuning, buffer, 25U); + uint32_t rxTuning = (uint32_t)m_modem->m_rxTuning; + __SET_UINT32(rxTuning, buffer, 29U); + + // symbol adjust + buffer[35U] = (uint8_t)(m_modem->m_dmrSymLevel3Adj + 128); + buffer[36U] = (uint8_t)(m_modem->m_dmrSymLevel1Adj + 128); + + buffer[37U] = (uint8_t)(m_modem->m_p25SymLevel3Adj + 128); + buffer[38U] = (uint8_t)(m_modem->m_p25SymLevel1Adj + 128); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + buffer[39U] = (uint8_t)(m_modem->m_nxdnDiscBWAdj + 128); + buffer[40U] = (uint8_t)(m_modem->m_nxdnPostBWAdj + 128); + + buffer[41U] = (uint8_t)(m_modem->m_nxdnSymLevel3Adj + 128); + buffer[42U] = (uint8_t)(m_modem->m_nxdnSymLevel1Adj + 128); + } + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + buffer[43U] = m_modem->m_rxCoarsePot; + buffer[44U] = m_modem->m_rxFinePot; + + buffer[45U] = m_modem->m_txCoarsePot; + buffer[46U] = m_modem->m_txFinePot; + + buffer[47U] = m_modem->m_rssiCoarsePot; + buffer[48U] = m_modem->m_rssiFinePot; + } + + // software signature + std::string software; + software.append(__NET_NAME__ " " __VER__ " (built " __BUILD__ ")"); + for (uint8_t i = 0; i < software.length(); i++) { + buffer[176U + i] = software[i]; + } + + // configuration version + buffer[DVM_CONF_AREA_LEN] = DVM_CONF_AREA_VER; + edac::CRC::addCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); + +#if DEBUG_MODEM_CAL + Utils::dump(1U, "HostSetup::writeFlash(), Written", buffer, 249U); +#endif + + int ret = m_modem->write(buffer, 249U); + if (ret <= 0) + return false; + + sleep(1000U); + + m_updateConfigFromModem = false; + + m_modem->clock(0U); + return true; +} +/// +/// Helper to clock the calibration BER timer. +/// +void HostSetup::timerClock() +{ + if (m_timer > 0U && m_timeout > 0U) { + m_timer += 1U; + + if (m_timer >= m_timeout) { + LogMessage(LOG_CAL, "Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + + timerStop(); + } + } +} + +/// +/// Helper to start the calibration BER timer. +/// +void HostSetup::timerStart() +{ + if (m_timeout > 0U) + m_timer = 1U; +} + +/// +/// Helper to stop the calibration BER timer. +/// +void HostSetup::timerStop() +{ + m_timer = 0U; +} + +/// +/// Retrieve the current status from the air interface modem. +/// +void HostSetup::getStatus() +{ + uint8_t buffer[50U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = CMD_GET_STATUS; + + int ret = m_modem->write(buffer, 4U); + if (ret <= 0) + return; + + sleep(25U); + + m_requestedStatus = true; + m_modem->clock(0U); +} + +#if defined(ENABLE_SETUP_TUI) +/// +/// Prints the current status of the calibration. +/// +void HostSetup::printStatus() +{ + if (m_setupWnd->m_statusWnd.isShown()) { + m_setupWnd->m_statusWnd.clear(); + } + + yaml::Node systemConf = m_conf["system"]; + { + std::stringstream status; + yaml::Node modemConfig = m_conf["system"]["modem"]; + std::string type = modemConfig["protocol"]["type"].as(); + + yaml::Node uartConfig = modemConfig["protocol"]["uart"]; + std::string modemPort = uartConfig["port"].as(); + uint32_t portSpeed = uartConfig["speed"].as(115200U); + + status << "Operating Mode: " << m_modeStr << std::endl; + status << "Port Type: " << type << ", Port Speed: " << portSpeed << ", Proto Ver: " << (uint32_t)(m_modem->getVersion()) << std::endl; + + status << std::endl; + m_setupWnd->m_statusWnd.append(status.str()); + } + + { + std::stringstream status; + std::string identity = systemConf["identity"].as(); + uint32_t timeout = systemConf["timeout"].as(); + bool duplex = systemConf["duplex"].as(true); + bool simplexSameFrequency = systemConf["simplexSameFrequency"].as(false); + uint32_t modeHang = systemConf["modeHang"].as(); + uint32_t rfTalkgroupHang = systemConf["rfTalkgroupHang"].as(); + bool fixedMode = systemConf["fixedMode"].as(false); + bool dmrEnabled = m_conf["protocols"]["dmr"]["enable"].as(true); + bool p25Enabled = m_conf["protocols"]["p25"]["enable"].as(true); + bool nxdnEnabled = m_conf["protocols"]["nxdn"]["enable"].as(true); + + IdenTable entry = m_idenTable->find(m_channelId); + if (entry.baseFrequency() == 0U) { + ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); + } + + calculateRxTxFreq(); + + status << "Identity: " << identity << ", Duplex: " << ((duplex) ? "yes" : "no") << ", Simplex Frequency: " << ((simplexSameFrequency) ? "yes" : "no") << ", Timeout: " << timeout << std::endl; + status << "Mode Hang: " << modeHang << ", RF TG Hang: " << rfTalkgroupHang << ", Fixed Mode: " << ((fixedMode) ? "yes" : "no") << std::endl; + status << "DMR Enabled: " << ((dmrEnabled) ? "yes" : "no") << ", P25 Enabled: " << ((p25Enabled) ? "yes" : "no") << ", NXDN Enabled: " << ((nxdnEnabled) ? "yes" : "no") << std::endl; + status << "Channel Id: " << (uint32_t)(m_channelId) << ", Channel No: $" << std::hex << m_channelNo << std::dec << std::endl; + status << "Base Freq: " << entry.baseFrequency() << "Hz, TX Offset: " << entry.txOffsetMhz() << ", BW: " << entry.chBandwidthKhz() << ", Spacing: " << entry.chSpaceKhz() << std::endl; + m_setupWnd->m_statusWnd.append(status.str()); + } + + { + std::stringstream status; yaml::Node cwId = systemConf["cwId"]; bool enabled = cwId["enable"].as(false); uint32_t cwTime = cwId["time"].as(10U); std::string callsign = cwId["callsign"].as(); - LogMessage(LOG_SETUP, " - Callsign: %s, CW Interval: %u mins, CW Enabled: %u", callsign.c_str(), cwTime, enabled); + status << "Callsign: " << callsign << ", CW Interval: " << cwTime << " mins, CW Enabled: " << ((enabled) ? "yes" : "no") << std::endl; + + status << std::endl; + m_setupWnd->m_statusWnd.append(status.str()); } { + std::stringstream status; yaml::Node rfssConfig = systemConf["config"]; uint32_t dmrColorCode = rfssConfig["colorCode"].as(2U); uint32_t p25NAC = (uint32_t)::strtoul(rfssConfig["nac"].as("293").c_str(), NULL, 16); + uint32_t nxdnRAN = rfssConfig["ran"].as(1U); - LogMessage(LOG_SETUP, " - DMR Color Code: %u, P25 NAC: $%03X", dmrColorCode, p25NAC); + status << "DMR Color Code: " << dmrColorCode << ", P25 NAC: $" << std::hex << p25NAC << std::dec << ", NXDN RAN: " << nxdnRAN << std::endl; + m_setupWnd->m_statusWnd.append(status.str()); } { + std::stringstream status; yaml::Node rfssConfig = systemConf["config"]; uint32_t siteId = (uint8_t)::strtoul(rfssConfig["siteId"].as("1").c_str(), NULL, 16); uint32_t dmrNetId = (uint32_t)::strtoul(rfssConfig["dmrNetId"].as("1").c_str(), NULL, 16); @@ -871,6 +2167,126 @@ void HostSetup::printStatus() uint32_t p25SysId = (uint32_t)::strtoul(rfssConfig["sysId"].as("001").c_str(), NULL, 16); uint32_t p25RfssId = (uint8_t)::strtoul(rfssConfig["rfssId"].as("1").c_str(), NULL, 16); - LogMessage(LOG_SETUP, " - Site Id: $%02X, DMR Network Id: $%05X, P25 Network Id: $%05X, P25 System Id: $%03X, P25 RFSS Id: $%02X", siteId, dmrNetId, p25NetId, p25SysId, p25RfssId); + status << "Site Id: $" << std::hex << siteId << ", DMR Network Id: $" << std::hex << dmrNetId << std::dec << std::endl; + status << "P25 Net Id: $" << std::hex << p25NetId << ", P25 System Id: $" << std::hex << p25SysId << ", P25 RFSS Id: $" << std::hex << p25RfssId << std::dec << std::endl; + + status << std::endl; + m_setupWnd->m_statusWnd.append(status.str()); + } + + { + std::stringstream status; + if (!m_isHotspot) { + status << "PTT Invert: " << (m_modem->m_pttInvert ? "yes" : "no") << ", RX Invert: " << (m_modem->m_rxInvert ? "yes" : "no") << + ", TX Invert: " << (m_modem->m_txInvert ? "yes" : "no") << ", DC Blocker: " << (m_modem->m_dcBlocker ? "yes" : "no") << std::endl; + } + + status << "Rx Level: " << m_modem->m_rxLevel << ", Tx Level: " << m_modem->m_cwIdTXLevel << + ", Tx DC Offset: " << m_modem->m_txDCOffset << ", Rx DC Offset: " << m_modem->m_rxDCOffset << std::endl; + + if (!m_isHotspot) { + status << "Rx Coarse: " << (int)(m_modem->m_rxCoarsePot) << ", Rx Fine: " << (int)(m_modem->m_rxFinePot) << ", Tx Coarse: " << (int)(m_modem->m_txCoarsePot) << + ", RSSI Coarse: " << (int)(m_modem->m_rssiCoarsePot) << std::endl; + status << "DMR Symb +/- 3 Adj.: " << m_modem->m_dmrSymLevel3Adj << ", DMR Symb +/- 1 Adj.: " << m_modem->m_dmrSymLevel1Adj << std::endl; + status << "P25 Symb +/- 3 Adj.: " << m_modem->m_p25SymLevel3Adj << ", P25 Symb +/- 1 Adj.: " << m_modem->m_p25SymLevel1Adj << std::endl; + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + status << "NXDN Symb +/- 3 Adj.: " << m_modem->m_nxdnSymLevel3Adj << ", NXDN Symb +/- 1 Adj.: " << m_modem->m_nxdnSymLevel1Adj << std::endl; + } + } + + m_setupWnd->m_statusWnd.append(status.str()); + } + + { + if (m_isHotspot) { + std::stringstream status; + status << "DMR Disc. BW: " << (int)(m_modem->m_dmrDiscBWAdj) << ", DMR Post Demod BW: " << (int)(m_modem->m_dmrPostBWAdj) << std::endl; + status << "P25 Disc BW: " << (int)(m_modem->m_p25DiscBWAdj) << ", P25 Post Demod BW: " << (int)(m_modem->m_p25PostBWAdj) << std::endl; + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + status << "NXDN Disc. BW: " << (int)(m_modem->m_nxdnDiscBWAdj) << ", NXDN Post Demod BW: " << (int)(m_modem->m_nxdnPostBWAdj) << std::endl; + status << "AFC Enabled: " << (m_modem->m_afcEnable ? "yes" : "no") << ", KI: " << (uint32_t)(m_modem->m_afcKI) << ", KP: " << (uint32_t)(m_modem->m_afcKP) << + ", Range: " << (uint32_t)(m_modem->m_afcRange) << std::endl; + } + + switch (m_modem->m_adfGainMode) { + case ADF_GAIN_AUTO_LIN: + status << "ADF7021 Gain Mode: Auto High Linearity" << std::endl; + break; + case ADF_GAIN_LOW: + status << "ADF7021 Gain Mode: Low" << std::endl; + break; + case ADF_GAIN_HIGH: + status << "ADF7021 Gain Mode: High" << std::endl; + break; + case ADF_GAIN_AUTO: + default: + status << "ADF7021 Gain Mode: Auto" << std::endl; + break; + } + + m_setupWnd->m_statusWnd.append(status.str()); + } + } + + { + std::stringstream status; + status << "FDMA Preambles: " << (uint32_t)(m_modem->m_fdmaPreamble) << ", DMR Rx Delay: " << (uint32_t)(m_modem->m_dmrRxDelay) << + ", P25 Corr. Count: " << (uint32_t)(m_modem->m_p25CorrCount) << std::endl; +/* + status << "FDMA Preambles: " << m_modem->m_fdmaPreamble << "(" << std::fixed << std::setprecision(1) << float(m_modem->m_fdmaPreamble) * 0.2222F << ")" << + ", DMR Rx Delay: " << m_modem->m_dmrRxDelay << "(" << std::fixed << std::setprecision(1) << float(m_modem->m_dmrRxDelay) * 0.0416666F << ")" << + ", P25 Corr. Count: " << m_modem->m_p25CorrCount << "(" << std::fixed << std::setprecision(1) << float(m_modem->m_p25CorrCount) * 0.667F << ")" << std::endl; +*/ + status << "Rx Freq: " << m_modem->m_rxFrequency << "Hz, Tx Freq: " << m_modem->m_txFrequency << "Hz, Rx Offset: " << m_modem->m_rxTuning << "Hz, Tx Offset: " << m_modem->m_txTuning << "Hz" << std::endl; + status << "Rx Effective Freq: " << m_rxAdjustedFreq << "Hz, Tx Effective Freq: " << m_txAdjustedFreq << std::endl; + m_setupWnd->m_statusWnd.append(status.str()); + } + + getStatus(); +} +#endif // defined(ENABLE_SETUP_TUI) + +/// +/// Add data frame to the data ring buffer. +/// +/// +/// +/// +void HostSetup::addFrame(const uint8_t* data, uint32_t length, uint32_t maxFrameSize) +{ + assert(data != nullptr); + + uint32_t space = m_queue.freeSpace(); + if (space < (length + 1U)) { + uint32_t queueLen = m_queue.length(); + m_queue.resize(queueLen + maxFrameSize); + LogError(LOG_CAL, "overflow in the frame queue while writing data; queue free is %u, needed %u; resized was %u is %u", space, length, queueLen, m_queue.length()); + return; + } + + uint8_t len = length; + m_queue.addData(&len, 1U); + m_queue.addData(data, len); +} + +/// +/// Counts the total number of bit errors between bytes. +/// +/// +/// +/// +uint8_t HostSetup::countErrs(uint8_t a, uint8_t b) +{ + int cnt = 0; + uint8_t tmp = a ^ b; + while (tmp) { + if (tmp % 2 == 1) + cnt++; + tmp /= 2; } + return cnt; } diff --git a/src/host/setup/HostSetup.h b/src/host/setup/HostSetup.h index 9ce9e053..d8f46342 100644 --- a/src/host/setup/HostSetup.h +++ b/src/host/setup/HostSetup.h @@ -7,7 +7,7 @@ * */ /* -* Copyright (C) 2021 by Bryan Biedenkapp N2PLL +* Copyright (C) 2021-2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,13 +27,57 @@ #define __HOST_SETUP_H__ #include "Defines.h" -#include "host/Console.h" +#include "edac/AMBEFEC.h" +#include "modem/Modem.h" #include "host/Host.h" #include "lookups/IdenTableLookup.h" #include "yaml/Yaml.h" +#include "RingBuffer.h" +#include "StopWatch.h" #include +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define DMR_CAL_STR "[Tx] DMR 1200 Hz Tone Mode (2.75Khz Deviation)" +#define P25_CAL_STR "[Tx] P25 1200 Hz Tone Mode (2.83Khz Deviation)" +#define DMR_LF_CAL_STR "[Tx] DMR Low Frequency Mode (80 Hz square wave)" +#define DMR_CAL_1K_STR "[Tx] DMR BS 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" +#define DMR_DMO_CAL_1K_STR "[Tx] DMR MS 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" +#define P25_CAL_1K_STR "[Tx] P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" +#define P25_TDU_TEST_STR "[Tx] P25 TDU Data Test Stream" +#define NXDN_CAL_1K_STR "[Tx] NXDN 1031 Hz Test Pattern (RAN1 ID1 TG1)" +#define DMR_FEC_STR "[Rx] DMR MS FEC BER Test Mode" +#define DMR_FEC_1K_STR "[Rx] DMR MS 1031 Hz Test Pattern (CC1 ID1 TG9)" +#define P25_FEC_STR "[Rx] P25 FEC BER Test Mode" +#define P25_FEC_1K_STR "[Rx] P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" +#define NXDN_FEC_STR "[Rx] NXDN FEC BER Test Mode" +#define RSSI_CAL_STR "RSSI Calibration Mode" + +// --------------------------------------------------------------------------- +// Class Prototypes +// --------------------------------------------------------------------------- + +#if defined(ENABLE_SETUP_TUI) +class HOST_SW_API SetupApplication; +class HOST_SW_API SetupMainWnd; + +class HOST_SW_API AdjustWndBase; +class HOST_SW_API CloseWndBase; +class HOST_SW_API LevelAdjustWnd; +class HOST_SW_API SymbLevelAdjustWnd; +class HOST_SW_API HSBandwidthAdjustWnd; +class HOST_SW_API HSGainAdjustWnd; +class HOST_SW_API FIFOBufferAdjustWnd; + +class HOST_SW_API LoggingAndDataSetWnd; +class HOST_SW_API SystemConfigSetWnd; +class HOST_SW_API SiteParamSetWnd; +class HOST_SW_API ChannelConfigSetWnd; +#endif // defined(ENABLE_SETUP_TUI) + // --------------------------------------------------------------------------- // Class Declaration // This class implements an interactive session to setup the DVM. @@ -44,37 +88,161 @@ public: /// Initializes a new instance of the HostSetup class. HostSetup(const std::string& confFile); /// Finalizes a instance of the HostSetup class. - ~HostSetup(); + virtual ~HostSetup(); +#if defined(ENABLE_SETUP_TUI) /// Executes the processing loop. - int run(); + virtual int run(int argc, char** argv); +#else + /// Executes the processing loop. + virtual int run(int argc, char **argv) = 0; +#endif // defined(ENABLE_SETUP_TUI) + +protected: +#if defined(ENABLE_SETUP_TUI) + friend class SetupApplication; + friend class SetupMainWnd; -private: - const std::string& m_confFile; + friend class AdjustWndBase; + friend class CloseWndBase; + friend class LevelAdjustWnd; + friend class SymbLevelAdjustWnd; + friend class HSBandwidthAdjustWnd; + friend class HSGainAdjustWnd; + friend class FIFOBufferAdjustWnd; + + friend class LoggingAndDataSetWnd; + friend class SystemConfigSetWnd; + friend class SiteParamSetWnd; + friend class ChannelConfigSetWnd; + + SetupMainWnd* m_setupWnd; +#endif // defined(ENABLE_SETUP_TUI) + const std::string &m_confFile; yaml::Node m_conf; - Console m_console; + StopWatch m_stopWatch; + modem::Modem *m_modem; + + RingBuffer m_queue; + + edac::AMBEFEC m_fec; + bool m_transmit; bool m_duplex; - uint32_t m_rxFrequency; - uint32_t m_txFrequency; + bool m_dmrEnabled; + bool m_dmrRx1K; + bool m_p25Enabled; + bool m_p25Rx1K; + bool m_p25TduTest; + bool m_nxdnEnabled; + + bool m_isHotspot; + + bool m_isConnected; + bool m_debug; + + uint8_t m_mode; + std::string m_modeStr; + + uint32_t m_rxFrequency; // hotspot modem - Rx Frequency + uint32_t m_rxAdjustedFreq; + uint32_t m_txFrequency; // hotspot modem - Tx Frequency + uint32_t m_txAdjustedFreq; uint8_t m_channelId; uint32_t m_channelNo; lookups::IdenTableLookup* m_idenTable; - /// Helper to print the help to the console. - void displayHelp(); + uint32_t m_berFrames; + uint32_t m_berBits; + uint32_t m_berErrs; + uint32_t m_berUndecodableLC; + uint32_t m_berUncorrectable; + + uint32_t m_timeout; + uint32_t m_timer; + + bool m_updateConfigFromModem; + bool m_hasFetchedStatus; + bool m_requestedStatus; + /// Modem port open callback. + bool portModemOpen(modem::Modem* modem); + /// Modem port close callback. + bool portModemClose(modem::Modem* modem); + /// Modem clock callback. + bool portModemHandler(modem::Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM rspType, bool rspDblLen, const uint8_t* buffer, uint16_t len); + + /// Helper to save configuration. + void saveConfig(); /// Helper to calculate the Rx/Tx frequencies. - bool calculateRxTxFreq(); + bool calculateRxTxFreq(bool consoleDisplay = false); + /// Helper to log the system configuration parameters. + void displayConfigParams(); + /// Initializes the modem DSP. + bool createModem(bool consoleDisplay = false); + /// Helper to toggle modem transmit mode. + bool setTransmit(); + + /// Helper to update BER display window. + void updateTUIBER(float ber); + /// Process DMR Rx BER. + void processDMRBER(const uint8_t* buffer, uint8_t seq); + /// Process DMR Tx 1011hz BER. + void processDMR1KBER(const uint8_t* buffer, uint8_t seq); + /// Process P25 Rx BER. + void processP25BER(const uint8_t* buffer); + /// Process P25 Tx 1011hz BER. + void processP251KBER(const uint8_t* buffer); + /// Process NXDN Rx BER. + void processNXDNBER(const uint8_t* buffer); - /// Helper to sleep the thread. + /// Write configuration to the modem DSP. + bool writeConfig(); + /// Write configuration to the modem DSP. + bool writeConfig(uint8_t modeOverride); + /// Write RF parameters to the air interface modem. + bool writeRFParams(); + /// Write symbol level adjustments to the modem DSP. + bool writeSymbolAdjust(); + /// Write transmit FIFO buffer lengths. + bool writeFifoLength(); + /// Helper to sleep the calibration thread. void sleep(uint32_t ms); + /// Read the configuration area on the air interface modem. + bool readFlash(); + /// Process the configuration data from the air interface modem. + void processFlashConfig(const uint8_t *buffer); + /// Erase the configuration area on the air interface modem. + bool eraseFlash(); + /// Write the configuration area on the air interface modem. + bool writeFlash(); + + /// Helper to clock the calibration BER timer. + void timerClock(); + /// Helper to start the calibration BER timer. + void timerStart(); + /// Helper to stop the calibration BER timer. + void timerStop(); + + /// Retrieve the current status from the air interface modem. + void getStatus(); +#if defined(ENABLE_SETUP_TUI) /// Prints the current status. - void printStatus(); + virtual void printStatus(); +#else + /// Prints the current status. + virtual void printStatus() = 0; +#endif // defined(ENABLE_SETUP_TUI) + + /// Add data frame to the data ring buffer. + void addFrame(const uint8_t* data, uint32_t length, uint32_t maxFrameSize); + + /// Counts the total number of bit errors between bytes. + uint8_t countErrs(uint8_t a, uint8_t b); }; #endif // __HOST_SETUP_H__ diff --git a/src/host/setup/LevelAdjustWnd.h b/src/host/setup/LevelAdjustWnd.h new file mode 100644 index 00000000..7ccc1e8e --- /dev/null +++ b/src/host/setup/LevelAdjustWnd.h @@ -0,0 +1,319 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__LEVEL_ADJUST_WND_H__) +#define __LEVEL_ADJUST_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/AdjustWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the modem level adjustment window. +// --------------------------------------------------------------------------- + +class HOST_SW_API LevelAdjustWnd final : public AdjustWndBase +{ +public: + /// + /// Initializes a new instance of the LevelAdjustWnd class. + /// + /// + /// + explicit LevelAdjustWnd(HostSetup* setup, FWidget* widget = nullptr) : AdjustWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_softwareLevelLabel{"Software Levels", this}; + FLabel m_rxLevelLabel{"Rx Level: ", this}; + FLabel m_rxDCOffsetLabel{"Rx DC Offset: ", this}; + FLabel m_txLevelLabel{"Tx Level: ", this}; + FLabel m_txDCOffsetLabel{"Tx DC Offset: ", this}; + + FLabel m_softpotLevelLabel{"Softpot Levels", this}; + FLabel m_rxCoarseLabel{"Rx Coarse: ", this}; + FLabel m_rxFineLabel{"Rx Fine: ", this}; + FLabel m_txCoarseLabel{"Tx Coarse: ", this}; + FLabel m_rssiCoarseLabel{"RSSI Coarse: ", this}; + + FLabel m_digitalTimingLabel{"Digital Timing", this}; + FLabel m_fdmaPreambleLabel{"FDMA Preambles: ", this}; + FLabel m_dmrRxDelayLabel{"DMR Rx Delay: ", this}; + FLabel m_p25CorrCountLabel{"P25 Corr. Count: ", this}; + + FLabel m_freqAdjustLabel{"Hotspot Frequency Offset", this}; + FLabel m_rxFreqAdjLabel{"Rx Freq. Offset: ", this}; + FLabel m_txFreqAdjLabel{"Tx Freq. Offset: ", this}; + + FSpinBox m_rxLevel{this}; + FSpinBox m_rxDCOffsetLevel{this}; + FSpinBox m_txLevel{this}; + FSpinBox m_txDCOffsetLevel{this}; + + FSpinBox m_rxCoarseLevel{this}; + FSpinBox m_rxFineLevel{this}; + FSpinBox m_txCoarseLevel{this}; + FSpinBox m_rssiCoarseLevel{this}; + + FSpinBox m_fdmaPreambles{this}; + FSpinBox m_dmrRxDelay{this}; + FSpinBox m_p25CorrCount{this}; + + FSpinBox m_rxTuning{this}; + FSpinBox m_txTuning{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Modem Level Adjustment"); + FDialog::setSize(FSize{65, 22}); + + AdjustWndBase::initLayout(); + } + + /// + /// + /// + void initControls() override + { + // software levels + { + m_softwareLevelLabel.setGeometry(FPoint(2, 1), FSize(20, 2)); + m_softwareLevelLabel.setEmphasis(); + m_softwareLevelLabel.setAlignment(Align::Center); + + m_rxLevelLabel.setGeometry(FPoint(2, 3), FSize(20, 1)); + m_rxLevel.setGeometry(FPoint(18, 3), FSize(10, 1)); + m_rxLevel.setRange(0, 100); + m_rxLevel.setValue(m_setup->m_modem->m_rxLevel); + m_rxLevel.setShadow(false); + m_rxLevel.addCallback("changed", [&]() { + m_setup->m_modem->m_rxLevel = m_rxLevel.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_rxDCOffsetLabel.setGeometry(FPoint(2, 4), FSize(20, 1)); + m_rxDCOffsetLevel.setGeometry(FPoint(18, 4), FSize(10, 1)); + m_rxDCOffsetLevel.setRange(-127, 127); + m_rxDCOffsetLevel.setValue(m_setup->m_modem->m_rxDCOffset); + m_rxDCOffsetLevel.setShadow(false); + m_rxDCOffsetLevel.addCallback("changed", [&]() { + m_setup->m_modem->m_rxDCOffset = m_rxDCOffsetLevel.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_txLevelLabel.setGeometry(FPoint(2, 5), FSize(20, 1)); + m_txLevel.setGeometry(FPoint(18, 5), FSize(10, 1)); + m_txLevel.setRange(0, 100); + m_txLevel.setValue(m_setup->m_modem->m_cwIdTXLevel); + m_txLevel.setShadow(false); + m_txLevel.addCallback("changed", [&]() { + m_setup->m_modem->m_cwIdTXLevel = m_txLevel.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_txDCOffsetLabel.setGeometry(FPoint(2, 6), FSize(20, 1)); + m_txDCOffsetLevel.setGeometry(FPoint(18, 6), FSize(10, 1)); + m_txDCOffsetLevel.setRange(-127, 127); + m_txDCOffsetLevel.setValue(m_setup->m_modem->m_txDCOffset); + m_txDCOffsetLevel.setShadow(false); + m_txDCOffsetLevel.addCallback("changed", [&]() { + m_setup->m_modem->m_txDCOffset = m_txDCOffsetLevel.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + } + + // digital timing + { + m_digitalTimingLabel.setGeometry(FPoint(32, 1), FSize(20, 2)); + m_digitalTimingLabel.setEmphasis(); + m_digitalTimingLabel.setAlignment(Align::Center); + + m_fdmaPreambleLabel.setGeometry(FPoint(32, 3), FSize(20, 1)); + m_fdmaPreambles.setGeometry(FPoint(52, 3), FSize(10, 1)); + m_fdmaPreambles.setRange(0, 255); + m_fdmaPreambles.setValue(m_setup->m_modem->m_fdmaPreamble); + m_fdmaPreambles.setShadow(false); + m_fdmaPreambles.addCallback("changed", [&]() { + m_setup->m_modem->m_fdmaPreamble = m_fdmaPreambles.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_dmrRxDelayLabel.setGeometry(FPoint(32, 4), FSize(20, 1)); + m_dmrRxDelay.setGeometry(FPoint(52, 4), FSize(10, 1)); + m_dmrRxDelay.setRange(0, 255); + m_dmrRxDelay.setValue(m_setup->m_modem->m_dmrRxDelay); + m_dmrRxDelay.setShadow(false); + m_dmrRxDelay.addCallback("changed", [&]() { + m_setup->m_modem->m_dmrRxDelay = m_dmrRxDelay.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_p25CorrCountLabel.setGeometry(FPoint(32, 5), FSize(20, 1)); + m_p25CorrCount.setGeometry(FPoint(52, 5), FSize(10, 1)); + m_p25CorrCount.setRange(0, 255); + m_p25CorrCount.setValue(m_setup->m_modem->m_p25CorrCount); + m_p25CorrCount.setShadow(false); + m_p25CorrCount.addCallback("changed", [&]() { + m_setup->m_modem->m_p25CorrCount = m_p25CorrCount.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + } + + // softpot levels + { + m_softpotLevelLabel.setGeometry(FPoint(2, 8), FSize(20, 2)); + m_softpotLevelLabel.setEmphasis(); + m_softpotLevelLabel.setAlignment(Align::Center); + + m_rxCoarseLabel.setGeometry(FPoint(2, 10), FSize(20, 1)); + m_rxCoarseLevel.setGeometry(FPoint(18, 10), FSize(10, 1)); + m_rxCoarseLevel.setRange(-127, 127); + m_rxCoarseLevel.setValue(m_setup->m_modem->m_rxCoarsePot); + m_rxCoarseLevel.setShadow(false); + m_rxCoarseLevel.addCallback("changed", [&]() { + m_setup->m_modem->m_rxCoarsePot = m_rxCoarseLevel.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_rxFineLabel.setGeometry(FPoint(2, 11), FSize(20, 1)); + m_rxFineLevel.setGeometry(FPoint(18, 11), FSize(10, 1)); + m_rxFineLevel.setRange(-127, 127); + m_rxFineLevel.setValue(m_setup->m_modem->m_rxFinePot); + m_rxFineLevel.setShadow(false); + m_rxFineLevel.addCallback("changed", [&]() { + m_setup->m_modem->m_rxFinePot = m_rxFineLevel.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_txCoarseLabel.setGeometry(FPoint(2, 12), FSize(20, 1)); + m_txCoarseLevel.setGeometry(FPoint(18, 12), FSize(10, 1)); + m_txCoarseLevel.setRange(-127, 127); + m_txCoarseLevel.setValue(m_setup->m_modem->m_txCoarsePot); + m_txCoarseLevel.setShadow(false); + m_txCoarseLevel.addCallback("changed", [&]() { + m_setup->m_modem->m_txCoarsePot = m_txCoarseLevel.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_rssiCoarseLabel.setGeometry(FPoint(2, 13), FSize(20, 1)); + m_rssiCoarseLevel.setGeometry(FPoint(18, 13), FSize(10, 1)); + m_rssiCoarseLevel.setRange(-127, 127); + m_rssiCoarseLevel.setValue(m_setup->m_modem->m_rssiCoarsePot); + m_rssiCoarseLevel.setShadow(false); + m_rssiCoarseLevel.addCallback("changed", [&]() { + m_setup->m_modem->m_rssiCoarsePot = m_rssiCoarseLevel.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + } + + // frequency offset + { + m_freqAdjustLabel.setGeometry(FPoint(32, 8), FSize(30, 2)); + m_freqAdjustLabel.setEmphasis(); + m_freqAdjustLabel.setAlignment(Align::Center); + + m_rxFreqAdjLabel.setGeometry(FPoint(32, 10), FSize(20, 1)); + m_rxTuning.setGeometry(FPoint(52, 10), FSize(10, 1)); + m_rxTuning.setRange(-1000, 1000); + m_rxTuning.setValue(m_setup->m_modem->m_rxTuning); + m_rxTuning.setShadow(false); + m_rxTuning.addCallback("changed", [&]() { + m_setup->m_modem->m_rxTuning = m_rxTuning.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + + m_txFreqAdjLabel.setGeometry(FPoint(32, 12), FSize(20, 1)); + m_txTuning.setGeometry(FPoint(52, 12), FSize(10, 1)); + m_txTuning.setRange(-1000, 1000); + m_txTuning.setValue(m_setup->m_modem->m_txTuning); + m_txTuning.setShadow(false); + m_txTuning.addCallback("changed", [&]() { + m_setup->m_modem->m_txTuning = m_txTuning.getValue(); + Thread::sleep(2); + m_setup->writeRFParams(); + }); + } + + // setup control states + if (m_setup->m_isConnected) { + if (m_setup->m_modem->m_isHotspot) { + m_p25CorrCount.setDisable(); + + m_rxLevel.setDisable(); + m_rxDCOffsetLevel.setDisable(); + m_txDCOffsetLevel.setDisable(); + + m_rxCoarseLevel.setDisable(); + m_rxFineLevel.setDisable(); + m_txCoarseLevel.setDisable(); + m_rssiCoarseLevel.setDisable(); + + m_rxTuning.setEnable(); + m_txTuning.setEnable(); + } + else { + m_p25CorrCount.setEnable(); + + m_rxLevel.setEnable(); + m_rxDCOffsetLevel.setEnable(); + m_txDCOffsetLevel.setEnable(); + + m_rxCoarseLevel.setEnable(); + m_rxFineLevel.setEnable(); + m_txCoarseLevel.setEnable(); + m_rssiCoarseLevel.setEnable(); + + m_rxTuning.setDisable(); + m_txTuning.setDisable(); + } + } + + AdjustWndBase::initControls(); + } +}; + +#endif // __LEVEL_ADJUST_WND_H__ \ No newline at end of file diff --git a/src/host/setup/LogDisplayWnd.h b/src/host/setup/LogDisplayWnd.h new file mode 100644 index 00000000..c599d68e --- /dev/null +++ b/src/host/setup/LogDisplayWnd.h @@ -0,0 +1,132 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__LOG_DISPLAY_WND_H__) +#define __LOG_DISPLAY_WND_H__ + +#include "host/setup/HostSetup.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the log display window. +// --------------------------------------------------------------------------- + +class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream +{ +public: + /// + /// Initializes a new instance of the LogDisplayWnd class. + /// + /// + explicit LogDisplayWnd(FWidget* widget = nullptr) : FDialog{widget} + { + m_scrollText.ignorePadding(); + + m_timerId = addTimer(250); // starts the timer every 250 milliseconds + } + /// Copy constructor. + LogDisplayWnd(const LogDisplayWnd&) = delete; + /// Move constructor. + LogDisplayWnd(LogDisplayWnd&&) noexcept = delete; + /// Finalizes an instance of the LogDisplayWnd class. + ~LogDisplayWnd() noexcept override = default; + + /// Disable copy assignment operator (=). + auto operator= (const LogDisplayWnd&) -> LogDisplayWnd& = delete; + /// Disable move assignment operator (=). + auto operator= (LogDisplayWnd&&) noexcept -> LogDisplayWnd& = delete; + + /// Disable set X coordinate. + void setX(int, bool = true) override { } + /// Disable set Y coordinate. + void setY(int, bool = true) override { } + /// Disable set position. + void setPos(const FPoint&, bool = true) override { } + +private: + FTextView m_scrollText{this}; + int m_timerId; + + /// + /// + /// + void initLayout() override + { + using namespace std::string_literals; + auto lightning = "\u26a1"; + FDialog::setText("System Log"s + lightning); + + std::size_t maxWidth; + const auto& rootWidget = getRootWidget(); + + if (rootWidget) { + maxWidth = rootWidget->getClientWidth() - 3; + } + else { + // fallback to xterm default size + maxWidth = 77; + } + + FDialog::setGeometry(FPoint{2, 2}, FSize{maxWidth, 20}); + FDialog::setMinimumSize(FSize{80, 5}); + FDialog::setResizeable(false); + FDialog::setMinimizable(false); + FDialog::setTitlebarButtonVisibility(false); + FDialog::setShadow(); + + m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1}); + + FDialog::initLayout(); + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + void onTimer(FTimerEvent* timer) override + { + if (timer != nullptr) { + if (timer->getTimerId() == m_timerId) { + if (str().empty()) { + return; + } + + m_scrollText.append(str()); + str(""); + m_scrollText.scrollToEnd(); + redraw(); + } + } + } +}; + +#endif // __LOG_DISPLAY_WND_H__ \ No newline at end of file diff --git a/src/host/setup/LoggingAndDataSetWnd.h b/src/host/setup/LoggingAndDataSetWnd.h new file mode 100644 index 00000000..c27387f6 --- /dev/null +++ b/src/host/setup/LoggingAndDataSetWnd.h @@ -0,0 +1,170 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__LOGGING_AND_DATA_SET_WND_H__) +#define __LOGGING_AND_DATA_SET_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/CloseWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the logging and data configuration window. +// --------------------------------------------------------------------------- + +class HOST_SW_API LoggingAndDataSetWnd final : public CloseWndBase +{ +public: + /// + /// Initializes a new instance of the LoggingAndDataSetWnd class. + /// + /// + /// + explicit LoggingAndDataSetWnd(HostSetup* setup, FWidget* widget = nullptr) : CloseWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_loggingLabel{"Logging", this}; + FLabel m_dataLabel{"Data Paths", this}; + + FLabel m_logFilePathLabel{"Log File Path: ", this}; + FLineEdit m_logFilePath{this}; + FLabel m_actFilePathLabel{"Activity File Path: ", this}; + FLineEdit m_actFilePath{this}; + FLabel m_logLevelLabel{"Logging Level (1-6 lowest): ", this}; + FSpinBox m_logLevel{this}; + + FLabel m_chIdTablePathLabel{"Ch. Identity Table File Path: ", this}; + FLineEdit m_chIdTablePath{this}; + FLabel m_radioIdPathLabel{"Radio ID ACL File Path: ", this}; + FLineEdit m_radioIdPath{this}; + FLabel m_tgIdPathLabel{"Talkgroup ACL File Path: ", this}; + FLineEdit m_tgIdPath{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Logging and Data Configuration"); + FDialog::setSize(FSize{68, 19}); + + m_enableSetButton = false; + CloseWndBase::initLayout(); + } + + /// + /// + /// + void initControls() + { + yaml::Node logConf = m_setup->m_conf["log"]; + uint32_t logLevel = logConf["fileLevel"].as(1U); + std::string logFilePath = logConf["filePath"].as(); + std::string logActFilePath = logConf["activityFilePath"].as(); + + // logging + { + m_loggingLabel.setGeometry(FPoint(2, 1), FSize(20, 2)); + m_loggingLabel.setEmphasis(); + m_loggingLabel.setAlignment(Align::Center); + + m_logFilePathLabel.setGeometry(FPoint(2, 3), FSize(30, 1)); + m_logFilePath.setGeometry(FPoint(33, 3), FSize(32, 1)); + m_logFilePath.setText(logFilePath); + m_logFilePath.setShadow(false); + m_logFilePath.addCallback("changed", [&]() { + m_setup->m_conf["log"]["filePath"] = m_logFilePath.getText().toString(); + }); + + m_actFilePathLabel.setGeometry(FPoint(2, 4), FSize(30, 1)); + m_actFilePath.setGeometry(FPoint(33, 4), FSize(32, 1)); + m_actFilePath.setText(logActFilePath); + m_actFilePath.setShadow(false); + m_actFilePath.addCallback("changed", [&]() { + m_setup->m_conf["log"]["activityFilePath"] = m_actFilePath.getText().toString(); + }); + + m_logLevelLabel.setGeometry(FPoint(2, 5), FSize(30, 1)); + m_logLevel.setGeometry(FPoint(33, 5), FSize(10, 1)); + m_logLevel.setRange(1, 6); + m_logLevel.setValue(logLevel); + m_logLevel.setShadow(false); + m_logLevel.addCallback("changed", [&]() { + m_setup->m_conf["log"]["displayLevel"] = __INT_STR(m_logLevel.getValue()); + m_setup->m_conf["log"]["fileLevel"] = __INT_STR(m_logLevel.getValue()); + }); + } + + yaml::Node idenTable = m_setup->m_conf["system"]["iden_table"]; + std::string idenFilePath = idenTable["file"].as(); + yaml::Node radioId = m_setup->m_conf["system"]["radio_id"]; + std::string ridFilePath = radioId["file"].as(); + yaml::Node talkgroupId = m_setup->m_conf["system"]["talkgroup_id"]; + std::string tgidFilePath = talkgroupId["file"].as(); + + // data paths + { + m_dataLabel.setGeometry(FPoint(2, 7), FSize(20, 2)); + m_dataLabel.setEmphasis(); + m_dataLabel.setAlignment(Align::Center); + + m_chIdTablePathLabel.setGeometry(FPoint(2, 9), FSize(30, 1)); + m_chIdTablePath.setGeometry(FPoint(33, 9), FSize(32, 1)); + m_chIdTablePath.setText(idenFilePath); + m_chIdTablePath.setShadow(false); + m_chIdTablePath.addCallback("changed", [&]() { + m_setup->m_conf["system"]["iden_table"]["file"] = m_chIdTablePath.getText().toString(); + }); + + m_radioIdPathLabel.setGeometry(FPoint(2, 10), FSize(30, 1)); + m_radioIdPath.setGeometry(FPoint(33, 10), FSize(32, 1)); + m_radioIdPath.setText(ridFilePath); + m_radioIdPath.setShadow(false); + m_radioIdPath.addCallback("changed", [&]() { + m_setup->m_conf["system"]["radio_id"]["file"] = m_radioIdPath.getText().toString(); + }); + + m_tgIdPathLabel.setGeometry(FPoint(2, 11), FSize(30, 1)); + m_tgIdPath.setGeometry(FPoint(33, 11), FSize(32, 1)); + m_tgIdPath.setText(tgidFilePath); + m_tgIdPath.setShadow(false); + m_tgIdPath.addCallback("changed", [&]() { + m_setup->m_conf["system"]["talkgroup_id"]["file"] = m_tgIdPath.getText().toString(); + }); + } + + CloseWndBase::initControls(); + } +}; + +#endif // __LOGGING_AND_DATA_SET_WND_H__ \ No newline at end of file diff --git a/src/host/setup/ModemStatusWnd.h b/src/host/setup/ModemStatusWnd.h new file mode 100644 index 00000000..c50fc692 --- /dev/null +++ b/src/host/setup/ModemStatusWnd.h @@ -0,0 +1,122 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__MODEM_STATUS_WND_H__) +#define __MODEM_STATUS_WND_H__ + +#include "host/setup/HostSetup.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the modem status display window. +// --------------------------------------------------------------------------- + +class HOST_SW_API ModemStatusWnd final : public finalcut::FDialog +{ +public: + /// + /// Initializes a new instance of the ModemStatusWnd class. + /// + /// + explicit ModemStatusWnd(FWidget* widget = nullptr) : FDialog{widget} + { + m_scrollText.ignorePadding(); + } + /// Copy constructor. + ModemStatusWnd(const ModemStatusWnd&) = delete; + /// Move constructor. + ModemStatusWnd(ModemStatusWnd&&) noexcept = delete; + /// Finalizes an instance of the ModemStatusWnd class. + ~ModemStatusWnd() noexcept override = default; + + /// Disable copy assignment operator (=). + auto operator= (const ModemStatusWnd&) -> ModemStatusWnd& = delete; + /// Disable move assignment operator (=). + auto operator= (ModemStatusWnd&&) noexcept -> ModemStatusWnd& = delete; + + /// Disable set X coordinate. + void setX(int, bool = true) override { } + /// Disable set Y coordinate. + void setY(int, bool = true) override { } + /// Disable set position. + void setPos(const FPoint&, bool = true) override { } + + /// + /// Helper to append text. + /// + void append(std::string str) + { + if (str.empty()) { + return; + } + + m_scrollText.append(str); + m_scrollText.scrollToEnd(); + redraw(); + } + + /// + /// Helper to clear the text. + /// + void clear() { m_scrollText.clear(); } + +private: + FTextView m_scrollText{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Modem Status (update every 1s)"); + + std::size_t maxWidth; + const auto& rootWidget = getRootWidget(); + + if (rootWidget) { + maxWidth = rootWidget->getClientWidth() - 3; + } + else { + // fallback to xterm default size + maxWidth = 77; + } + + FDialog::setGeometry(FPoint{2, 23}, FSize{maxWidth, 25}); + FDialog::setMinimumSize(FSize{80, 5}); + FDialog::setResizeable(false); + FDialog::setMinimizable(false); + FDialog::setTitlebarButtonVisibility(false); + FDialog::setShadow(); + + m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1}); + + FDialog::initLayout(); + } +}; + +#endif // __MODEM_STATUS_WND_H__ \ No newline at end of file diff --git a/src/host/setup/SetupApplication.h b/src/host/setup/SetupApplication.h new file mode 100644 index 00000000..da4ade79 --- /dev/null +++ b/src/host/setup/SetupApplication.h @@ -0,0 +1,126 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__SETUP_APPLICATION_H__) +#define __SETUP_APPLICATION_H__ + +#include "host/setup/HostSetup.h" +#include "host/setup/SetupMainWnd.h" +#include "Log.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the finalcut application. +// --------------------------------------------------------------------------- + +class HOST_SW_API SetupApplication final : public finalcut::FApplication { +public: + /// + /// Initializes a new instance of the SetupWnd class. + /// + /// + /// + /// + explicit SetupApplication(HostSetup* setup, const int& argc, char** argv) : FApplication{argc, argv}, + m_setup(setup) + { + m_statusRefreshTimer = addTimer(1000); + } + +protected: + /// + /// + /// + virtual void processExternalUserEvent() + { + if (m_setup->m_p25TduTest && m_setup->m_queue.hasSpace(p25::P25_TDU_FRAME_LENGTH_BYTES + 2U)) { + uint8_t data[p25::P25_TDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, p25::P25_TDU_FRAME_LENGTH_BYTES); + + // Generate Sync + p25::Sync::addP25Sync(data + 2U); + + // Generate NID + std::unique_ptr nid = new_unique(p25::NID, 1U); + nid->encode(data + 2U, p25::P25_DUID_TDU); + + // Add busy bits + p25::P25Utils::addBusyBits(data + 2U, p25::P25_TDU_FRAME_LENGTH_BITS, true, true); + + data[0U] = modem::TAG_EOT; + data[1U] = 0x00U; + + m_setup->addFrame(data, p25::P25_TDU_FRAME_LENGTH_BYTES + 2U, p25::P25_LDU_FRAME_LENGTH_BYTES); + } + + // ------------------------------------------------------ + // -- Modem Clocking -- + // ------------------------------------------------------ + + uint32_t ms = m_setup->m_stopWatch.elapsed(); + m_setup->m_stopWatch.start(); + + m_setup->m_modem->clock(ms); + + m_setup->timerClock(); + + if (ms < 2U) + Thread::sleep(1U); + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + void onTimer(FTimerEvent* timer) override + { + if (timer != nullptr) { + if (timer->getTimerId() == m_statusRefreshTimer) { + m_setup->m_setupWnd->updateMenuStates(); + + // display modem status + if (m_setup->m_isConnected) { + if (m_setup->m_setupWnd->m_statusWnd.isShown()) { + m_setup->printStatus(); + } + } + } + } + } + +private: + HostSetup* m_setup; + + int m_statusRefreshTimer; +}; + +#endif // __SETUP_APPLICATION_H__ \ No newline at end of file diff --git a/src/host/setup/SetupMainWnd.h b/src/host/setup/SetupMainWnd.h new file mode 100644 index 00000000..475811ff --- /dev/null +++ b/src/host/setup/SetupMainWnd.h @@ -0,0 +1,761 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__SETUP_WND_H__) +#define __SETUP_WND_H__ + +#include "modem/Modem.h" +#include "host/setup/HostSetup.h" +#include "Log.h" +#include "Thread.h" + +using namespace modem; + +#include "host/setup/LogDisplayWnd.h" +#include "host/setup/ModemStatusWnd.h" +#include "host/setup/BERDisplayWnd.h" + +#include "host/setup/LevelAdjustWnd.h" +#include "host/setup/SymbLevelAdjustWnd.h" +#include "host/setup/HSBandwidthAdjustWnd.h" +#include "host/setup/HSGainAdjustWnd.h" +#include "host/setup/FIFOBufferAdjustWnd.h" + +#include "host/setup/LoggingAndDataSetWnd.h" +#include "host/setup/SystemConfigSetWnd.h" +#include "host/setup/SiteParamSetWnd.h" +#include "host/setup/ChannelConfigSetWnd.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Prototypes +// --------------------------------------------------------------------------- + +class HOST_SW_API SetupApplication; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the root window control. +// --------------------------------------------------------------------------- + +class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { +public: + /// + /// Initializes a new instance of the SetupMainWnd class. + /// + /// + /// + explicit SetupMainWnd(HostSetup* setup, FWidget* widget = nullptr) : FWidget{widget}, + m_setup(setup) + { + __InternalOutputStream(m_logWnd); + m_statusWnd.hide(); + + resetBERWnd(); + + // file menu + m_fileMenuSeparator1.setSeparator(); + m_fileMenuSeparator2.setSeparator(); + m_connectToModemItem.addAccelerator(FKey::Meta_c); // Meta/Alt + C + m_connectToModemItem.addCallback("clicked", this, &SetupMainWnd::cb_connectToModemClick); + m_keyF8.addCallback("activate", this, &SetupMainWnd::cb_connectToModemClick); + m_saveSettingsItem.addAccelerator(FKey::Meta_s); // Meta/Alt + S + m_saveSettingsItem.addCallback("clicked", this, [&]() { m_setup->saveConfig(); }); + m_keyF2.addCallback("activate", this, [&]() { m_setup->saveConfig(); }); + m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X + m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this); + m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this); + m_keyF12.addCallback("activate", this, [&]() { + if (m_setup->m_isConnected) { + if (!m_setup->setTransmit()) { + FMessageBox::error(this, "Failed to enable modem transmit!"); + } + } + }); + + // setup menu + m_setupMenuSeparator1.setSeparator(); + m_setupMenuSeparator2.setSeparator(); + m_setLoggingDataConfig.addCallback("clicked", this, [&]() { + FMessageBox::error(this, "NOTE: These settings will take effect on restart of dvmhost."); + LoggingAndDataSetWnd wnd{m_setup, this}; + wnd.show(); + }); + m_systemConfig.addCallback("clicked", this, [&]() { + SystemConfigSetWnd wnd{m_setup, this}; + wnd.show(); + }); + m_siteParams.addCallback("clicked", this, [&]() { + SiteParamSetWnd wnd{m_setup, this}; + wnd.show(); + }); + m_chConfig.addCallback("clicked", this, [&]() { + ChannelConfigSetWnd wnd{m_setup, this}; + wnd.show(); + }); + + // calibrate menu + m_calibrateMenuSeparator1.setSeparator(); + m_calibrateMenuSeparator2.setSeparator(); + m_dmrCal.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_DMR_CAL; + m_setup->m_modeStr = DMR_CAL_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_p25Cal.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_P25_CAL; + m_setup->m_modeStr = P25_CAL_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_dmrLFCal.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_DMR_LF_CAL; + m_setup->m_modeStr = DMR_LF_CAL_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_dmrCal1K.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_DMR_CAL_1K; + m_setup->m_modeStr = DMR_CAL_1K_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_dmrDMOCal1K.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_DMR_DMO_CAL_1K; + m_setup->m_modeStr = DMR_DMO_CAL_1K_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_p25Cal1K.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_P25_CAL_1K; + m_setup->m_modeStr = P25_CAL_1K_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_p25TDUTest.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_P25; + m_setup->m_modeStr = P25_TDU_TEST_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_p25Enabled = true; + m_setup->m_p25TduTest = true; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(); + + m_setup->m_queue.clear(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_nxdnCal1K.addCallback("toggled", [&]() { + // are we on a protocol version 3 firmware? + if (m_setup->m_modem->getVersion() >= 3U) { + m_setup->m_mode = STATE_NXDN_CAL; + m_setup->m_modeStr = NXDN_CAL_1K_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + } + else { + FMessageBox::error(this, NXDN_CAL_1K_STR " test mode is not supported on your firmware!"); + } + }); + m_dmrFEC.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_DMR; + m_setup->m_modeStr = DMR_FEC_STR; + m_setup->m_dmrRx1K = false; +/* + if (c == ')') { + m_setup->m_duplex = true; + } else { + m_setup->m_duplex = false; + } +*/ + m_setup->m_duplex = false; + m_setup->m_dmrEnabled = true; + m_setup->m_p25Enabled = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(true); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_dmrFEC1K.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_DMR; + m_setup->m_modeStr = DMR_FEC_1K_STR; + m_setup->m_dmrRx1K = true; +/* + if (c == ')') { + m_setup->m_duplex = true; + } else { + m_setup->m_duplex = false; + } +*/ + m_setup->m_duplex = false; + m_setup->m_dmrEnabled = true; + m_setup->m_p25Enabled = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(true); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_p25FEC.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_P25; + m_setup->m_modeStr = P25_FEC_STR; + m_setup->m_p25Rx1K = false; + m_setup->m_duplex = false; + m_setup->m_dmrEnabled = false; + m_setup->m_p25Enabled = true; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(true); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_p25FEC1K.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_P25; + m_setup->m_modeStr = P25_FEC_1K_STR; + m_setup->m_p25Rx1K = true; + m_setup->m_duplex = false; + m_setup->m_dmrEnabled = false; + m_setup->m_p25Enabled = true; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + + resetBERWnd(true); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + m_nxdnFEC.addCallback("toggled", [&]() { + // are we on a protocol version 3 firmware? + if (m_setup->m_modem->getVersion() >= 3U) { + m_setup->m_mode = STATE_NXDN; + m_setup->m_modeStr = NXDN_FEC_STR; + m_setup->m_duplex = false; + m_setup->m_dmrEnabled = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = true; + + resetBERWnd(true); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + } + else { + FMessageBox::error(this, NXDN_FEC_STR " test mode is not supported on your firmware!"); + } + }); + m_rssiCal.addCallback("toggled", [&]() { + m_setup->m_mode = STATE_RSSI_CAL; + m_setup->m_modeStr = RSSI_CAL_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + + resetBERWnd(); + + LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + m_setup->writeConfig(); + }); + + m_toggleTxInvert.addCallback("toggled", this, [&]() { + if (!m_setup->m_isHotspot) { + m_setup->m_modem->m_txInvert = !m_setup->m_modem->m_txInvert; + LogMessage(LOG_CAL, "Tx Invert: %s", m_setup->m_modem->m_txInvert ? "on" : "off"); + m_setup->writeConfig(); + } + }); + m_toggleRxInvert.addCallback("toggled", this, [&]() { + if (!m_setup->m_isHotspot) { + m_setup->m_modem->m_rxInvert = !m_setup->m_modem->m_rxInvert; + LogMessage(LOG_CAL, "Rx Invert: %s", m_setup->m_modem->m_rxInvert ? "on" : "off"); + m_setup->writeConfig(); + } + }); + m_togglePTTInvert.addCallback("toggled", this, [&]() { + if (!m_setup->m_isHotspot) { + m_setup->m_modem->m_pttInvert = !m_setup->m_modem->m_pttInvert; + LogMessage(LOG_CAL, "PTT Invert: %s", m_setup->m_modem->m_pttInvert ? "on" : "off"); + m_setup->writeConfig(); + } + }); + m_toggleDCBlocker.addCallback("toggled", this, [&]() { + if (!m_setup->m_isHotspot) { + m_setup->m_modem->m_dcBlocker = !m_setup->m_modem->m_dcBlocker; + LogMessage(LOG_CAL, "DC Blocker: %s", m_setup->m_modem->m_dcBlocker ? "on" : "off"); + m_setup->writeConfig(); + } + }); + + m_adjustLevel.addAccelerator(FKey::Meta_l); // Meta/Alt + L + m_adjustLevel.addCallback("clicked", this, [&]() { + LevelAdjustWnd wnd{m_setup, this}; + wnd.show(); + }); + m_keyF5.addCallback("activate", this, [&]() { + LevelAdjustWnd wnd{m_setup, this}; + wnd.show(); + }); + + // engineering menu + m_engineeringMenuSeparator1.setSeparator(); + m_engineeringMenuSeparator2.setSeparator(); + m_engineeringMenuSeparator3.setSeparator(); + m_adjSymLevel.addAccelerator(FKey::Meta_s); // Meta/Alt + S + m_adjSymLevel.addCallback("clicked", this, [&]() { + SymbLevelAdjustWnd wnd{m_setup, this}; + wnd.show(); + }); + + m_adjFifoBuffers.addAccelerator(FKey::Meta_f); // Meta/Alt + F + m_adjFifoBuffers.addCallback("clicked", this, [&]() { + FIFOBufferAdjustWnd wnd{m_setup, this}; + wnd.show(); + }); + + m_adjHSBandwidth.addAccelerator(FKey::Meta_b); // Meta/Alt + B + m_adjHSBandwidth.addCallback("clicked", this, [&]() { + HSBandwidthAdjustWnd wnd{m_setup, this}; + wnd.show(); + }); + m_adjHSGain.addAccelerator(FKey::Meta_g); // Meta/Alt + G + m_adjHSGain.addCallback("clicked", this, [&]() { + HSGainAdjustWnd wnd{m_setup, this}; + wnd.show(); + }); + + m_eraseConfigArea.addCallback("clicked", this, [&]() { + FMessageBox wait("", L"Wait...", + FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); + wait.setCenterText(); + wait.setModal(false); + wait.show(); + + m_setup->eraseFlash(); + wait.hide(); + }); + m_readConfigArea.addCallback("clicked", this, [&]() { + FMessageBox wait("", L"Wait...", + FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); + wait.setCenterText(); + wait.setModal(false); + wait.show(); + + m_setup->m_updateConfigFromModem = true; + m_setup->readFlash(); + wait.hide(); + }); + + m_forceHotspot.addCallback("toggled", this, [&]() { + if (m_setup->m_isConnected && !m_setup->m_isHotspot && m_forceHotspot.isChecked()) { + FMessageBox::error(this, "NOTICE: Enabling hotspot options on non-hotspot modems may result in undesired operation."); + } + m_setup->m_isHotspot = m_forceHotspot.isChecked(); + m_setup->m_modem->m_forceHotspot = m_forceHotspot.isChecked(); + m_setup->m_modem->m_isHotspot = m_forceHotspot.isChecked(); + setMenuStates(); + }); + m_modemDebug.addCallback("toggled", this, [&]() { + m_setup->m_modem->m_debug = m_modemDebug.isChecked(); + m_setup->m_debug = m_modemDebug.isChecked(); + m_setup->writeConfig(); + }); + + // help menu + m_aboutItem.addCallback("clicked", this, [&]() { + const FString line(2, UniChar::BoxDrawingsHorizontal); + FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n" + L"Version " + __VER__ + L"\n\n" + L"Copyright (c) 2017-2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n" + L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others", + FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); + info.setCenterText(); + info.show(); + }); + } + + /// + /// Helper to set menu states. + /// + void setMenuStates() + { + m_dmrCal.setChecked(); + if (m_setup->m_modem->m_debug) { + m_modemDebug.setChecked(); + } + + if (m_setup->m_modem->m_txInvert) { + m_toggleTxInvert.setChecked(); + } + + if (m_setup->m_modem->m_rxInvert) { + m_toggleRxInvert.setChecked(); + } + + if (m_setup->m_modem->m_pttInvert) { + m_togglePTTInvert.setChecked(); + } + + if (m_setup->m_modem->m_dcBlocker) { + m_toggleDCBlocker.setChecked(); + } + + updateMenuStates(); + } + + /// + /// Helper to update menu states. + /// + void updateMenuStates() + { + if (!m_setup->m_isConnected) { + m_eraseConfigArea.setDisable(); + m_readConfigArea.setDisable(); + } + else { + m_eraseConfigArea.setEnable(); + m_readConfigArea.setEnable(); + } + + if (m_setup->m_isConnected) { + if (m_setup->m_isHotspot) { + m_toggleTxInvert.setDisable(); + m_toggleRxInvert.setDisable(); + m_togglePTTInvert.setDisable(); + m_toggleDCBlocker.setDisable(); + + m_adjSymLevel.setDisable(); + m_adjHSBandwidth.setEnable(); + m_adjHSGain.setEnable(); + } + else { + m_toggleTxInvert.setEnable(); + m_toggleRxInvert.setEnable(); + m_togglePTTInvert.setEnable(); + m_toggleDCBlocker.setEnable(); + + m_adjSymLevel.setEnable(); + m_adjHSBandwidth.setDisable(); + m_adjHSGain.setDisable(); + } + } + } + + /// Gets the instance of HostSetup. + const HostSetup* setup() { return m_setup; }; + +private: + friend class HostSetup; + friend class SetupApplication; + HostSetup* m_setup; + + LogDisplayWnd m_logWnd{this}; + ModemStatusWnd m_statusWnd{this}; + BERDisplayWnd m_berWnd{this}; + + FString m_line{13, UniChar::BoxDrawingsHorizontal}; + + FMenuBar m_menuBar{this}; + + FMenu m_fileMenu{"&File", &m_menuBar}; + FMenuItem m_connectToModemItem{"&Connect to Modem", &m_fileMenu}; + FMenuItem m_fileMenuSeparator1{&m_fileMenu}; + FMenuItem m_saveSettingsItem{"&Save Settings", &m_fileMenu}; + FCheckMenuItem m_saveOnCloseToggle{"Save on Close?", &m_fileMenu}; + FMenuItem m_fileMenuSeparator2{&m_fileMenu}; + FMenuItem m_quitItem{"&Quit", &m_fileMenu}; + + FMenu m_setupMenu{"&Setup", &m_menuBar}; + FMenuItem m_setLoggingDataConfig{"&Logging & Data Configuration", &m_setupMenu}; + FMenuItem m_setupMenuSeparator1{&m_setupMenu}; + FMenuItem m_systemConfig{"&System Configuration", &m_setupMenu}; + FMenuItem m_siteParams{"Site &Parameters", &m_setupMenu}; + FMenuItem m_setupMenuSeparator2{&m_setupMenu}; + FMenuItem m_chConfig{"C&hannel Configuration", &m_setupMenu}; + + FMenu m_calibrateMenu{"&Calibrate", &m_menuBar}; + FMenu m_opMode{"Operational Mode", &m_calibrateMenu}; + FRadioMenuItem m_dmrCal{DMR_CAL_STR, &m_opMode}; + FRadioMenuItem m_p25Cal{P25_CAL_STR, &m_opMode}; + FRadioMenuItem m_dmrLFCal{DMR_LF_CAL_STR, &m_opMode}; + FRadioMenuItem m_dmrCal1K{DMR_CAL_1K_STR, &m_opMode}; + FRadioMenuItem m_dmrDMOCal1K{DMR_DMO_CAL_1K_STR, &m_opMode}; + FRadioMenuItem m_p25Cal1K{P25_CAL_1K_STR, &m_opMode}; + FRadioMenuItem m_p25TDUTest{P25_TDU_TEST_STR, &m_opMode}; + FRadioMenuItem m_nxdnCal1K{NXDN_CAL_1K_STR, &m_opMode}; + FRadioMenuItem m_dmrFEC{DMR_FEC_STR, &m_opMode}; + FRadioMenuItem m_dmrFEC1K{DMR_FEC_1K_STR, &m_opMode}; + FRadioMenuItem m_p25FEC{P25_FEC_STR, &m_opMode}; + FRadioMenuItem m_p25FEC1K{P25_FEC_1K_STR, &m_opMode}; + FRadioMenuItem m_nxdnFEC{NXDN_FEC_STR, &m_opMode}; + FRadioMenuItem m_rssiCal{RSSI_CAL_STR, &m_opMode}; + FMenuItem m_calibrateMenuSeparator1{&m_calibrateMenu}; + FCheckMenuItem m_toggleTxInvert{"Transmit Invert", &m_calibrateMenu}; + FCheckMenuItem m_toggleRxInvert{"Receive Invert", &m_calibrateMenu}; + FCheckMenuItem m_togglePTTInvert{"PTT Invert", &m_calibrateMenu}; + FCheckMenuItem m_toggleDCBlocker{"DC Blocker", &m_calibrateMenu}; + FMenuItem m_calibrateMenuSeparator2{&m_calibrateMenu}; + FMenuItem m_adjustLevel{"&Level Adjustment", &m_calibrateMenu}; + + FMenu m_engineeringMenu{"&Engineering", &m_menuBar}; + FMenuItem m_adjSymLevel{"&Symbol Level Adjustment", &m_engineeringMenu}; + FMenuItem m_adjHSBandwidth{"Hotspot &Bandwidth Adjustment", &m_engineeringMenu}; + FMenuItem m_adjHSGain{"Hotspot &Gain & AFC", &m_engineeringMenu}; + FMenuItem m_engineeringMenuSeparator1{&m_engineeringMenu}; + FMenuItem m_adjFifoBuffers{"&FIFO Buffers", &m_engineeringMenu}; + FMenuItem m_engineeringMenuSeparator3{&m_engineeringMenu}; + FMenuItem m_eraseConfigArea{"Erase Modem Configuration Area", &m_engineeringMenu}; + FMenuItem m_readConfigArea{"Read Modem Configuration Area", &m_engineeringMenu}; + FMenuItem m_engineeringMenuSeparator2{&m_engineeringMenu}; + FCheckMenuItem m_forceHotspot{"Force Hotspot Settings", &m_engineeringMenu}; + FCheckMenuItem m_modemDebug{"Modem Debug", &m_engineeringMenu}; + + FMenu m_helpMenu{"&Help", &m_menuBar}; + FMenuItem m_aboutItem{"&About", &m_helpMenu}; + + FStatusBar m_statusBar{this}; + FStatusKey m_keyF2{FKey::F2, "Save Settings", &m_statusBar}; + FStatusKey m_keyF3{FKey::F3, "Quit", &m_statusBar}; + FStatusKey m_keyF5{FKey::F5, "Level Adjustment", &m_statusBar}; + FStatusKey m_keyF8{FKey::F8, "Connect to Modem", &m_statusBar}; + FStatusKey m_keyF12{FKey::F12, "Transmit", &m_statusBar}; + + /// + /// Helper to reset the BER window to a default state. + /// + void resetBERWnd(bool show = false) + { + if (show) { + m_berWnd.show(); + } + else { + m_berWnd.hide(); + } + m_berWnd.ber("-.---"); + m_berWnd.segmentColor(FColor::LightGray); + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + void onClose(FCloseEvent* e) override + { + // if we are saving on close -- fire off the file save event + if (m_saveOnCloseToggle.isChecked()) { + m_setup->saveConfig(); + } + + if (m_setup->m_isConnected) { + if (m_setup->m_transmit) + m_setup->setTransmit(); + + m_setup->m_isConnected = false; + m_setup->m_modem->close(); + Thread::sleep(250); + } + + FApplication::closeConfirmationDialog(this, e); + } + + /* + ** Callbacks + */ + + /// + /// "Save Settings" menu item click callback. + /// + void cb_connectToModemClick() + { + if (!m_setup->m_isConnected) { + FMessageBox wait("Wait", L"Please wait...\nConnecting to modem...", + FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); + wait.setCenterText(); + wait.setModal(false); + wait.show(); + + // open modem and initialize + bool ret = m_setup->m_modem->open(); + wait.hide(); + if (!ret) { + FMessageBox::error(this, L"Failed to connect to modem!"); + return; + } + + FMessageBox initWait("Wait", L"Please wait...\nInitializing modem...", + FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this); + initWait.setCenterText(); + initWait.setModal(false); + initWait.show(); + + m_setup->readFlash(); + + m_setup->writeFifoLength(); + m_setup->writeConfig(); + m_setup->writeRFParams(); + + m_setup->getStatus(); + uint8_t timeout = 0U; + while (!m_setup->m_hasFetchedStatus) { + m_setup->m_modem->clock(0U); + + timeout++; + if (timeout >= 75U) { + break; + } + + sleep(5U); + } + + if (!m_setup->m_hasFetchedStatus) { + FMessageBox::error(this, L"Failed to get status from the modem!"); + + m_setup->m_isConnected = false; + m_connectToModemItem.setEnable(); + + m_setup->m_modem->close(); + return; + } + + m_setup->m_isConnected = true; + m_connectToModemItem.setDisable(); + + m_statusWnd.show(); + + m_setup->m_modem->m_statusTimer.start(); + m_setup->m_stopWatch.start(); + + setMenuStates(); + m_setup->printStatus(); + + // set default state + m_setup->m_mode = STATE_DMR_CAL; + m_setup->m_modeStr = DMR_CAL_STR; + m_setup->m_duplex = true; + m_setup->m_dmrEnabled = false; + m_setup->m_dmrRx1K = false; + m_setup->m_p25Enabled = false; + m_setup->m_p25Rx1K = false; + m_setup->m_p25TduTest = false; + m_setup->m_nxdnEnabled = false; + m_setup->writeConfig(); + + initWait.hide(); + } + else { + FMessageBox::error(this, L"Cannot connect to a modem when already connected."); + } + } +}; + +#endif // __SETUP_WND_H__ \ No newline at end of file diff --git a/src/host/setup/SiteParamSetWnd.h b/src/host/setup/SiteParamSetWnd.h new file mode 100644 index 00000000..c945248f --- /dev/null +++ b/src/host/setup/SiteParamSetWnd.h @@ -0,0 +1,266 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__SITE_PARAM_SET_WND_H__) +#define __SITE_PARAM_SET_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/CloseWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the site parameters configuration window. +// --------------------------------------------------------------------------- + +class HOST_SW_API SiteParamSetWnd final : public CloseWndBase +{ +public: + /// + /// Initializes a new instance of the SiteParamSetWnd class. + /// + /// + /// + explicit SiteParamSetWnd(HostSetup* setup, FWidget* widget = nullptr) : CloseWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_cwParams{"CW Configuration", this}; + FLabel m_siteParams{"Parameters", this}; + + FCheckBox m_cwEnabled{"Enabled", this}; + FLabel m_cwCallsignLabel{"Callsign: ", this}; + FLineEdit m_cwCallsign{this}; + FLabel m_cwTimeLabel{"CW Interval: ", this}; + FSpinBox m_cwTime{this}; + +#if defined(ENABLE_DMR) + FLabel m_dmrColorCodeLabel{"DMR CC: ", this}; + FSpinBox m_dmrColorCode{this}; +#endif // defined(ENABLE_DMR) +#if defined(ENABLE_P25) + FLabel m_p25NACLabel{"P25 NAC: ", this}; + FLineEdit m_p25NAC{this}; +#endif // defined(ENABLE_P25) +#if defined(ENABLE_NXDN) + FLabel m_nxdnRANLabel{"NXDN RAN: ", this}; + FSpinBox m_nxdnRAN{this}; +#endif // defined(ENABLE_NXDN) + + FLabel m_siteIdLabel{"Site ID: ", this}; + FLineEdit m_siteId{this}; +#if defined(ENABLE_DMR) + FLabel m_dmrNetIdLabel{"DMR Net. ID: ", this}; + FLineEdit m_dmrNetId{this}; +#endif // defined(ENABLE_DMR) +#if defined(ENABLE_P25) + FLabel m_p25NetIdLabel{"P25 Net. ID: ", this}; + FLineEdit m_p25NetId{this}; + FLabel m_p25SysIdLabel{"P25 System ID: ", this}; + FLineEdit m_p25SysId{this}; + FLabel m_p25RfssIdLabel{"P25 RFSS ID: ", this}; + FLineEdit m_p25RfssId{this}; +#endif // defined(ENABLE_P25) + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Site Parameters"); + FDialog::setSize(FSize{63, 20}); + + m_enableSetButton = false; + CloseWndBase::initLayout(); + } + + /// + /// + /// + void initControls() + { + yaml::Node cwId = m_setup->m_conf["system"]["cwId"]; + bool enabled = cwId["enable"].as(false); + uint32_t cwTime = cwId["time"].as(10U); + std::string callsign = cwId["callsign"].as(); + + // cw configuration + { + m_cwParams.setGeometry(FPoint(2, 1), FSize(30, 2)); + m_cwParams.setEmphasis(); + m_cwParams.setAlignment(Align::Center); + + m_cwEnabled.setGeometry(FPoint(2, 3), FSize(10, 1)); + m_cwEnabled.setChecked(enabled); + m_cwEnabled.addCallback("toggled", [&]() { + m_setup->m_conf["system"]["cwId"]["enable"] = __BOOL_STR(m_cwEnabled.isChecked()); + }); + + m_cwCallsignLabel.setGeometry(FPoint(2, 4), FSize(20, 1)); + m_cwCallsign.setGeometry(FPoint(23, 4), FSize(28, 1)); + m_cwCallsign.setText(callsign); + m_cwCallsign.setShadow(false); + m_cwCallsign.addCallback("changed", [&]() { + m_setup->m_conf["system"]["cwId"]["callsign"] = m_cwCallsign.getText().toString(); + }); + + m_cwTimeLabel.setGeometry(FPoint(2, 5), FSize(20, 1)); + m_cwTime.setGeometry(FPoint(23, 5), FSize(10, 1)); + m_cwTime.setValue(cwTime); + m_cwTime.setMinValue(0); + m_cwTime.setShadow(false); + m_cwTime.addCallback("changed", [&]() { + m_setup->m_conf["system"]["cwId"]["time"] = __INT_STR(m_cwTime.getValue()); + }); + } + + yaml::Node rfssConfig = m_setup->m_conf["system"]["config"]; + + // site parameters + { + m_siteParams.setGeometry(FPoint(2, 7), FSize(30, 2)); + m_siteParams.setEmphasis(); + m_siteParams.setAlignment(Align::Center); + +#if defined(ENABLE_DMR) + uint32_t dmrColorCode = rfssConfig["colorCode"].as(2U); + m_dmrColorCodeLabel.setGeometry(FPoint(2, 9), FSize(8, 1)); + m_dmrColorCode.setGeometry(FPoint(12, 9), FSize(8, 1)); + m_dmrColorCode.setValue(dmrColorCode); + m_dmrColorCode.setRange(0, 15); + m_dmrColorCode.setShadow(false); + m_dmrColorCode.addCallback("changed", [&]() { + m_setup->m_conf["system"]["config"]["colorCode"] = __INT_STR(m_dmrColorCode.getValue()); + }); +#endif // defined(ENABLE_DMR) + +#if defined(ENABLE_P25) + m_p25NACLabel.setGeometry(FPoint(23, 9), FSize(10, 1)); + m_p25NAC.setGeometry(FPoint(33, 9), FSize(8, 1)); + m_p25NAC.setText(m_setup->m_conf["system"]["config"]["nac"].as("1").c_str()); + m_p25NAC.setShadow(false); + m_p25NAC.setMaxLength(3); + m_p25NAC.setInputFilter("[[:xdigit:]]"); + m_p25NAC.addCallback("changed", [&]() { + uint32_t nac = (uint32_t)::strtoul(std::string(m_p25NAC.getText().toString()).c_str(), NULL, 16); + nac = p25::P25Utils::nac(nac); + + m_setup->m_conf["system"]["config"]["nac"] = __INT_HEX_STR(nac); + }); +#endif // defined(ENABLE_P25) + +#if defined(ENABLE_NXDN) + uint32_t nxdnRAN = rfssConfig["ran"].as(1U); + m_nxdnRANLabel.setGeometry(FPoint(42, 9), FSize(10, 1)); + m_nxdnRAN.setGeometry(FPoint(53, 9), FSize(8, 1)); + m_nxdnRAN.setValue(nxdnRAN); + m_nxdnRAN.setRange(0, 15); + m_nxdnRAN.setShadow(false); + m_nxdnRAN.addCallback("changed", [&]() { + m_setup->m_conf["system"]["config"]["ran"] = __INT_STR(m_nxdnRAN.getValue()); + }); +#endif // defined(ENABLE_NXDN) + + m_siteIdLabel.setGeometry(FPoint(2, 10), FSize(20, 1)); + m_siteId.setGeometry(FPoint(23, 10), FSize(10, 1)); + m_siteId.setText(rfssConfig["siteId"].as("1").c_str()); + m_siteId.setShadow(false); + m_siteId.setMaxLength(3); + m_siteId.setInputFilter("[[:xdigit:]]"); + m_siteId.addCallback("changed", [&]() { + uint32_t id = (uint32_t)::strtoul(std::string(m_siteId.getText().toString()).c_str(), NULL, 16); + id = p25::P25Utils::siteId(id); + + m_setup->m_conf["system"]["config"]["siteId"] = __INT_HEX_STR(id); + }); + +#if defined(ENABLE_DMR) + m_dmrNetIdLabel.setGeometry(FPoint(2, 11), FSize(20, 1)); + m_dmrNetId.setGeometry(FPoint(23, 11), FSize(10, 1)); + m_dmrNetId.setText(rfssConfig["dmrNetId"].as("1").c_str()); + m_dmrNetId.setShadow(false); + m_dmrNetId.setMaxLength(6); + m_dmrNetId.setInputFilter("[[:xdigit:]]"); + m_dmrNetId.addCallback("changed", [&]() { + uint32_t id = (uint32_t)::strtoul(std::string(m_dmrNetId.getText().toString()).c_str(), NULL, 16); + id = dmr::DMRUtils::netId(id, dmr::SITE_MODEL_TINY); + + m_setup->m_conf["system"]["config"]["dmrNetId"] = __INT_HEX_STR(id); + }); +#endif // defined(ENABLE_DMR) + +#if defined(ENABLE_P25) + m_p25NetIdLabel.setGeometry(FPoint(2, 12), FSize(20, 1)); + m_p25NetId.setGeometry(FPoint(23, 12), FSize(10, 1)); + m_p25NetId.setText(rfssConfig["netId"].as("1").c_str()); + m_p25NetId.setShadow(false); + m_p25NetId.setMaxLength(6); + m_p25NetId.setInputFilter("[[:xdigit:]]"); + m_p25NetId.addCallback("changed", [&]() { + uint32_t id = (uint32_t)::strtoul(std::string(m_p25NetId.getText().toString()).c_str(), NULL, 16); + id = p25::P25Utils::netId(id); + + m_setup->m_conf["system"]["config"]["netId"] = __INT_HEX_STR(id); + }); + + m_p25SysIdLabel.setGeometry(FPoint(2, 13), FSize(20, 1)); + m_p25SysId.setGeometry(FPoint(23, 13), FSize(10, 1)); + m_p25SysId.setText(rfssConfig["sysId"].as("1").c_str()); + m_p25SysId.setShadow(false); + m_p25SysId.setMaxLength(4); + m_p25SysId.setInputFilter("[[:xdigit:]]"); + m_p25SysId.addCallback("changed", [&]() { + uint32_t id = (uint32_t)::strtoul(std::string(m_p25SysId.getText().toString()).c_str(), NULL, 16); + id = p25::P25Utils::sysId(id); + + m_setup->m_conf["system"]["config"]["sysId"] = __INT_HEX_STR(id); + }); + + m_p25RfssIdLabel.setGeometry(FPoint(2, 14), FSize(20, 1)); + m_p25RfssId.setGeometry(FPoint(23, 14), FSize(10, 1)); + m_p25RfssId.setText(rfssConfig["dmrNetId"].as("1").c_str()); + m_p25RfssId.setShadow(false); + m_p25RfssId.setMaxLength(3); + m_p25RfssId.setInputFilter("[[:xdigit:]]"); + m_p25RfssId.addCallback("changed", [&]() { + uint32_t id = (uint8_t)::strtoul(std::string(m_p25RfssId.getText().toString()).c_str(), NULL, 16); + id = p25::P25Utils::rfssId(id); + + m_setup->m_conf["system"]["config"]["rfssId"] = __INT_HEX_STR(id); + }); +#endif // defined(ENABLE_P25) + } + + CloseWndBase::initControls(); + } +}; + +#endif // __SITE_PARAM_SET_WND_H__ \ No newline at end of file diff --git a/src/host/setup/SymbLevelAdjustWnd.h b/src/host/setup/SymbLevelAdjustWnd.h new file mode 100644 index 00000000..0887f335 --- /dev/null +++ b/src/host/setup/SymbLevelAdjustWnd.h @@ -0,0 +1,184 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__SYMB_LEVEL_ADJUST_WND_H__) +#define __SYMB_LEVEL_ADJUST_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/AdjustWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the symbol level adjustment window. +// --------------------------------------------------------------------------- + +class HOST_SW_API SymbLevelAdjustWnd final : public AdjustWndBase +{ +public: + /// + /// Initializes a new instance of the SymbLevelAdjustWnd class. + /// + /// + /// + explicit SymbLevelAdjustWnd(HostSetup* setup, FWidget* widget = nullptr) : AdjustWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_symbLevelLabel{"Symbol Levels", this}; + FLabel m_dmr3LevelLabel{"DMR +/- 3 Symbol Level: ", this}; + FLabel m_dmr1LevelLabel{"DMR +/- 1 Symbol Level: ", this}; + FLabel m_p253LevelLabel{"P25 +/- 3 Symbol Level: ", this}; + FLabel m_p251LevelLabel{"P25 +/- 1 Symbol Level: ", this}; + FLabel m_nxdn3LevelLabel{"NXDN +/- 3 Symbol Level: ", this}; + FLabel m_nxdn1LevelLabel{"NXDN +/- 1 Symbol Level: ", this}; + + FSpinBox m_dmr3Level{this}; + FSpinBox m_dmr1Level{this}; + FSpinBox m_p253Level{this}; + FSpinBox m_p251Level{this}; + FSpinBox m_nxdn3Level{this}; + FSpinBox m_nxdn1Level{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Symbol Level Adjustment"); + FDialog::setSize(FSize{60, 16}); + + AdjustWndBase::initLayout(); + } + + /// + /// + /// + void initControls() + { + // symbol levels + { + m_symbLevelLabel.setGeometry(FPoint(2, 1), FSize(20, 2)); + m_symbLevelLabel.setEmphasis(); + m_symbLevelLabel.setAlignment(Align::Center); + + m_dmr3LevelLabel.setGeometry(FPoint(2, 3), FSize(25, 1)); + m_dmr3Level.setGeometry(FPoint(28, 3), FSize(10, 1)); + m_dmr3Level.setRange(-127, 127); + m_dmr3Level.setValue(m_setup->m_modem->m_dmrSymLevel3Adj); + m_dmr3Level.setShadow(false); + m_dmr3Level.addCallback("changed", [&]() { + m_setup->m_modem->m_dmrSymLevel3Adj = m_dmr3Level.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_dmr1LevelLabel.setGeometry(FPoint(2, 4), FSize(25, 1)); + m_dmr1Level.setGeometry(FPoint(28, 4), FSize(10, 1)); + m_dmr1Level.setRange(-127, 127); + m_dmr1Level.setValue(m_setup->m_modem->m_dmrSymLevel1Adj); + m_dmr1Level.setShadow(false); + m_dmr1Level.addCallback("changed", [&]() { + m_setup->m_modem->m_dmrSymLevel1Adj = m_dmr1Level.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_p253LevelLabel.setGeometry(FPoint(2, 5), FSize(25, 1)); + m_p253Level.setGeometry(FPoint(28, 5), FSize(10, 1)); + m_p253Level.setRange(-127, 127); + m_p253Level.setValue(m_setup->m_modem->m_p25SymLevel3Adj); + m_p253Level.setShadow(false); + m_p253Level.addCallback("changed", [&]() { + m_setup->m_modem->m_p25SymLevel3Adj = m_p253Level.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_p251LevelLabel.setGeometry(FPoint(2, 6), FSize(25, 1)); + m_p251Level.setGeometry(FPoint(28, 6), FSize(10, 1)); + m_p251Level.setRange(-127, 127); + m_p251Level.setValue(m_setup->m_modem->m_p25SymLevel1Adj); + m_p251Level.setShadow(false); + m_p251Level.addCallback("changed", [&]() { + m_setup->m_modem->m_p25SymLevel1Adj = m_p251Level.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_nxdn3LevelLabel.setGeometry(FPoint(2, 7), FSize(25, 1)); + m_nxdn3Level.setGeometry(FPoint(28, 7), FSize(10, 1)); + m_nxdn3Level.setRange(-127, 127); + m_nxdn3Level.setValue(m_setup->m_modem->m_nxdnSymLevel3Adj); + m_nxdn3Level.setShadow(false); + m_nxdn3Level.addCallback("changed", [&]() { + m_setup->m_modem->m_nxdnSymLevel3Adj = m_nxdn3Level.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + + m_nxdn1LevelLabel.setGeometry(FPoint(2, 8), FSize(25, 1)); + m_nxdn1Level.setGeometry(FPoint(28, 8), FSize(10, 1)); + m_nxdn1Level.setRange(-127, 127); + m_nxdn1Level.setValue(m_setup->m_modem->m_nxdnSymLevel1Adj); + m_nxdn1Level.setShadow(false); + m_nxdn1Level.addCallback("changed", [&]() { + m_setup->m_modem->m_nxdnSymLevel1Adj = m_nxdn1Level.getValue(); + Thread::sleep(2); + m_setup->writeConfig(); + }); + } + + // setup control states + if (m_setup->m_isConnected) { + if (m_setup->m_modem->m_isHotspot) { + m_dmr3Level.setDisable(); + m_dmr1Level.setDisable(); + m_p253Level.setDisable(); + m_p251Level.setDisable(); + m_nxdn3Level.setDisable(); + m_nxdn1Level.setDisable(); + } + else { + m_dmr3Level.setEnable(); + m_dmr1Level.setEnable(); + m_p253Level.setEnable(); + m_p251Level.setEnable(); + m_nxdn3Level.setEnable(); + m_nxdn1Level.setEnable(); + } + } + + AdjustWndBase::initControls(); + } +}; + +#endif // __SYMB_LEVEL_ADJUST_WND_H__ \ No newline at end of file diff --git a/src/host/setup/SystemConfigSetWnd.h b/src/host/setup/SystemConfigSetWnd.h new file mode 100644 index 00000000..dc20d2b7 --- /dev/null +++ b/src/host/setup/SystemConfigSetWnd.h @@ -0,0 +1,244 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__SYSTEM_CONFIG_SET_WND_H__) +#define __SYSTEM_CONFIG_SET_WND_H__ + +#include "host/setup/HostSetup.h" +#include "Thread.h" + +#include "host/setup/CloseWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the system configuration window. +// --------------------------------------------------------------------------- + +class HOST_SW_API SystemConfigSetWnd final : public CloseWndBase +{ +public: + /// + /// Initializes a new instance of the SystemConfigSetWnd class. + /// + /// + /// + explicit SystemConfigSetWnd(HostSetup* setup, FWidget* widget = nullptr) : CloseWndBase{setup, widget} + { + /* stub */ + } + +private: + FLabel m_portAndSpeedLabel{"Modem Port and Speed", this}; + FLabel m_systemSettingsLabel{"System Settings", this}; + FLabel m_modeSettingsLabel{"Mode Settings", this}; + + FLabel m_modemPortLabel{"Modem Port: ", this}; + FLineEdit m_modemPort{this}; + FLabel m_modemSpeedLabel{"Modem Speed: ", this}; + FSpinBox m_modemSpeed{this}; + + FLabel m_identityLabel{"Identity: ", this}; + FLineEdit m_identity{this}; + FCheckBox m_duplex{"Duplex", this}; + FCheckBox m_simplexFreq{"Simplex Freq", this}; + FLabel m_timeoutLabel{"Timeout: ", this}; + FSpinBox m_timeout{this}; + FLabel m_modeHangLabel{"Mode Hangtime: ", this}; + FSpinBox m_modeHang{this}; + FLabel m_rfTalkgroupLabel{"RF TG Hangtime: ", this}; + FSpinBox m_rfTalkgroup{this}; + + FCheckBox m_fixedMode{"Fixed Mode", this}; +#if defined(ENABLE_DMR) + FCheckBox m_dmrEnabled{"DMR", this}; +#endif // defined(ENABLE_DMR) +#if defined(ENABLE_P25) + FCheckBox m_p25Enabled{"P25", this}; +#endif // defined(ENABLE_P25) +#if defined(ENABLE_NXDN) + FCheckBox m_nxdnEnabled{"NXDN", this}; +#endif // defined(ENABLE_NXDN) + + /// + /// + /// + void initLayout() override + { + FDialog::setText("System Configuration"); + FDialog::setSize(FSize{56, 22}); + + m_enableSetButton = false; + CloseWndBase::initLayout(); + } + + /// + /// + /// + void initControls() + { + yaml::Node modemConfig = m_setup->m_conf["system"]["modem"]; + m_setup->m_conf["system"]["modem"]["protocol"]["type"] = std::string("uart"); // configuring modem, always sets type to UART + + yaml::Node uartConfig = modemConfig["protocol"]["uart"]; + + std::string modemPort = uartConfig["port"].as("/dev/ttyUSB0"); + uint32_t portSpeed = uartConfig["speed"].as(115200); + + // port and speed + { + m_portAndSpeedLabel.setGeometry(FPoint(2, 1), FSize(30, 2)); + m_portAndSpeedLabel.setEmphasis(); + m_portAndSpeedLabel.setAlignment(Align::Center); + + m_modemPortLabel.setGeometry(FPoint(2, 3), FSize(20, 1)); + m_modemPort.setGeometry(FPoint(23, 3), FSize(28, 1)); + m_modemPort.setText(modemPort); + m_modemPort.setShadow(false); + m_modemPort.addCallback("changed", [&]() { + m_setup->m_conf["system"]["modem"]["protocol"]["uart"]["port"] = m_modemPort.getText().toString(); + }); + + m_modemSpeedLabel.setGeometry(FPoint(2, 4), FSize(20, 1)); + m_modemSpeed.setGeometry(FPoint(23, 4), FSize(10, 1)); + m_modemSpeed.setRange(1200, 460800); + m_modemSpeed.setValue(portSpeed); + m_modemSpeed.setShadow(false); + m_modemSpeed.addCallback("changed", [&]() { + m_setup->m_conf["system"]["modem"]["protocol"]["uart"]["speed"] = __INT_STR(m_modemSpeed.getValue()); + }); + m_modemSpeed.setDisable(); // don't allow this to be changed right now + } + + std::string identity = m_setup->m_conf["system"]["identity"].as(); + uint32_t timeout = m_setup->m_conf["system"]["timeout"].as(); + bool duplex = m_setup->m_conf["system"]["duplex"].as(true); + bool simplexSameFrequency = m_setup->m_conf["system"]["simplexSameFrequency"].as(false); + uint32_t modeHang = m_setup->m_conf["system"]["modeHang"].as(); + uint32_t rfTalkgroupHang = m_setup->m_conf["system"]["rfTalkgroupHang"].as(); + bool fixedMode = m_setup->m_conf["system"]["fixedMode"].as(false); + + // system settings + { + m_systemSettingsLabel.setGeometry(FPoint(2, 6), FSize(30, 2)); + m_systemSettingsLabel.setEmphasis(); + m_systemSettingsLabel.setAlignment(Align::Center); + + m_identityLabel.setGeometry(FPoint(2, 8), FSize(20, 1)); + m_identity.setGeometry(FPoint(23, 8), FSize(28, 1)); + m_identity.setText(identity); + m_identity.setShadow(false); + m_identity.addCallback("changed", [&]() { + m_setup->m_conf["system"]["identity"] = m_identity.getText().toString(); + }); + + m_duplex.setGeometry(FPoint(2, 9), FSize(10, 1)); + m_duplex.setChecked(duplex); + m_duplex.addCallback("toggled", [&]() { + m_setup->m_conf["system"]["duplex"] = __BOOL_STR(m_duplex.isChecked()); + }); + + m_simplexFreq.setGeometry(FPoint(15, 9), FSize(10, 1)); + m_simplexFreq.setChecked(simplexSameFrequency); + m_simplexFreq.addCallback("toggled", [&]() { + m_setup->m_conf["system"]["duplex"] = __BOOL_STR(m_simplexFreq.isChecked()); + }); + + m_timeoutLabel.setGeometry(FPoint(2, 10), FSize(20, 1)); + m_timeout.setGeometry(FPoint(23, 10), FSize(10, 1)); + m_timeout.setValue(timeout); + m_timeout.setMinValue(0); + m_timeout.setShadow(false); + m_timeout.addCallback("changed", [&]() { + m_setup->m_conf["system"]["timeout"] = __INT_STR(m_modeHang.getValue()); + }); + + m_modeHangLabel.setGeometry(FPoint(2, 11), FSize(20, 1)); + m_modeHang.setGeometry(FPoint(23, 11), FSize(10, 1)); + m_modeHang.setValue(modeHang); + m_modeHang.setMinValue(0); + m_modeHang.setShadow(false); + m_modeHang.addCallback("changed", [&]() { + m_setup->m_conf["system"]["modeHang"] = __INT_STR(m_modeHang.getValue()); + }); + + m_rfTalkgroupLabel.setGeometry(FPoint(2, 12), FSize(20, 1)); + m_rfTalkgroup.setGeometry(FPoint(23, 12), FSize(10, 1)); + m_rfTalkgroup.setValue(rfTalkgroupHang); + m_rfTalkgroup.setMinValue(0); + m_rfTalkgroup.setShadow(false); + m_rfTalkgroup.addCallback("changed", [&]() { + m_setup->m_conf["system"]["rfTalkgroupHang"] = __INT_STR(m_rfTalkgroup.getValue()); + }); + } + + // mode settings + { + m_modeSettingsLabel.setGeometry(FPoint(2, 14), FSize(30, 2)); + m_modeSettingsLabel.setEmphasis(); + m_modeSettingsLabel.setAlignment(Align::Center); + + m_fixedMode.setGeometry(FPoint(2, 16), FSize(10, 1)); + m_fixedMode.setChecked(fixedMode); + m_fixedMode.addCallback("toggled", [&]() { + m_setup->m_conf["system"]["fixedMode"] = __BOOL_STR(m_fixedMode.isChecked()); + }); + +#if defined(ENABLE_DMR) + bool dmrEnabled = m_setup->m_conf["protocols"]["dmr"]["enable"].as(true); + + m_dmrEnabled.setGeometry(FPoint(2, 17), FSize(10, 1)); + m_dmrEnabled.setChecked(dmrEnabled); + m_dmrEnabled.addCallback("toggled", [&]() { + m_setup->m_conf["protocols"]["dmr"]["enable"] = __BOOL_STR(m_dmrEnabled.isChecked()); + }); +#endif // defined(ENABLE_DMR) +#if defined(ENABLE_P25) + bool p25Enabled = m_setup->m_conf["protocols"]["p25"]["enable"].as(true); + + m_p25Enabled.setGeometry(FPoint(12, 17), FSize(10, 1)); + m_p25Enabled.setChecked(p25Enabled); + m_p25Enabled.addCallback("toggled", [&]() { + m_setup->m_conf["protocols"]["p25"]["enable"] = __BOOL_STR(m_p25Enabled.isChecked()); + }); +#endif // defined(ENABLE_P25) +#if defined(ENABLE_NXDN) + bool nxdnEnabled = m_setup->m_conf["protocols"]["nxdn"]["enable"].as(true); + + m_nxdnEnabled.setGeometry(FPoint(22, 17), FSize(10, 1)); + m_nxdnEnabled.setChecked(nxdnEnabled); + m_nxdnEnabled.addCallback("toggled", [&]() { + m_setup->m_conf["protocols"]["nxdn"]["enable"] = __BOOL_STR(m_nxdnEnabled.isChecked()); + }); +#endif // defined(ENABLE_NXDN) + } + + CloseWndBase::initControls(); + } +}; + +#endif // __SYSTEM_CONFIG_SET_WND_H__ \ No newline at end of file diff --git a/src/lookups/IdenTableLookup.cpp b/src/lookups/IdenTableLookup.cpp index 659413ce..9f234173 100644 --- a/src/lookups/IdenTableLookup.cpp +++ b/src/lookups/IdenTableLookup.cpp @@ -122,7 +122,7 @@ bool IdenTableLookup::load() std::ifstream file (m_filename, std::ifstream::in); if (file.fail()) { - LogError(LOG_HOST, "Cannot open the lookup file - %s", m_filename.c_str()); + LogError(LOG_HOST, "Cannot open the identity table lookup file - %s", m_filename.c_str()); return false; } diff --git a/src/lookups/LookupTable.h b/src/lookups/LookupTable.h index e951f9a9..a915d8b2 100644 --- a/src/lookups/LookupTable.h +++ b/src/lookups/LookupTable.h @@ -151,6 +151,10 @@ namespace lookups /// Table entry. virtual T find(uint32_t id) = 0; + /// Helper to return the lookup table. + /// Table. + virtual std::unordered_map table() { return m_table; } + protected: std::string m_filename; uint32_t m_reloadTime; diff --git a/src/lookups/RadioIdLookup.cpp b/src/lookups/RadioIdLookup.cpp index e98d8c10..97c60cdc 100644 --- a/src/lookups/RadioIdLookup.cpp +++ b/src/lookups/RadioIdLookup.cpp @@ -177,7 +177,7 @@ bool RadioIdLookup::load() std::ifstream file (m_filename, std::ifstream::in); if (file.fail()) { - LogError(LOG_HOST, "Cannot open the lookup file - %s", m_filename.c_str()); + LogError(LOG_HOST, "Cannot open the radio ID lookup file - %s", m_filename.c_str()); return false; } diff --git a/src/lookups/TalkgroupIdLookup.cpp b/src/lookups/TalkgroupIdLookup.cpp deleted file mode 100644 index 8de5befd..00000000 --- a/src/lookups/TalkgroupIdLookup.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/** -* Digital Voice Modem - Host Software -* GPLv2 Open Source. Use is subject to license terms. -* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -* -* @package DVM / Host Software -* -*/ -// -// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) -// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) -// -/* -* Copyright (C) 2016 by Jonathan Naylor G4KLX -* Copyright (C) 2017-2022 by Bryan Biedenkapp N2PLL -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ -#include "lookups/TalkgroupIdLookup.h" -#include "Log.h" -#include "Timer.h" - -using namespace lookups; - -#include -#include -#include -#include -#include -#include -#include - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/// -/// Initializes a new instance of the TalkgroupIdLookup class. -/// -/// Full-path to the talkgroup ID table file. -/// Interval of time to reload the talkgroup ID table. -/// Flag indicating whether talkgroup ID access control is enabled. -TalkgroupIdLookup::TalkgroupIdLookup(const std::string& filename, uint32_t reloadTime, bool tidAcl) : LookupTable(filename, reloadTime), - m_acl(tidAcl) -{ - /* stub */ -} - -/// -/// Finalizes a instance of the TalkgroupIdLookup class. -/// -TalkgroupIdLookup::~TalkgroupIdLookup() -{ - /* stub */ -} - -/// -/// Adds a new entry to the lookup table by the specified unique ID. -/// -/// Unique ID to add. -/// DMR slot this talkgroup is valid on. -/// Flag indicating if talkgroup ID is enabled or not. -void TalkgroupIdLookup::addEntry(uint32_t id, unsigned char slot, bool enabled) -{ - TalkgroupId entry = TalkgroupId(enabled, slot, false); - - m_mutex.lock(); - { - try { - TalkgroupId _entry = m_table.at(id); - - // if the enabled value doesn't match -- override with the intended - if (_entry.tgEnabled() != enabled) { - _entry = TalkgroupId(enabled, _entry.tgSlot(), false); - m_table[id] = _entry; - } - } catch (...) { - m_table[id] = entry; - } - } - m_mutex.unlock(); -} - -/// -/// Finds a table entry in this lookup table. -/// -/// Unique identifier for table entry. -/// Table entry. -TalkgroupId TalkgroupIdLookup::find(uint32_t id) -{ - TalkgroupId entry; - - m_mutex.lock(); - { - try { - entry = m_table.at(id); - } catch (...) { - entry = TalkgroupId(false, 0U, true); - } - } - m_mutex.unlock(); - - return entry; -} - -/// -/// Flag indicating whether talkgroup ID access control is enabled or not. -/// -/// True, if talkgroup ID access control is enabled, otherwise false. -bool TalkgroupIdLookup::getACL() -{ - return m_acl; -} - -// --------------------------------------------------------------------------- -// Private Class Members -// --------------------------------------------------------------------------- - -/// -/// Loads the table from the passed lookup table file. -/// -/// True, if lookup table was loaded, otherwise false. -bool TalkgroupIdLookup::load() -{ - if (m_filename.length() <= 0) { - return false; - } - - std::ifstream file (m_filename, std::ifstream::in); - if (file.fail()) { - LogError(LOG_HOST, "Cannot open the lookup file - %s", m_filename.c_str()); - return false; - } - - // clear table - clear(); - - m_mutex.lock(); - { - // read lines from file - std::string line; - while (std::getline(file, line)) { - if (line.length() > 0) { - if (line.at(0) == '#') - continue; - - // tokenize line - std::string next; - std::vector parsed; - char delim = ','; - - for (char c : line) { - if (c == delim) { - if (!next.empty()) { - parsed.push_back(next); - next.clear(); - } - } - else - next += c; - } - if (!next.empty()) - parsed.push_back(next); - - // parse tokenized line - uint32_t id = ::atoi(parsed[0].c_str()); - bool tgEnabled = ::atoi(parsed[1].c_str()) == 1; - uint8_t tgSlot = (uint8_t)::atoi(parsed[2].c_str()); - bool tgDefault = false; - - m_table[id] = TalkgroupId(tgEnabled, tgSlot, tgDefault); - } - } - } - m_mutex.unlock(); - - file.close(); - - size_t size = m_table.size(); - if (size == 0U) - return false; - - LogInfoEx(LOG_HOST, "Loaded %u entries into lookup table", size); - - return true; -} diff --git a/src/lookups/TalkgroupIdLookup.h b/src/lookups/TalkgroupIdLookup.h deleted file mode 100644 index 49d6c6db..00000000 --- a/src/lookups/TalkgroupIdLookup.h +++ /dev/null @@ -1,132 +0,0 @@ -/** -* Digital Voice Modem - Host Software -* GPLv2 Open Source. Use is subject to license terms. -* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -* -* @package DVM / Host Software -* -*/ -// -// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) -// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) -// -/* -* Copyright (C) 2016 by Jonathan Naylor G4KLX -* Copyright (C) 2017-2022 by Bryan Biedenkapp N2PLL -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ -#if !defined(__TALKGROUP_ID_LOOKUP_H__) -#define __TALKGROUP_ID_LOOKUP_H__ - -#include "Defines.h" -#include "lookups/LookupTable.h" -#include "Thread.h" - -#include -#include - -namespace lookups -{ - // --------------------------------------------------------------------------- - // Class Declaration - // Represents an individual entry in the talkgroup ID table. - // --------------------------------------------------------------------------- - - class HOST_SW_API TalkgroupId { - public: - /// Initializes a new insatnce of the TalkgroupId class. - TalkgroupId() : - m_tgEnabled(false), - m_tgSlot(0U), - m_tgDefault(false) - { - /* stub */ - } - /// Initializes a new insatnce of the TalkgroupId class. - /// - /// - /// - TalkgroupId(bool tgEnabled, uint8_t tgSlot, bool tgDefault) : - m_tgEnabled(tgEnabled), - m_tgSlot(tgSlot), - m_tgDefault(tgDefault) - { - /* stub */ - } - - /// Equals operator. Copies this TalkgroupId to another TalkgroupId. - TalkgroupId& operator=(const TalkgroupId& data) - { - if (this != &data) { - m_tgEnabled = data.m_tgEnabled; - m_tgSlot = data.m_tgSlot; - m_tgDefault = data.m_tgDefault; - } - - return *this; - } - - /// Sets talkgroup values. - /// Talkgroup Enabled. - /// Talkgroup DMR slot. - /// Talkgroup Default. - void set(bool tgEnabled, uint8_t tgSlot, bool tgDefault) - { - m_tgEnabled = tgEnabled; - m_tgSlot = tgSlot; - m_tgDefault = tgDefault; - } - - public: - /// Flag indicating if the talkgroup is enabled. - __READONLY_PROPERTY_PLAIN(bool, tgEnabled, tgEnabled); - /// Talkgroup DMR slot. - __READONLY_PROPERTY_PLAIN(uint8_t, tgSlot, tgSlot); - /// Flag indicating if the talkgroup is default. - __READONLY_PROPERTY_PLAIN(bool, tgDefault, tgDefault); - }; - - // --------------------------------------------------------------------------- - // Class Declaration - // Implements a threading lookup table class that contains a talkgroup - // ID lookup table. - // --------------------------------------------------------------------------- - - class HOST_SW_API TalkgroupIdLookup : public LookupTable { - public: - /// Initializes a new instance of the TalkgroupIdLookup class. - TalkgroupIdLookup(const std::string& filename, uint32_t reloadTime, bool tidAcl); - /// Finalizes a instance of the TalkgroupIdLookup class. - virtual ~TalkgroupIdLookup(); - - /// Adds a new entry to the lookup table by the specified unique ID. - void addEntry(uint32_t id, unsigned char slot, bool enabled); - /// Finds a table entry in this lookup table. - virtual TalkgroupId find(uint32_t id); - - /// Flag indicating whether talkgroup ID access control is enabled or not. - bool getACL(); - - protected: - bool m_acl; - - /// Loads the table from the passed lookup table file. - /// True, if lookup table was loaded, otherwise false. - virtual bool load(); - }; -} // namespace lookups - -#endif // __TALKGROUP_ID_LOOKUP_H__ diff --git a/src/lookups/TalkgroupRulesLookup.cpp b/src/lookups/TalkgroupRulesLookup.cpp new file mode 100644 index 00000000..47f2d9f8 --- /dev/null +++ b/src/lookups/TalkgroupRulesLookup.cpp @@ -0,0 +1,344 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "lookups/TalkgroupRulesLookup.h" +#include "Log.h" +#include "Timer.h" + +using namespace lookups; + +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the TalkgroupRulesLookup class. +/// +/// Full-path to the routing rules file. +/// Interval of time to reload the routing rules. +/// +TalkgroupRulesLookup::TalkgroupRulesLookup(const std::string& filename, uint32_t reloadTime, bool acl) : Thread(), + m_rulesFile(filename), + m_reloadTime(reloadTime), + m_rules(), + m_acl(false), + m_groupHangTime(5U), + m_sendTalkgroups(false), + m_groupVoice() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the TalkgroupRulesLookup class. +/// +TalkgroupRulesLookup::~TalkgroupRulesLookup() +{ + /* stub */ +} + +/// +/// +/// +void TalkgroupRulesLookup::entry() +{ + if (m_reloadTime == 0U) { + return; + } + + Timer timer(1U, 60U * m_reloadTime); + timer.start(); + + while (!m_stop) { + sleep(1000U); + + timer.clock(); + if (timer.hasExpired()) { + load(); + timer.start(); + } + } +} + +/// +/// Stops and unloads this lookup table. +/// +void TalkgroupRulesLookup::stop() +{ + if (m_reloadTime == 0U) { + delete this; + return; + } + + m_stop = true; + + wait(); +} + +/// +/// Reads the lookup table from the specified lookup table file. +/// +/// True, if lookup table was read, otherwise false. +bool TalkgroupRulesLookup::read() +{ + bool ret = load(); + + if (m_reloadTime > 0U) + run(); + + return ret; +} + +/// +/// Clears all entries from the lookup table. +/// +void TalkgroupRulesLookup::clear() +{ + m_mutex.lock(); + { + m_groupVoice.clear(); + } + m_mutex.unlock(); +} + +/// +/// Adds a new entry to the lookup table by the specified unique ID. +/// +/// Unique ID to add. +/// DMR slot this talkgroup is valid on. +/// Flag indicating if talkgroup ID is enabled or not. +void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled) +{ + TalkgroupRuleGroupVoiceSource source; + TalkgroupRuleConfig config; + source.tgId(id); + source.tgSlot(slot); + config.active(enabled); + + m_mutex.lock(); + { + auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), + [&](TalkgroupRuleGroupVoice x) + { + if (slot != 0U) { + return x.source().tgId() == id && x.source().tgSlot() == slot; + } + + return x.source().tgId() == id; + }); + if (it != m_groupVoice.end()) { + source = it->source(); + source.tgId(id); + source.tgSlot(slot); + + config = it->config(); + config.active(enabled); + + TalkgroupRuleGroupVoice entry = *it; + entry.config(config); + entry.source(source); + + m_groupVoice[it - m_groupVoice.begin()] = entry; + } + else { + TalkgroupRuleGroupVoice entry; + entry.config(config); + entry.source(source); + + m_groupVoice.push_back(entry); + } + } + m_mutex.unlock(); +} + +/// +/// Adds a new entry to the lookup table by the specified unique ID. +/// +/// +void TalkgroupRulesLookup::addEntry(TalkgroupRuleGroupVoice groupVoice) +{ + if (groupVoice.isInvalid()) + return; + + TalkgroupRuleGroupVoice entry = groupVoice; + uint32_t id = entry.source().tgId(); + uint8_t slot = entry.source().tgSlot(); + + m_mutex.lock(); + { + auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), + [&](TalkgroupRuleGroupVoice x) + { + if (slot != 0U) { + return x.source().tgId() == id && x.source().tgSlot() == slot; + } + + return x.source().tgId() == id; + }); + if (it != m_groupVoice.end()) { + m_groupVoice[it - m_groupVoice.begin()] = entry; + } + else { + m_groupVoice.push_back(entry); + } + } + m_mutex.unlock(); +} + +/// +/// Erases an existing entry from the lookup table by the specified unique ID. +/// +/// Unique ID to erase. +/// DMR slot this talkgroup is valid on. +void TalkgroupRulesLookup::eraseEntry(uint32_t id, uint8_t slot) +{ + m_mutex.lock(); + { + auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { return x.source().tgId() == id && x.source().tgSlot() == slot; }); + if (it != m_groupVoice.end()) { + m_groupVoice.erase(it); + } + } + m_mutex.unlock(); +} + +/// +/// Finds a table entry in this lookup table. +/// +/// Unique identifier for table entry. +/// DMR slot this talkgroup is valid on. +/// Table entry. +TalkgroupRuleGroupVoice TalkgroupRulesLookup::find(uint32_t id, uint8_t slot) +{ + TalkgroupRuleGroupVoice entry; + + m_mutex.lock(); + { + auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), + [&](TalkgroupRuleGroupVoice x) + { + if (slot != 0U) { + return x.source().tgId() == id && x.source().tgSlot() == slot; + } + + return x.source().tgId() == id; + }); + if (it != m_groupVoice.end()) { + entry = *it; + } else { + entry = TalkgroupRuleGroupVoice(); + } + } + m_mutex.unlock(); + + return entry; +} + +/// +/// Flag indicating whether talkgroup ID access control is enabled or not. +/// +/// True, if talkgroup ID access control is enabled, otherwise false. +bool TalkgroupRulesLookup::getACL() +{ + return m_acl; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Loads the table from the passed lookup table file. +/// +/// True, if lookup table was loaded, otherwise false. +bool TalkgroupRulesLookup::load() +{ + if (m_rulesFile.length() <= 0) { + return false; + } + + try { + bool ret = yaml::Parse(m_rules, m_rulesFile.c_str()); + if (!ret) { + LogError(LOG_HOST, "Cannot open the talkgroup rules lookup file - %s", m_rulesFile.c_str()); + return false; + } + } + catch (yaml::OperationException const& e) { + LogError(LOG_HOST, "Cannot open the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); + return false; + } + + // clear table + clear(); + + m_mutex.lock(); + { + yaml::Node& groupVoiceList = m_rules["groupVoice"]; + + if (groupVoiceList.size() == 0U) { + ::LogError(LOG_HOST, "No group voice rules list defined!"); + return false; + } + + for (size_t i = 0; i < groupVoiceList.size(); i++) { + TalkgroupRuleGroupVoice groupVoice = TalkgroupRuleGroupVoice(groupVoiceList[i]); + m_groupVoice.push_back(groupVoice); + + std::string groupName = groupVoice.name(); + uint32_t tgId = groupVoice.source().tgId(); + uint8_t tgSlot = groupVoice.source().tgSlot(); + bool active = groupVoice.config().active(); + bool affiliated = groupVoice.config().affiliated(); + bool parrot = groupVoice.config().parrot(); + + uint32_t incCount = groupVoice.config().inclusion().size(); + uint32_t excCount = groupVoice.config().exclusion().size(); + + if (incCount > 0 && excCount > 0) { + ::LogWarning(LOG_HOST, "Talkgroup (%s) defines both inclusions and exclusions! Inclusions take precedence and exclusions will be ignored.", groupName.c_str()); + } + + ::LogInfoEx(LOG_HOST, "Talkgroup NAME: %s SRC_TGID: %u SRC_TS: %u ACTIVE: %u AFFILIATED: %u PARROT: %u INCLUSIONS: %u EXCLUSIONS: %u", groupName.c_str(), tgId, tgSlot, active, affiliated, parrot, incCount, excCount); + } + } + m_mutex.unlock(); + + size_t size = m_groupVoice.size(); + if (size == 0U) + return false; + + LogInfoEx(LOG_HOST, "Loaded %u entries into lookup table", size); + + return true; +} diff --git a/src/lookups/TalkgroupRulesLookup.h b/src/lookups/TalkgroupRulesLookup.h new file mode 100644 index 00000000..f87eb3d0 --- /dev/null +++ b/src/lookups/TalkgroupRulesLookup.h @@ -0,0 +1,266 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__TALKGROUP_RULES_LOOKUP_H__) +#define __TALKGROUP_RULES_LOOKUP_H__ + +#include "Defines.h" +#include "lookups/LookupTable.h" +#include "Thread.h" +#include "yaml/Yaml.h" + +#include +#include +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an source block for a routing rule. + // --------------------------------------------------------------------------- + + class HOST_SW_API TalkgroupRuleGroupVoiceSource { + public: + /// Initializes a new insatnce of the TalkgroupRuleGroupVoiceSource class. + TalkgroupRuleGroupVoiceSource() : + m_tgId(0U), + m_tgSlot(0U) + { + /* stub */ + } + /// Initializes a new insatnce of the TalkgroupRuleGroupVoiceSource class. + /// + TalkgroupRuleGroupVoiceSource(yaml::Node& node) : + TalkgroupRuleGroupVoiceSource() + { + m_tgId = node["tgid"].as(0U); + m_tgSlot = (uint8_t)node["slot"].as(0U); + } + + /// Equals operator. Copies this TalkgroupRuleGroupVoiceSource to another TalkgroupRuleGroupVoiceSource. + virtual TalkgroupRuleGroupVoiceSource& operator=(const TalkgroupRuleGroupVoiceSource& data) + { + if (this != &data) { + m_tgId = data.m_tgId; + m_tgSlot = data.m_tgSlot; + } + + return *this; + } + + public: + /// Talkgroup ID. + __PROPERTY_PLAIN(uint32_t, tgId, tgId); + /// Talkgroup DMR slot. + __PROPERTY_PLAIN(uint8_t, tgSlot, tgSlot); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an configuration block for a routing rule. + // --------------------------------------------------------------------------- + + class HOST_SW_API TalkgroupRuleConfig { + public: + /// Initializes a new insatnce of the TalkgroupRuleConfig class. + TalkgroupRuleConfig() : + m_active(false), + m_affiliated(false), + m_parrot(false), + m_inclusion(), + m_exclusion() + { + /* stub */ + } + /// Initializes a new insatnce of the TalkgroupRuleConfig class. + /// + TalkgroupRuleConfig(yaml::Node& node) : + TalkgroupRuleConfig() + { + m_active = node["active"].as(false); + m_affiliated = node["affiliated"].as(false); + m_parrot = node["parrot"].as(false); + + yaml::Node& inclusionList = node["inclusion"]; + if (inclusionList.size() > 0U) { + for (size_t i = 0; i < inclusionList.size(); i++) { + uint32_t peerId = inclusionList[i].as(0U); + m_inclusion.push_back(peerId); + } + } + + yaml::Node& exclusionList = node["exclusion"]; + if (exclusionList.size() > 0U) { + for (size_t i = 0; i < exclusionList.size(); i++) { + uint32_t peerId = exclusionList[i].as(0U); + m_exclusion.push_back(peerId); + } + } + } + + /// Equals operator. Copies this TalkgroupRuleConfig to another TalkgroupRuleConfig. + virtual TalkgroupRuleConfig& operator=(const TalkgroupRuleConfig& data) + { + if (this != &data) { + m_active = data.m_active; + m_affiliated = data.m_affiliated; + m_parrot = data.m_parrot; + m_inclusion = data.m_inclusion; + m_exclusion = data.m_exclusion; + } + + return *this; + } + + public: + /// Flag indicating whether the rule is active. + __PROPERTY_PLAIN(bool, active, active); + /// Flag indicating whether or not affiliations are requires to repeat traffic. + __PROPERTY_PLAIN(bool, affiliated, affiliated); + /// Flag indicating whether or not the talkgroup is a parrot. + __PROPERTY_PLAIN(bool, parrot, parrot); + /// List of peer IDs included by this rule. + __PROPERTY_PLAIN(std::vector, inclusion, inclusion); + /// List of peer IDs excluded by this rule. + __PROPERTY_PLAIN(std::vector, exclusion, exclusion); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an group voice block for a routing rule. + // --------------------------------------------------------------------------- + + class HOST_SW_API TalkgroupRuleGroupVoice { + public: + /// Initializes a new insatnce of the TalkgroupRuleGroupVoice class. + TalkgroupRuleGroupVoice() : + m_name(), + m_config(), + m_source() + { + /* stub */ + } + /// Initializes a new insatnce of the TalkgroupRuleGroupVoice class. + /// + TalkgroupRuleGroupVoice(yaml::Node& node) : + TalkgroupRuleGroupVoice() + { + m_name = node["name"].as(); + m_config = TalkgroupRuleConfig(node["config"]); + m_source = TalkgroupRuleGroupVoiceSource(node["source"]); + } + + /// Equals operator. Copies this TalkgroupRuleGroupVoice to another TalkgroupRuleGroupVoice. + virtual TalkgroupRuleGroupVoice& operator=(const TalkgroupRuleGroupVoice& data) + { + if (this != &data) { + m_name = data.m_name; + m_config = data.m_config; + m_source = data.m_source; + } + + return *this; + } + + /// Helper to quickly determine if a group voice entry is valid. + bool isInvalid() const + { + if (m_source.tgId() == 0U) + return true; + return false; + } + + public: + /// Textual name for the routing rule. + __PROPERTY_PLAIN(std::string, name, name); + /// Configuration for the routing rule. + __PROPERTY_PLAIN(TalkgroupRuleConfig, config, config); + /// Source talkgroup information for the routing rule. + __PROPERTY_PLAIN(TalkgroupRuleGroupVoiceSource, source, source); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a threading lookup table class that contains routing + // rules information. + // --------------------------------------------------------------------------- + + class HOST_SW_API TalkgroupRulesLookup : public Thread { + public: + /// Initializes a new instance of the TalkgroupRulesLookup class. + TalkgroupRulesLookup(const std::string& filename, uint32_t reloadTime, bool acl); + /// Finalizes a instance of the TalkgroupRulesLookup class. + virtual ~TalkgroupRulesLookup(); + + /// + void entry(); + + /// Stops and unloads this lookup table. + void stop(); + /// Reads the lookup table from the specified lookup table file. + /// True, if lookup table was read, otherwise false. + bool read(); + /// Clears all entries from the lookup table. + void clear(); + + /// Adds a new entry to the lookup table. + void addEntry(uint32_t id, uint8_t slot, bool enabled); + /// Adds a new entry to the lookup table. + void addEntry(TalkgroupRuleGroupVoice groupVoice); + /// Adds a new entry to the lookup table. + void eraseEntry(uint32_t id, uint8_t slot); + /// Finds a table entry in this lookup table. + virtual TalkgroupRuleGroupVoice find(uint32_t id, uint8_t slot = 0U); + + /// Flag indicating whether talkgroup ID access control is enabled or not. + bool getACL(); + + private: + const std::string& m_rulesFile; + uint32_t m_reloadTime; + yaml::Node m_rules; + + bool m_acl; + + std::mutex m_mutex; + bool m_stop; + + /// Loads the table from the passed lookup table file. + /// True, if lookup table was loaded, otherwise false. + bool load(); + + public: + /// Number indicating the number of seconds to hang on a talkgroup. + __PROPERTY_PLAIN(uint32_t, groupHangTime, groupHangTime); + /// Flag indicating whether or not the network layer should send the talkgroups to peers. + __PROPERTY_PLAIN(bool, sendTalkgroups, sendTalkgroups); + /// List of group voice rules. + __PROPERTY_PLAIN(std::vector, groupVoice, groupVoice); + }; +} // namespace lookups + +#endif // __TALKGROUP_RULES_LOOKUP_H__ diff --git a/src/modem/Modem.cpp b/src/modem/Modem.cpp index 037f3963..bfd60a3a 100644 --- a/src/modem/Modem.cpp +++ b/src/modem/Modem.cpp @@ -47,12 +47,7 @@ using namespace modem; #include #include -#if defined(_WIN32) || defined(_WIN64) -#define WIN32_LEAN_AND_MEAN -#include -#else #include -#endif // --------------------------------------------------------------------------- // Constants @@ -169,6 +164,9 @@ Modem::Modem(port::IModemPort* port, bool duplex, bool rxInvert, bool txInvert, m_txFinePot(127U), m_rssiCoarsePot(127U), m_rssiFinePot(127U), + m_dmrFifoLength(DMR_TX_BUFFER_LEN), + m_p25FifoLength(P25_TX_BUFFER_LEN), + m_nxdnFifoLength(NXDN_TX_BUFFER_LEN), m_adcOverFlowCount(0U), m_dacOverFlowCount(0U), m_modemState(STATE_IDLE), @@ -181,13 +179,13 @@ Modem::Modem(port::IModemPort* port, bool duplex, bool rxInvert, bool txInvert, m_openPortHandler(nullptr), m_closePortHandler(nullptr), m_rspHandler(nullptr), - m_rxDMRData1(dmrQueueSize, "Modem RX DMR1"), - m_rxDMRData2(dmrQueueSize, "Modem RX DMR2"), - m_rxP25Data(p25QueueSize, "Modem RX P25"), - m_rxNXDNData(nxdnQueueSize, "Modem RX NXDN"), + m_rxDMRQueue1(dmrQueueSize, "Modem RX DMR1"), + m_rxDMRQueue2(dmrQueueSize, "Modem RX DMR2"), + m_rxP25Queue(p25QueueSize, "Modem RX P25"), + m_rxNXDNQueue(nxdnQueueSize, "Modem RX NXDN"), m_useDFSI(false), m_statusTimer(1000U, 0U, 250U), - m_inactivityTimer(1000U, 4U), + m_inactivityTimer(1000U, 8U), m_dmrSpace1(0U), m_dmrSpace2(0U), m_p25Space(0U), @@ -411,14 +409,14 @@ void Modem::setRXLevel(float rxLevel) uint8_t buffer[4U]; buffer[0U] = DVM_FRAME_START; - buffer[1U] = 16U; + buffer[1U] = 4U; buffer[2U] = CMD_SET_RXLEVEL; buffer[3U] = (uint8_t)(m_rxLevel * 2.55F + 0.5F); #if DEBUG_MODEM - Utils::dump(1U, "Modem::setRXLevel(), Written", buffer, 16U); + Utils::dump(1U, "Modem::setRXLevel(), Written", buffer, 4U); #endif - int ret = write(buffer, 16U); + int ret = write(buffer, 4U); if (ret != 16) return; @@ -431,7 +429,7 @@ void Modem::setRXLevel(float rxLevel) if (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK) { count++; if (count >= MAX_RESPONSES) { - LogError(LOG_MODEM, "No response, SET_RXLEVEL command"); + LogError(LOG_MODEM, "No response, %s command", cmdToString(CMD_SET_RXLEVEL).c_str()); return; } } @@ -440,7 +438,73 @@ void Modem::setRXLevel(float rxLevel) Utils::dump(1U, "Modem::setRXLevel(), Response", m_buffer, m_length); #endif if (resp == RTM_OK && m_buffer[2U] == CMD_NAK) { - LogError(LOG_MODEM, "NAK, SET_RXLEVEL, command = 0x%02X, reason = %u", m_buffer[3U], m_buffer[4U]); + LogError(LOG_MODEM, "NAK, %s, command = 0x%02X, reason = %u", cmdToString(CMD_SET_RXLEVEL).c_str(), m_buffer[3U], m_buffer[4U]); + } +} + +/// +/// Sets the modem transmit FIFO buffer lengths. +/// +/// +/// +/// +void Modem::setFifoLength(uint16_t dmrLength, uint16_t p25Length, uint16_t nxdnLength) +{ + m_dmrFifoLength = dmrLength; + m_p25FifoLength = p25Length; + m_nxdnFifoLength = nxdnLength; + + // ensure DMR fifo length is not less then the minimum + if (m_dmrFifoLength < DMR_TX_BUFFER_LEN) + m_dmrFifoLength = DMR_TX_BUFFER_LEN; + + // ensure P25 fifo length is not less then the minimum + if (m_p25FifoLength < P25_TX_BUFFER_LEN) + m_p25FifoLength = P25_TX_BUFFER_LEN; + + // ensure NXDN fifo length is not less then the minimum + if (m_nxdnFifoLength < NXDN_TX_BUFFER_LEN) + m_nxdnFifoLength = NXDN_TX_BUFFER_LEN; + + uint8_t buffer[9U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 9U; + buffer[2U] = CMD_SET_BUFFERS; + + buffer[3U] = (uint8_t)(m_dmrFifoLength >> 8) & 0xFFU; + buffer[4U] = (uint8_t)(m_dmrFifoLength & 0xFFU); + buffer[5U] = (uint8_t)(m_p25FifoLength >> 8) & 0xFFU; + buffer[6U] = (uint8_t)(m_p25FifoLength & 0xFFU); + buffer[7U] = (uint8_t)(m_nxdnFifoLength >> 8) & 0xFFU; + buffer[8U] = (uint8_t)(m_nxdnFifoLength & 0xFFU); + +#if DEBUG_MODEM + Utils::dump(1U, "Modem::setFifoLength(), Written", buffer, 9U); +#endif + int ret = write(buffer, 9U); + if (ret != 16) + return; + + uint32_t count = 0U; + RESP_TYPE_DVM resp; + do { + Thread::sleep(10U); + + resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK) { + count++; + if (count >= MAX_RESPONSES) { + LogError(LOG_MODEM, "No response, %s command", cmdToString(CMD_SET_BUFFERS).c_str()); + return; + } + } + } while (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK); +#if DEBUG_MODEM + Utils::dump(1U, "Modem::setFifoLength(), Response", m_buffer, m_length); +#endif + if (resp == RTM_OK && m_buffer[2U] == CMD_NAK) { + LogError(LOG_MODEM, "NAK, %s, command = 0x%02X, reason = %u", cmdToString(CMD_SET_BUFFERS).c_str(), m_buffer[3U], m_buffer[4U]); } } @@ -609,15 +673,15 @@ void Modem::clock(uint32_t ms) } uint8_t data = m_length - 2U; - m_rxDMRData1.addData(&data, 1U); + m_rxDMRQueue1.addData(&data, 1U); if (m_buffer[3U] == (dmr::DMR_SYNC_DATA | dmr::DT_TERMINATOR_WITH_LC)) data = TAG_EOT; else data = TAG_DATA; - m_rxDMRData1.addData(&data, 1U); + m_rxDMRQueue1.addData(&data, 1U); - m_rxDMRData1.addData(m_buffer + 3U, m_length - 3U); + m_rxDMRQueue1.addData(m_buffer + 3U, m_length - 3U); #endif // defined(ENABLE_DMR) } break; @@ -634,15 +698,15 @@ void Modem::clock(uint32_t ms) } uint8_t data = m_length - 2U; - m_rxDMRData2.addData(&data, 1U); + m_rxDMRQueue2.addData(&data, 1U); if (m_buffer[3U] == (dmr::DMR_SYNC_DATA | dmr::DT_TERMINATOR_WITH_LC)) data = TAG_EOT; else data = TAG_DATA; - m_rxDMRData2.addData(&data, 1U); + m_rxDMRQueue2.addData(&data, 1U); - m_rxDMRData2.addData(m_buffer + 3U, m_length - 3U); + m_rxDMRQueue2.addData(m_buffer + 3U, m_length - 3U); #endif // defined(ENABLE_DMR) } break; @@ -659,10 +723,10 @@ void Modem::clock(uint32_t ms) } uint8_t data = 1U; - m_rxDMRData1.addData(&data, 1U); + m_rxDMRQueue1.addData(&data, 1U); data = TAG_LOST; - m_rxDMRData1.addData(&data, 1U); + m_rxDMRQueue1.addData(&data, 1U); #endif // defined(ENABLE_DMR) } break; @@ -679,10 +743,10 @@ void Modem::clock(uint32_t ms) } uint8_t data = 1U; - m_rxDMRData2.addData(&data, 1U); + m_rxDMRQueue2.addData(&data, 1U); data = TAG_LOST; - m_rxDMRData2.addData(&data, 1U); + m_rxDMRQueue2.addData(&data, 1U); #endif // defined(ENABLE_DMR) } break; @@ -700,12 +764,12 @@ void Modem::clock(uint32_t ms) } uint8_t data = m_length - 2U; - m_rxP25Data.addData(&data, 1U); + m_rxP25Queue.addData(&data, 1U); data = TAG_DATA; - m_rxP25Data.addData(&data, 1U); + m_rxP25Queue.addData(&data, 1U); - m_rxP25Data.addData(m_buffer + 3U, m_length - 3U); + m_rxP25Queue.addData(m_buffer + 3U, m_length - 3U); #endif // defined(ENABLE_P25) } break; @@ -722,10 +786,10 @@ void Modem::clock(uint32_t ms) } uint8_t data = 1U; - m_rxP25Data.addData(&data, 1U); + m_rxP25Queue.addData(&data, 1U); data = TAG_LOST; - m_rxP25Data.addData(&data, 1U); + m_rxP25Queue.addData(&data, 1U); #endif // defined(ENABLE_P25) } break; @@ -743,12 +807,12 @@ void Modem::clock(uint32_t ms) } uint8_t data = m_length - 2U; - m_rxNXDNData.addData(&data, 1U); + m_rxNXDNQueue.addData(&data, 1U); data = TAG_DATA; - m_rxNXDNData.addData(&data, 1U); + m_rxNXDNQueue.addData(&data, 1U); - m_rxNXDNData.addData(m_buffer + 3U, m_length - 3U); + m_rxNXDNQueue.addData(m_buffer + 3U, m_length - 3U); #endif // defined(ENABLE_NXDN) } break; @@ -765,10 +829,10 @@ void Modem::clock(uint32_t ms) } uint8_t data = 1U; - m_rxNXDNData.addData(&data, 1U); + m_rxNXDNQueue.addData(&data, 1U); data = TAG_LOST; - m_rxNXDNData.addData(&data, 1U); + m_rxNXDNQueue.addData(&data, 1U); #endif // defined(ENABLE_NXDN) } break; @@ -781,6 +845,11 @@ void Modem::clock(uint32_t ms) m_isHotspot = (m_buffer[3U] & 0x01U) == 0x01U; + // override hotspot flag if we're forcing hotspot + if (m_forceHotspot) { + m_isHotspot = m_forceHotspot; + } + bool dmrEnable = (m_buffer[3U] & 0x02U) == 0x02U; bool p25Enable = (m_buffer[3U] & 0x08U) == 0x08U; bool nxdnEnable = (m_buffer[3U] & 0x10U) == 0x10U; @@ -861,8 +930,8 @@ void Modem::clock(uint32_t ms) LogDebug(LOG_MODEM, "Modem::clock(), CMD_GET_STATUS, isHotspot = %u, dmr = %u / %u, p25 = %u / %u, nxdn = %u / %u, modemState = %u, tx = %u, adcOverflow = %u, rxOverflow = %u, txOverflow = %u, dacOverflow = %u, dmrSpace1 = %u, dmrSpace2 = %u, p25Space = %u, nxdnSpace = %u", m_isHotspot, dmrEnable, m_dmrEnabled, p25Enable, m_p25Enabled, nxdnEnable, m_nxdnEnabled, m_modemState, m_tx, adcOverflow, rxOverflow, txOverflow, dacOverflow, m_dmrSpace1, m_dmrSpace2, m_p25Space, m_nxdnSpace); LogDebug(LOG_MODEM, "Modem::clock(), CMD_GET_STATUS, rxDMRData1 size = %u, len = %u, free = %u; rxDMRData2 size = %u, len = %u, free = %u, rxP25Data size = %u, len = %u, free = %u, rxNXDNData size = %u, len = %u, free = %u", - m_rxDMRData1.length(), m_rxDMRData1.dataSize(), m_rxDMRData1.freeSpace(), m_rxDMRData2.length(), m_rxDMRData2.dataSize(), m_rxDMRData2.freeSpace(), - m_rxP25Data.length(), m_rxP25Data.dataSize(), m_rxP25Data.freeSpace(), m_rxNXDNData.length(), m_rxNXDNData.dataSize(), m_rxNXDNData.freeSpace()); + m_rxDMRQueue1.length(), m_rxDMRQueue1.dataSize(), m_rxDMRQueue1.freeSpace(), m_rxDMRQueue2.length(), m_rxDMRQueue2.dataSize(), m_rxDMRQueue2.freeSpace(), + m_rxP25Queue.length(), m_rxP25Queue.dataSize(), m_rxP25Queue.freeSpace(), m_rxNXDNQueue.length(), m_rxNXDNQueue.dataSize(), m_rxNXDNQueue.freeSpace()); } m_inactivityTimer.start(); @@ -874,7 +943,7 @@ void Modem::clock(uint32_t ms) break; case CMD_NAK: - LogWarning(LOG_MODEM, "NAK, command = 0x%02X, reason = %u", m_buffer[3U], m_buffer[4U]); + LogWarning(LOG_MODEM, "NAK, command = 0x%02X (%s), reason = %u (%s)", m_buffer[3U], cmdToString(m_buffer[3U]).c_str(), m_buffer[4U], rsnToString(m_buffer[4U]).c_str()); break; case CMD_DEBUG1: @@ -919,16 +988,16 @@ void Modem::close() /// /// Buffer to write frame data to. /// Length of data read from ring buffer. -uint32_t Modem::readDMRData1(uint8_t* data) +uint32_t Modem::readDMRFrame1(uint8_t* data) { assert(data != nullptr); - if (m_rxDMRData1.isEmpty()) + if (m_rxDMRQueue1.isEmpty()) return 0U; uint8_t len = 0U; - m_rxDMRData1.getData(&len, 1U); - m_rxDMRData1.getData(data, len); + m_rxDMRQueue1.getData(&len, 1U); + m_rxDMRQueue1.getData(data, len); return len; } @@ -938,16 +1007,16 @@ uint32_t Modem::readDMRData1(uint8_t* data) /// /// Buffer to write frame data to. /// Length of data read from ring buffer. -uint32_t Modem::readDMRData2(uint8_t* data) +uint32_t Modem::readDMRFrame2(uint8_t* data) { assert(data != nullptr); - if (m_rxDMRData2.isEmpty()) + if (m_rxDMRQueue2.isEmpty()) return 0U; uint8_t len = 0U; - m_rxDMRData2.getData(&len, 1U); - m_rxDMRData2.getData(data, len); + m_rxDMRQueue2.getData(&len, 1U); + m_rxDMRQueue2.getData(data, len); return len; } @@ -957,16 +1026,16 @@ uint32_t Modem::readDMRData2(uint8_t* data) /// /// Buffer to write frame data to. /// Length of data read from ring buffer. -uint32_t Modem::readP25Data(uint8_t* data) +uint32_t Modem::readP25Frame(uint8_t* data) { assert(data != nullptr); - if (m_rxP25Data.isEmpty()) + if (m_rxP25Queue.isEmpty()) return 0U; uint8_t len = 0U; - m_rxP25Data.getData(&len, 1U); - m_rxP25Data.getData(data, len); + m_rxP25Queue.getData(&len, 1U); + m_rxP25Queue.getData(data, len); return len; } @@ -976,16 +1045,16 @@ uint32_t Modem::readP25Data(uint8_t* data) /// /// Buffer to write frame data to. /// Length of data read from ring buffer. -uint32_t Modem::readNXDNData(uint8_t* data) +uint32_t Modem::readNXDNFrame(uint8_t* data) { assert(data != nullptr); - if (m_rxNXDNData.isEmpty()) + if (m_rxNXDNQueue.isEmpty()) return 0U; uint8_t len = 0U; - m_rxNXDNData.getData(&len, 1U); - m_rxNXDNData.getData(data, len); + m_rxNXDNQueue.getData(&len, 1U); + m_rxNXDNQueue.getData(data, len); return len; } @@ -1084,7 +1153,7 @@ bool Modem::hasError() const /// /// Clears any buffered DMR Slot 1 frame data to be sent to the air interface modem. /// -void Modem::clearDMRData1() +void Modem::clearDMRFrame1() { // TODO -- implement modem side buffer clear } @@ -1092,7 +1161,7 @@ void Modem::clearDMRData1() /// /// Clears any buffered DMR Slot 2 frame data to be sent to the air interface modem. /// -void Modem::clearDMRData2() +void Modem::clearDMRFrame2() { // TODO -- implement modem side buffer clear } @@ -1100,7 +1169,7 @@ void Modem::clearDMRData2() /// /// Clears any buffered P25 frame data to be sent to the air interface modem. /// -void Modem::clearP25Data() +void Modem::clearP25Frame() { uint8_t buffer[3U]; @@ -1116,7 +1185,7 @@ void Modem::clearP25Data() /// /// Clears any buffered NXDN frame data to be sent to the air interface modem. /// -void Modem::clearNXDNData() +void Modem::clearNXDNFrame() { // TODO -- implement modem side buffer clear } @@ -1126,7 +1195,7 @@ void Modem::clearNXDNData() /// /// Data to write to ring buffer. /// Length of data to write. -void Modem::injectDMRData1(const uint8_t* data, uint32_t length) +void Modem::injectDMRFrame1(const uint8_t* data, uint32_t length) { #if defined(ENABLE_DMR) assert(data != nullptr); @@ -1141,14 +1210,14 @@ void Modem::injectDMRData1(const uint8_t* data, uint32_t length) Utils::dump(1U, "Injected DMR Slot 1 Data", data, length); uint8_t val = length; - m_rxDMRData1.addData(&val, 1U); + m_rxDMRQueue1.addData(&val, 1U); val = TAG_DATA; - m_rxDMRData1.addData(&val, 1U); + m_rxDMRQueue1.addData(&val, 1U); val = dmr::DMR_SYNC_VOICE & dmr::DMR_SYNC_DATA; // valid sync - m_rxDMRData1.addData(&val, 1U); + m_rxDMRQueue1.addData(&val, 1U); - m_rxDMRData1.addData(data, length); + m_rxDMRQueue1.addData(data, length); #endif // defined(ENABLE_DMR) } @@ -1157,7 +1226,7 @@ void Modem::injectDMRData1(const uint8_t* data, uint32_t length) /// /// Data to write to ring buffer. /// Length of data to write. -void Modem::injectDMRData2(const uint8_t* data, uint32_t length) +void Modem::injectDMRFrame2(const uint8_t* data, uint32_t length) { #if defined(ENABLE_DMR) assert(data != nullptr); @@ -1172,14 +1241,14 @@ void Modem::injectDMRData2(const uint8_t* data, uint32_t length) Utils::dump(1U, "Injected DMR Slot 2 Data", data, length); uint8_t val = length; - m_rxDMRData2.addData(&val, 1U); + m_rxDMRQueue2.addData(&val, 1U); val = TAG_DATA; - m_rxDMRData2.addData(&val, 1U); + m_rxDMRQueue2.addData(&val, 1U); val = dmr::DMR_SYNC_VOICE & dmr::DMR_SYNC_DATA; // valid sync - m_rxDMRData2.addData(&val, 1U); + m_rxDMRQueue2.addData(&val, 1U); - m_rxDMRData2.addData(data, length); + m_rxDMRQueue2.addData(data, length); #endif // defined(ENABLE_DMR) } @@ -1188,7 +1257,7 @@ void Modem::injectDMRData2(const uint8_t* data, uint32_t length) /// /// Data to write to ring buffer. /// Length of data to write. -void Modem::injectP25Data(const uint8_t* data, uint32_t length) +void Modem::injectP25Frame(const uint8_t* data, uint32_t length) { #if defined(ENABLE_P25) assert(data != nullptr); @@ -1198,14 +1267,14 @@ void Modem::injectP25Data(const uint8_t* data, uint32_t length) Utils::dump(1U, "Injected P25 Data", data, length); uint8_t val = length; - m_rxP25Data.addData(&val, 1U); + m_rxP25Queue.addData(&val, 1U); val = TAG_DATA; - m_rxP25Data.addData(&val, 1U); + m_rxP25Queue.addData(&val, 1U); val = 0x01U; // valid sync - m_rxP25Data.addData(&val, 1U); + m_rxP25Queue.addData(&val, 1U); - m_rxP25Data.addData(data, length); + m_rxP25Queue.addData(data, length); #endif // defined(ENABLE_P25) } @@ -1214,7 +1283,7 @@ void Modem::injectP25Data(const uint8_t* data, uint32_t length) /// /// Data to write to ring buffer. /// Length of data to write. -void Modem::injectNXDNData(const uint8_t* data, uint32_t length) +void Modem::injectNXDNFrame(const uint8_t* data, uint32_t length) { #if defined(ENABLE_NXDN) assert(data != nullptr); @@ -1224,14 +1293,14 @@ void Modem::injectNXDNData(const uint8_t* data, uint32_t length) Utils::dump(1U, "Injected NXDN Data", data, length); uint8_t val = length; - m_rxNXDNData.addData(&val, 1U); + m_rxNXDNQueue.addData(&val, 1U); val = TAG_DATA; - m_rxNXDNData.addData(&val, 1U); + m_rxNXDNQueue.addData(&val, 1U); val = 0x01U; // valid sync - m_rxNXDNData.addData(&val, 1U); + m_rxNXDNQueue.addData(&val, 1U); - m_rxNXDNData.addData(data, length); + m_rxNXDNQueue.addData(data, length); #endif // defined(ENABLE_NXDN) } @@ -1241,7 +1310,7 @@ void Modem::injectNXDNData(const uint8_t* data, uint32_t length) /// Data to write to ring buffer. /// Length of data to write. /// True, if data is written, otherwise false. -bool Modem::writeDMRData1(const uint8_t* data, uint32_t length) +bool Modem::writeDMRFrame1(const uint8_t* data, uint32_t length) { #if defined(ENABLE_DMR) assert(data != nullptr); @@ -1298,7 +1367,7 @@ bool Modem::writeDMRData1(const uint8_t* data, uint32_t length) /// Data to write to ring buffer. /// Length of data to write. /// True, if data is written, otherwise false. -bool Modem::writeDMRData2(const uint8_t* data, uint32_t length) +bool Modem::writeDMRFrame2(const uint8_t* data, uint32_t length) { #if defined(ENABLE_DMR) assert(data != nullptr); @@ -1355,7 +1424,7 @@ bool Modem::writeDMRData2(const uint8_t* data, uint32_t length) /// Data to write to ring buffer. /// Length of data to write. /// True, if data is written, otherwise false. -bool Modem::writeP25Data(const uint8_t* data, uint32_t length) +bool Modem::writeP25Frame(const uint8_t* data, uint32_t length) { #if defined(ENABLE_P25) assert(data != nullptr); @@ -1412,7 +1481,7 @@ bool Modem::writeP25Data(const uint8_t* data, uint32_t length) /// Data to write to ring buffer. /// Length of data to write. /// True, if data is written, otherwise false. -bool Modem::writeNXDNData(const uint8_t* data, uint32_t length) +bool Modem::writeNXDNFrame(const uint8_t* data, uint32_t length) { #if defined(ENABLE_NXDN) assert(data != nullptr); @@ -1874,7 +1943,7 @@ bool Modem::writeConfig() if (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK) { count++; if (count >= MAX_RESPONSES) { - LogError(LOG_MODEM, "No response, SET_CONFIG command"); + LogError(LOG_MODEM, "No response, %s command", cmdToString(CMD_SET_CONFIG).c_str()); return false; } } @@ -1883,31 +1952,7 @@ bool Modem::writeConfig() Utils::dump(1U, "Modem::writeConfig(), Response", m_buffer, m_length); #endif if (resp == RTM_OK && m_buffer[2U] == CMD_NAK) { - LogError(LOG_MODEM, "NAK, SET_CONFIG, command = 0x%02X, reason = %u", m_buffer[3U], m_buffer[4U]); - - switch (m_buffer[4U]) { - case RSN_INVALID_FDMA_PREAMBLE: - LogError(LOG_MODEM, "Invalid FDMA preamble"); - break; - case RSN_INVALID_DMR_CC: - LogError(LOG_MODEM, "Invalid DMR Color Code"); - break; - case RSN_INVALID_DMR_RX_DELAY: - LogError(LOG_MODEM, "Invalid DMR Rx Delay"); - break; - case RSN_INVALID_P25_CORR_COUNT: - LogError(LOG_MODEM, "Invalid P25 correlation count"); - break; - case RSN_HS_NO_DUAL_MODE: - LogError(LOG_MODEM, "Cannot multi-mode, DMR, P25 and NXDN when using hotspot!"); - break; - case RSN_INVALID_REQUEST: - LogError(LOG_MODEM, "Invalid SET_CONFIG request"); - break; - default: - break; - } - + LogError(LOG_MODEM, "NAK, %s, command = 0x%02X, reason = %u (%s)", cmdToString(CMD_SET_CONFIG).c_str(), m_buffer[3U], m_buffer[4U], rsnToString(m_buffer[4U]).c_str()); return false; } @@ -1956,23 +2001,14 @@ bool Modem::writeSymbolAdjust() if (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK) { count++; if (count >= MAX_RESPONSES) { - LogError(LOG_MODEM, "No response, SET_SYMLVLADJ command"); + LogError(LOG_MODEM, "No response, %s command", cmdToString(CMD_SET_SYMLVLADJ).c_str()); return false; } } } while (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK); if (resp == RTM_OK && m_buffer[2U] == CMD_NAK) { - LogError(LOG_MODEM, "NAK, SET_SYMLVLADJ, command = 0x%02X, reason = %u", m_buffer[3U], m_buffer[4U]); - - switch (m_buffer[4U]) { - case RSN_INVALID_REQUEST: - LogError(LOG_MODEM, "Invalid SET_SYMLVLADJ request"); - break; - default: - break; - } - + LogError(LOG_MODEM, "NAK, %s, command = 0x%02X, reason = %u (%s)", cmdToString(CMD_SET_SYMLVLADJ).c_str(), m_buffer[3U], m_buffer[4U], rsnToString(m_buffer[4U]).c_str()); return false; } @@ -2043,7 +2079,7 @@ bool Modem::writeRFParams() if (resp == RTM_OK && m_buffer[2U] != RSN_OK && m_buffer[2U] != RSN_NAK) { count++; if (count >= MAX_RESPONSES) { - LogError(LOG_MODEM, "No response, SET_RFPARAMS command"); + LogError(LOG_MODEM, "No response, %s command", cmdToString(CMD_SET_RFPARAMS).c_str()); return false; } } @@ -2052,16 +2088,7 @@ bool Modem::writeRFParams() // CUtils::dump(1U, "Response", m_buffer, m_length); if (resp == RTM_OK && m_buffer[2U] == RSN_NAK) { - LogError(LOG_MODEM, "NAK, SET_RFPARAMS, command = 0x%02X, reason = %u", m_buffer[3U], m_buffer[4U]); - - switch (m_buffer[4U]) { - case RSN_INVALID_REQUEST: - LogError(LOG_MODEM, "Invalid SET_RFPARAMS request"); - break; - default: - break; - } - + LogError(LOG_MODEM, "NAK, %s, command = 0x%02X, reason = %u (%s)", cmdToString(CMD_SET_RFPARAMS).c_str(), m_buffer[3U], m_buffer[4U], rsnToString(m_buffer[4U]).c_str()); return false; } @@ -2095,10 +2122,12 @@ bool Modem::readFlash() continue; if (resp == RTM_OK && m_buffer[2U] == CMD_NAK) { - LogWarning(LOG_MODEM, "Modem::readFlash(), old modem that doesn't support flash commands?"); + LogWarning(LOG_MODEM, "%s, old modem that doesn't support flash commands?", cmdToString(CMD_FLSH_READ).c_str()); + m_flashDisabled = true; return false; } + m_flashDisabled = false; if (resp == RTM_OK && m_buffer[2U] == CMD_FLSH_READ) { uint8_t len = m_buffer[1U]; if (m_debug) { @@ -2458,3 +2487,138 @@ RESP_TYPE_DVM Modem::getResponse(bool noReportInvalid) return RTM_OK; } +/// +/// Helper to convert a serial opcode to a string. +/// +std::string Modem::cmdToString(uint8_t opcode) +{ + switch (opcode) { + case CMD_GET_VERSION: + return std::string("GET_VERSION"); + case CMD_GET_STATUS: + return std::string("GET_STATUS"); + case CMD_SET_CONFIG: + return std::string("SET_CONFIG"); + case CMD_SET_MODE: + return std::string("SET_MODE"); + + case CMD_SET_SYMLVLADJ: + return std::string("SET_SYMLVLADJ"); + case CMD_SET_RXLEVEL: + return std::string("SET_RXLEVEL"); + case CMD_SET_RFPARAMS: + return std::string("SET_RFPARAMS"); + + case CMD_CAL_DATA: + return std::string("CAL_DATA"); + case CMD_RSSI_DATA: + return std::string("RSSI_DATA"); + + case CMD_SEND_CWID: + return std::string("SEND_CWID"); + + case CMD_SET_BUFFERS: + return std::string("SET_BUFFERS"); + + case CMD_DMR_DATA1: + return std::string("DMR_DATA1"); + case CMD_DMR_LOST1: + return std::string("DMR_LOST1"); + case CMD_DMR_DATA2: + return std::string("DMR_DATA2"); + case CMD_DMR_LOST2: + return std::string("DMR_LOST2"); + case CMD_DMR_SHORTLC: + return std::string("DMR_SHORTLC"); + case CMD_DMR_START: + return std::string("DMR_START"); + case CMD_DMR_ABORT: + return std::string("DMR_ABORT"); + case CMD_DMR_CACH_AT_CTRL: + return std::string("DMR_CACH_AT_CTRL"); + + case CMD_P25_DATA: + return std::string("P25_DATA"); + case CMD_P25_LOST: + return std::string("P25_LOST"); + case CMD_P25_CLEAR: + return std::string("P25_CLEAR"); + + case CMD_NXDN_DATA: + return std::string("NXDN_DATA"); + case CMD_NXDN_LOST: + return std::string("NXDN_LOST"); + + case CMD_ACK: + return std::string("ACK"); + case CMD_NAK: + return std::string("NAK"); + + case CMD_FLSH_READ: + return std::string("FLSH_READ"); + case CMD_FLSH_WRITE: + return std::string("FLSH_WRITE"); + + default: + return std::string(); + } +} + +/// +/// Helper to convert a serial reason code to a string. +/// +std::string Modem::rsnToString(uint8_t reason) +{ + switch (reason) { + case RSN_OK: + return std::string("OK"); + case RSN_NAK: + return std::string("NAK"); + + case RSN_ILLEGAL_LENGTH: + return std::string("ILLEGAL_LENGTH"); + case RSN_INVALID_REQUEST: + return std::string("INVALID_REQUEST"); + case RSN_RINGBUFF_FULL: + return std::string("RINGBUFF_FULL"); + + case RSN_INVALID_FDMA_PREAMBLE: + return std::string("INVALID_FDMA_PREAMBLE"); + case RSN_INVALID_MODE: + return std::string("INVALID_MODE"); + + case RSN_INVALID_DMR_CC: + return std::string("INVALID_DMR_CC"); + case RSN_INVALID_DMR_SLOT: + return std::string("INVALID_DMR_SLOT"); + case RSN_INVALID_DMR_START: + return std::string("INVALID_DMR_START"); + case RSN_INVALID_DMR_RX_DELAY: + return std::string("INVALID_DMR_RX_DELAY"); + + case RSN_INVALID_P25_CORR_COUNT: + return std::string("INVALID_P25_CORR_COUNT"); + + case RSN_NO_INTERNAL_FLASH: + return std::string("NO_INTERNAL_FLASH"); + case RSN_FAILED_ERASE_FLASH: + return std::string("FAILED_ERASE_FLASH"); + case RSN_FAILED_WRITE_FLASH: + return std::string("FAILED_WRITE_FLASH"); + case RSN_FLASH_WRITE_TOO_BIG: + return std::string("FLASH_WRITE_TOO_BIG"); + + case RSN_HS_NO_DUAL_MODE: + return std::string("HS_NO_DUAL_MODE"); + + case RSN_DMR_DISABLED: + return std::string("DMR_DISABLED"); + case RSN_P25_DISABLED: + return std::string("P25_DISABLED"); + case RSN_NXDN_DISABLED: + return std::string("NXDN_DISABLED"); + + default: + return std::string(); + } +} diff --git a/src/modem/Modem.h b/src/modem/Modem.h index 52c8c869..39e9d569 100644 --- a/src/modem/Modem.h +++ b/src/modem/Modem.h @@ -49,6 +49,13 @@ #define MODEM_UNSUPPORTED_STR "Modem protocol: %u, unsupported! Stopping." #define NULL_MODEM "null" +// 505 = DMR_FRAME_LENGTH_BYTES * 15 + 10 (BUFFER_LEN = DMR_FRAME_LENGTH_BYTES * NO_OF_FRAMES + 10) +#define DMR_TX_BUFFER_LEN 505U // 15 frames + pad +// 442 = P25_LDU_FRAME_LENGTH_BYTES * 2 + 10 (BUFFER_LEN = P25_LDU_FRAME_LENGTH_BYTES * NO_OF_FRAMES + 10) +#define P25_TX_BUFFER_LEN 442U // 2 frames + pad +// 538 = NXDN_FRAME_LENGTH_BYTES * 11 + 10 (BUFFER_LEN = NXDN_FRAME_LENGTH_BYTES * NO_OF_FRAMES) +#define NXDN_TX_BUFFER_LEN 538U // 11 frames + pad + // --------------------------------------------------------------------------- // Macros // --------------------------------------------------------------------------- @@ -66,6 +73,18 @@ class HOST_SW_API HostCal; class HOST_SW_API RESTAPI; +class HOST_SW_API HostSetup; +#if defined(ENABLE_SETUP_TUI) +class HOST_SW_API SetupApplication; +class HOST_SW_API SetupMainWnd; + +class HOST_SW_API LevelAdjustWnd; +class HOST_SW_API SymbLevelAdjustWnd; +class HOST_SW_API HSBandwidthAdjustWnd; +class HOST_SW_API HSGainAdjustWnd; +class HOST_SW_API FIFOBufferAdjustWnd; +#endif // defined(ENABLE_SETUP_TUI) + namespace modem { // --------------------------------------------------------------------------- @@ -119,6 +138,8 @@ namespace modem CMD_SEND_CWID = 0x0AU, + CMD_SET_BUFFERS = 0x0FU, + CMD_DMR_DATA1 = 0x18U, CMD_DMR_LOST1 = 0x19U, CMD_DMR_DATA2 = 0x1AU, @@ -251,6 +272,8 @@ namespace modem void setP25DFSI(bool dfsi); /// Sets the RF receive deviation levels. void setRXLevel(float rxLevel); + /// Sets the modem transmit FIFO buffer lengths. + void setFifoLength(uint16_t dmrLength, uint16_t p25Length, uint16_t nxdnLength); /// Sets a custom modem response handler. /// If the response handler returns true, processing will stop, otherwise it will continue. @@ -272,13 +295,13 @@ namespace modem void close(); /// Reads DMR Slot 1 frame data from the DMR Slot 1 ring buffer. - uint32_t readDMRData1(uint8_t* data); + uint32_t readDMRFrame1(uint8_t* data); /// Reads DMR Slot 2 frame data from the DMR Slot 1 ring buffer. - uint32_t readDMRData2(uint8_t* data); + uint32_t readDMRFrame2(uint8_t* data); /// Reads P25 frame data from the P25 ring buffer. - uint32_t readP25Data(uint8_t* data); + uint32_t readP25Frame(uint8_t* data); /// Reads NXDN frame data from the NXDN ring buffer. - uint32_t readNXDNData(uint8_t* data); + uint32_t readNXDNFrame(uint8_t* data); /// Helper to test if the DMR Slot 1 ring buffer has free space. bool hasDMRSpace1() const; @@ -306,31 +329,31 @@ namespace modem bool hasError() const; /// Clears any buffered DMR Slot 1 frame data to be sent to the air interface modem. - void clearDMRData1(); + void clearDMRFrame1(); /// Clears any buffered DMR Slot 2 frame data to be sent to the air interface modem. - void clearDMRData2(); + void clearDMRFrame2(); /// Clears any buffered P25 frame data to be sent to the air interface modem. - void clearP25Data(); + void clearP25Frame(); /// Clears any buffered NXDN frame data to be sent to the air interface modem. - void clearNXDNData(); + void clearNXDNFrame(); /// Internal helper to inject DMR Slot 1 frame data as if it came from the air interface modem. - void injectDMRData1(const uint8_t* data, uint32_t length); + void injectDMRFrame1(const uint8_t* data, uint32_t length); /// Internal helper to inject DMR Slot 2 frame data as if it came from the air interface modem. - void injectDMRData2(const uint8_t* data, uint32_t length); + void injectDMRFrame2(const uint8_t* data, uint32_t length); /// Internal helper to inject P25 frame data as if it came from the air interface modem. - void injectP25Data(const uint8_t* data, uint32_t length); + void injectP25Frame(const uint8_t* data, uint32_t length); /// Internal helper to inject NXDN frame data as if it came from the air interface modem. - void injectNXDNData(const uint8_t* data, uint32_t length); + void injectNXDNFrame(const uint8_t* data, uint32_t length); /// Writes DMR Slot 1 frame data to the DMR Slot 1 ring buffer. - bool writeDMRData1(const uint8_t* data, uint32_t length); + bool writeDMRFrame1(const uint8_t* data, uint32_t length); /// Writes DMR Slot 2 frame data to the DMR Slot 2 ring buffer. - bool writeDMRData2(const uint8_t* data, uint32_t length); + bool writeDMRFrame2(const uint8_t* data, uint32_t length); /// Writes P25 frame data to the P25 ring buffer. - bool writeP25Data(const uint8_t* data, uint32_t length); + bool writeP25Frame(const uint8_t* data, uint32_t length); /// Writes NXDN frame data to the NXDN ring buffer. - bool writeNXDNData(const uint8_t* data, uint32_t length); + bool writeNXDNFrame(const uint8_t* data, uint32_t length); /// Triggers the start of DMR transmit. bool writeDMRStart(bool tx); @@ -359,6 +382,18 @@ namespace modem friend class ::HostCal; friend class ::RESTAPI; + friend class ::HostSetup; +#if defined(ENABLE_SETUP_TUI) + friend class ::SetupApplication; + friend class ::SetupMainWnd; + + friend class ::LevelAdjustWnd; + friend class ::SymbLevelAdjustWnd; + friend class ::HSBandwidthAdjustWnd; + friend class ::HSGainAdjustWnd; + friend class ::FIFOBufferAdjustWnd; +#endif // defined(ENABLE_SETUP_TUI) + port::IModemPort* m_port; uint8_t m_protoVer; @@ -394,6 +429,7 @@ namespace modem int m_txDCOffset; // dedicated modem - Tx signal DC offset bool m_isHotspot; + bool m_forceHotspot; uint32_t m_rxFrequency; // hotspot modem - Rx Frequency int m_rxTuning; // hotspot modem - Rx Frequency Offset @@ -429,6 +465,10 @@ namespace modem uint8_t m_rssiCoarsePot; // dedicated modem - with softpot uint8_t m_rssiFinePot; // dedicated modem - with softpot + uint16_t m_dmrFifoLength; + uint16_t m_p25FifoLength; + uint16_t m_nxdnFifoLength; + uint32_t m_adcOverFlowCount; // dedicated modem - ADC overflow count uint32_t m_dacOverFlowCount; // dedicated modem - DAC overflow count @@ -445,10 +485,10 @@ namespace modem std::function m_closePortHandler; std::function m_rspHandler; - RingBuffer m_rxDMRData1; - RingBuffer m_rxDMRData2; - RingBuffer m_rxP25Data; - RingBuffer m_rxNXDNData; + RingBuffer m_rxDMRQueue1; + RingBuffer m_rxDMRQueue2; + RingBuffer m_rxP25Queue; + RingBuffer m_rxNXDNQueue; bool m_useDFSI; @@ -495,6 +535,11 @@ namespace modem /// Helper to get the raw response packet from modem. RESP_TYPE_DVM getResponse(bool noReportInvalid = false); + /// Helper to convert a serial opcode to a string. + std::string cmdToString(uint8_t opcode); + /// Helper to convert a serial reason code to a string. + std::string rsnToString(uint8_t reason); + public: /// Flag indicating if modem trace is enabled. __READONLY_PROPERTY(bool, trace, Trace); diff --git a/src/modem/port/ModemNullPort.cpp b/src/modem/port/ModemNullPort.cpp index df97b6bc..61180f7f 100644 --- a/src/modem/port/ModemNullPort.cpp +++ b/src/modem/port/ModemNullPort.cpp @@ -108,6 +108,9 @@ int ModemNullPort::write(const uint8_t* buffer, uint32_t length) case CMD_SET_MODE: writeAck(buffer[2U]); break; + case CMD_FLSH_READ: + writeNAK(CMD_FLSH_READ, RSN_NO_INTERNAL_FLASH); + break; default: break; } @@ -128,7 +131,7 @@ void ModemNullPort::close() // --------------------------------------------------------------------------- /// -/// +/// Helper to return a faked modem version. /// void ModemNullPort::getVersion() { @@ -154,7 +157,7 @@ void ModemNullPort::getVersion() } /// -/// +/// Helper to return a faked modem status. /// void ModemNullPort::getStatus() { @@ -184,8 +187,9 @@ void ModemNullPort::getStatus() } /// -/// +/// Helper to write a faked modem acknowledge. /// +/// void ModemNullPort::writeAck(uint8_t type) { unsigned char reply[4U]; @@ -197,3 +201,21 @@ void ModemNullPort::writeAck(uint8_t type) m_buffer.addData(reply, 4U); } + +/// +/// Helper to write a faked modem negative acknowledge. +/// +/// +/// +void ModemNullPort::writeNAK(uint8_t opcode, uint8_t err) +{ + uint8_t reply[5U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 5U; + reply[2U] = CMD_NAK; + reply[3U] = opcode; + reply[4U] = err; + + m_buffer.addData(reply, 5U); +} \ No newline at end of file diff --git a/src/modem/port/ModemNullPort.h b/src/modem/port/ModemNullPort.h index 85e14cbe..47bf37e7 100644 --- a/src/modem/port/ModemNullPort.h +++ b/src/modem/port/ModemNullPort.h @@ -53,15 +53,15 @@ namespace modem virtual ~ModemNullPort(); /// Opens a connection to the port. - virtual bool open(); + bool open(); /// Reads data from the port. - virtual int read(uint8_t* buffer, uint32_t length); + int read(uint8_t* buffer, uint32_t length); /// Writes data to the port. - virtual int write(const uint8_t* buffer, uint32_t length); + int write(const uint8_t* buffer, uint32_t length); /// Closes the connection to the port. - virtual void close(); + void close(); private: RingBuffer m_buffer; @@ -72,6 +72,8 @@ namespace modem void getStatus(); /// Helper to write a faked modem acknowledge. void writeAck(uint8_t type); + /// Helper to write a faked modem negative acknowledge. + void writeNAK(uint8_t opcode, uint8_t err); }; } // namespace port } // namespace modem diff --git a/src/modem/port/PseudoPTYPort.cpp b/src/modem/port/PseudoPTYPort.cpp index 0d7b031b..567289f5 100644 --- a/src/modem/port/PseudoPTYPort.cpp +++ b/src/modem/port/PseudoPTYPort.cpp @@ -34,8 +34,6 @@ #include #include -#if !defined(_WIN32) && !defined(_WIN64) - using namespace modem::port; #include @@ -114,5 +112,3 @@ void PseudoPTYPort::close() UARTPort::close(); ::unlink(m_symlink.c_str()); } - -#endif diff --git a/src/modem/port/PseudoPTYPort.h b/src/modem/port/PseudoPTYPort.h index 6374b9fb..92c0cdb0 100644 --- a/src/modem/port/PseudoPTYPort.h +++ b/src/modem/port/PseudoPTYPort.h @@ -30,8 +30,6 @@ #if !defined(__PSEUDO_PTY_PORT_H__) #define __PSEUDO_PTY_PORT_H__ -#if !defined(_WIN32) && !defined(_WIN64) - #include "Defines.h" #include "modem/port/UARTPort.h" @@ -56,17 +54,15 @@ namespace modem virtual ~PseudoPTYPort(); /// Opens a connection to the serial port. - virtual bool open(); + bool open(); /// Closes the connection to the serial port. - virtual void close(); + void close(); protected: std::string m_symlink; }; // class HOST_SW_API PseudoPTYPort : public UARTPort } // namespace port -} // namespace Modem - -#endif +} // namespace modem #endif // __PSEUDO_PTY_PORT_H__ diff --git a/src/modem/port/UARTPort.cpp b/src/modem/port/UARTPort.cpp index d7873772..de168d29 100644 --- a/src/modem/port/UARTPort.cpp +++ b/src/modem/port/UARTPort.cpp @@ -40,10 +40,6 @@ using namespace modem::port; #include -#if defined(_WIN32) || defined(_WIN64) -#include -#include -#else #include #include #include @@ -51,207 +47,11 @@ using namespace modem::port; #include #include #include -#endif // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- -#if defined(_WIN32) || defined(_WIN64) -/// -/// Initializes a new instance of the UARTPort class. -/// -/// Serial port device. -/// Serial port speed. -/// -UARTPort::UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS) : - m_isOpen(false), - m_device(device), - m_speed(speed), - m_assertRTS(assertRTS), - m_handle(INVALID_HANDLE_VALUE) -{ - assert(!device.empty()); -} - -/// -/// Finalizes a instance of the UARTPort class. -/// -UARTPort::~UARTPort() -{ - /* stub */ -} - -/// -/// Opens a connection to the serial port. -/// -/// True, if connection is opened, otherwise false. -bool UARTPort::open() -{ - if (m_isOpen) - return true; - - assert(m_handle == INVALID_HANDLE_VALUE); - - DWORD errCode; - - // Convert "\\.\COM10" to "COM10" - std::string baseName = m_device.substr(4U); - - m_handle = ::CreateFileA(m_device.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (m_handle == INVALID_HANDLE_VALUE) { - ::LogError(LOG_HOST, "Cannot open device - %s, err=%04lx", m_device.c_str(), ::GetLastError()); - return false; - } - - DCB dcb; - if (::GetCommState(m_handle, &dcb) == 0) { - ::LogError(LOG_HOST, "Cannot get the attributes for %s, err=%04lx", m_device.c_str(), ::GetLastError()); - ::ClearCommError(m_handle, &errCode, NULL); - ::CloseHandle(m_handle); - return false; - } - - dcb.BaudRate = DWORD(m_speed); - dcb.ByteSize = 8; - dcb.Parity = NOPARITY; - dcb.fParity = FALSE; - dcb.StopBits = ONESTOPBIT; - dcb.fInX = FALSE; - dcb.fOutX = FALSE; - dcb.fOutxCtsFlow = FALSE; - dcb.fOutxDsrFlow = FALSE; - dcb.fDsrSensitivity = FALSE; - dcb.fDtrControl = DTR_CONTROL_DISABLE; - dcb.fRtsControl = RTS_CONTROL_DISABLE; - - if (::SetCommState(m_handle, &dcb) == 0) { - ::LogError(LOG_HOST, "Cannot set the attributes for %s, err=%04lx", m_device.c_str(), ::GetLastError()); - ::ClearCommError(m_handle, &errCode, NULL); - ::CloseHandle(m_handle); - return false; - } - - COMMTIMEOUTS timeouts; - if (!::GetCommTimeouts(m_handle, &timeouts)) { - ::LogError(LOG_HOST, "Cannot get the timeouts for %s, err=%04lx", m_device.c_str(), ::GetLastError()); - ::ClearCommError(m_handle, &errCode, NULL); - ::CloseHandle(m_handle); - return false; - } - - timeouts.ReadIntervalTimeout = MAXDWORD; - timeouts.ReadTotalTimeoutMultiplier = 0UL; - timeouts.ReadTotalTimeoutConstant = 0UL; - - if (!::SetCommTimeouts(m_handle, &timeouts)) { - ::LogError(LOG_HOST, "Cannot set the timeouts for %s, err=%04lx", m_device.c_str(), ::GetLastError()); - ::ClearCommError(m_handle, &errCode, NULL); - ::CloseHandle(m_handle); - return false; - } - - if (::EscapeCommFunction(m_handle, CLRDTR) == 0) { - ::LogError(LOG_HOST, "Cannot clear DTR for %s, err=%04lx", m_device.c_str(), ::GetLastError()); - ::ClearCommError(m_handle, &errCode, NULL); - ::CloseHandle(m_handle); - return false; - } - - if (::EscapeCommFunction(m_handle, m_assertRTS ? SETRTS : CLRRTS) == 0) { - ::LogError(LOG_HOST, "Cannot set/clear RTS for %s, err=%04lx", m_device.c_str(), ::GetLastError()); - ::ClearCommError(m_handle, &errCode, NULL); - ::CloseHandle(m_handle); - return false; - } - - ::ClearCommError(m_handle, &errCode, NULL); - - m_isOpen = true; - return true; -} - -/// -/// Reads data from the serial port. -/// -/// Buffer to read data from the serial port to. -/// Length of data to read from the serial port. -/// Actual length of data read from serial port. -int UARTPort::read(uint8_t* buffer, uint32_t length) -{ - assert(m_handle != INVALID_HANDLE_VALUE); - assert(buffer != nullptr); - - uint32_t ptr = 0U; - - while (ptr < length) { - int ret = readNonblock(buffer + ptr, length - ptr); - if (ret < 0) { - return ret; - } - else if (ret == 0) { - if (ptr == 0U) - return 0; - } - else { - ptr += ret; - } - } - - return int(length); -} - -/// -/// Writes data to the serial port. -/// -/// Buffer containing data to write to serial port. -/// Length of data to write to serial port. -/// Actual length of data written to the serial port. -int UARTPort::write(const uint8_t* buffer, uint32_t length) -{ - assert(buffer != nullptr); - - if (m_isOpen && m_handle == INVALID_HANDLE_VALUE) - return 0; - - assert(m_handle != INVALID_HANDLE_VALUE); - - if (length == 0U) - return 0; - - uint32_t ptr = 0U; - - while (ptr < length) { - DWORD bytes = 0UL; - BOOL ret = ::WriteFile(m_handle, buffer + ptr, length - ptr, &bytes, NULL); - if (!ret) { - ::LogError(LOG_HOST, "Error from WriteFile for %s: %04lx", m_device.c_str(), ::GetLastError()); - return -1; - } - - ptr += bytes; - } - - return int(length); -} - -/// -/// Closes the connection to the serial port. -/// -void UARTPort::close() -{ - if (!m_isOpen && m_handle == INVALID_HANDLE_VALUE) - return; - - assert(m_handle != INVALID_HANDLE_VALUE); - - ::CloseHandle(m_handle); - m_handle = INVALID_HANDLE_VALUE; - m_isOpen = false; -} - -#else - /// /// Initializes a new instance of the UARTPort class. /// @@ -431,7 +231,6 @@ int UARTPort::setNonblock(bool nonblock) return ::fcntl(m_fd, F_SETFL, flag); } #endif -#endif // --------------------------------------------------------------------------- // Private Class Members @@ -446,56 +245,11 @@ UARTPort::UARTPort(SERIAL_SPEED speed, bool assertRTS) : m_isOpen(false), m_speed(speed), m_assertRTS(assertRTS), -#if defined(_WIN32) || defined(_WIN64) - m_handle(INVALID_HANDLE_VALUE) -#else m_fd(-1) -#endif { /* stub */ } -#if defined(_WIN32) || defined(_WIN64) -/// -/// -/// -/// -/// -/// -int UARTPort::readNonblock(uint8_t* buffer, uint32_t length) -{ - assert(m_handle != INVALID_HANDLE_VALUE); - assert(buffer != nullptr); - - if (length == 0U) - return 0; - - DWORD errors; - COMSTAT status; - if (::ClearCommError(m_handle, &errors, &status) == 0) { - ::LogError(LOG_HOST, "Error from ClearCommError for %s, err=%04lx", m_device.c_str(), ::GetLastError()); - return -1; - } - - if (status.cbInQue == 0UL) - return 0; - - DWORD readLength = status.cbInQue; - if (length < readLength) - readLength = length; - - DWORD bytes = 0UL; - BOOL ret = ::ReadFile(m_handle, buffer, readLength, &bytes, NULL); - if (!ret) { - ::LogError(LOG_HOST, "Error from ReadFile for %s: %04lx", m_device.c_str(), ::GetLastError()); - return -1; - } - - return int(bytes); -} - -#else - /// /// /// @@ -623,4 +377,3 @@ bool UARTPort::setTermios() m_isOpen = true; return true; } -#endif diff --git a/src/modem/port/UARTPort.h b/src/modem/port/UARTPort.h index 81159902..3d8ae448 100644 --- a/src/modem/port/UARTPort.h +++ b/src/modem/port/UARTPort.h @@ -38,11 +38,6 @@ #include -#if defined(_WIN32) || defined(_WIN64) -#define WIN32_LEAN_AND_MEAN -#include -#endif - namespace modem { namespace port @@ -78,19 +73,19 @@ namespace modem virtual ~UARTPort(); /// Opens a connection to the serial port. - virtual bool open(); + bool open(); /// Reads data from the serial port. - virtual int read(uint8_t* buffer, uint32_t length); + int read(uint8_t* buffer, uint32_t length); /// Writes data to the serial port. - virtual int write(const uint8_t* buffer, uint32_t length); + int write(const uint8_t* buffer, uint32_t length); /// Closes the connection to the serial port. - virtual void close(); + void close(); #if defined(__APPLE__) /// - virtual int setNonblock(bool nonblock); + int setNonblock(bool nonblock); #endif protected: @@ -102,22 +97,13 @@ namespace modem std::string m_device; SERIAL_SPEED m_speed; bool m_assertRTS; -#if defined(_WIN32) || defined(_WIN64) - HANDLE m_handle; -#else int m_fd; -#endif -#if defined(_WIN32) || defined(_WIN64) - /// - int readNonblock(uint8_t * buffer, uint32_t length); -#else /// bool canWrite(); /// bool setTermios(); -#endif }; // class HOST_SW_API UARTPort : public ISerialPort, public IModemPort } // namespace port } // namespace Modem diff --git a/src/modem/port/UDPPort.h b/src/modem/port/UDPPort.h index e0ad8d44..f934ceba 100644 --- a/src/modem/port/UDPPort.h +++ b/src/modem/port/UDPPort.h @@ -55,15 +55,15 @@ namespace modem virtual ~UDPPort(); /// Opens a connection to the serial port. - virtual bool open(); + bool open(); /// Reads data from the serial port. - virtual int read(uint8_t* buffer, uint32_t length); + int read(uint8_t* buffer, uint32_t length); /// Writes data to the serial port. - virtual int write(const uint8_t* buffer, uint32_t length); + int write(const uint8_t* buffer, uint32_t length); /// Closes the connection to the serial port. - virtual void close(); + void close(); protected: network::UDPSocket m_socket; diff --git a/src/network/BaseNetwork.cpp b/src/network/BaseNetwork.cpp index dbbf5f6b..f4318671 100644 --- a/src/network/BaseNetwork.cpp +++ b/src/network/BaseNetwork.cpp @@ -12,7 +12,7 @@ // /* * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX -* Copyright (C) 2020-2022 by Bryan Biedenkapp N2PLL +* Copyright (C) 2020-2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,6 +35,7 @@ #include "Utils.h" using namespace network; +using namespace network::frame; #include #include @@ -47,55 +48,51 @@ using namespace network; /// /// Initializes a new instance of the BaseNetwork class. /// -/// Local port used to listen for incoming data. -/// Unique ID of this modem on the network. +/// Unique ID of this modem on the network. /// Flag indicating full-duplex operation. -/// +/// Flag indicating whether network debug is enabled. /// Flag indicating whether DMR slot 1 is enabled for network traffic. /// Flag indicating whether DMR slot 2 is enabled for network traffic. /// Flag indicating that the system activity logs will be sent to the network. /// Flag indicating that the system diagnostic logs will be sent to the network. -BaseNetwork::BaseNetwork(uint16_t localPort, uint32_t id, bool duplex, bool debug, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer) : - m_id(id), +/// Local port used to listen for incoming data. +BaseNetwork::BaseNetwork(uint32_t peerId, bool duplex, bool debug, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, uint16_t localPort) : + m_peerId(peerId), + m_status(NET_STAT_INVALID), + m_addr(), + m_addrLen(0U), m_slot1(slot1), m_slot2(slot2), + m_duplex(duplex), m_allowActivityTransfer(allowActivityTransfer), m_allowDiagnosticTransfer(allowDiagnosticTransfer), - m_duplex(duplex), m_debug(debug), - m_addr(), - m_addrLen(0U), - m_socket(localPort), - m_status(NET_STAT_INVALID), - m_retryTimer(1000U, 10U), - m_timeoutTimer(1000U, 60U), - m_buffer(nullptr), - m_salt(nullptr), - m_streamId(nullptr), - m_p25StreamId(0U), - m_nxdnStreamId(0U), + m_socket(nullptr), + m_frameQueue(nullptr), m_rxDMRData(4000U, "DMR Net Buffer"), m_rxP25Data(4000U, "P25 Net Buffer"), m_rxNXDNData(4000U, "NXDN Net Buffer"), - m_rxGrantData(4000U, "Grant Net Buffer"), - m_audio(), - m_random() + m_random(), + m_dmrStreamId(nullptr), + m_p25StreamId(0U), + m_nxdnStreamId(0U), + m_pktSeq(0U), + m_audio() { - assert(id > 1000U); + assert(peerId > 1000U); - m_buffer = new uint8_t[DATA_PACKET_LENGTH]; - m_salt = new uint8_t[sizeof(uint32_t)]; - m_streamId = new uint32_t[2U]; + m_socket = new UDPSocket(localPort); + m_frameQueue = new FrameQueue(m_socket, peerId, debug); std::random_device rd; std::mt19937 mt(rd()); m_random = mt; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - m_p25StreamId = dist(m_random); - m_nxdnStreamId = dist(m_random); - m_streamId[0U] = dist(m_random); - m_streamId[1U] = dist(m_random); + m_dmrStreamId = new uint32_t[2U]; + m_dmrStreamId[0U] = createStreamId(); + m_dmrStreamId[1U] = createStreamId(); + m_p25StreamId = createStreamId(); + m_nxdnStreamId = createStreamId(); } /// @@ -103,239 +100,191 @@ BaseNetwork::BaseNetwork(uint16_t localPort, uint32_t id, bool duplex, bool debu /// BaseNetwork::~BaseNetwork() { - delete[] m_buffer; - delete[] m_salt; - delete[] m_streamId; + if (m_frameQueue != nullptr) { + delete m_frameQueue; + } + + if (m_socket != nullptr) { + delete m_socket; + } + + delete[] m_dmrStreamId; } /// -/// Reads DMR frame data from the DMR ring buffer. +/// Writes grant request to the network. /// -/// +/// +/// +/// +/// +/// /// -bool BaseNetwork::readDMR(dmr::data::Data& data) +bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, const uint8_t slot, const bool unitToUnit) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - if (m_rxDMRData.isEmpty()) - return false; - - uint8_t length = 0U; - m_rxDMRData.getData(&length, 1U); - m_rxDMRData.getData(m_buffer, length); + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); - uint8_t seqNo = m_buffer[4U]; + ::memcpy(buffer + 0U, TAG_REPEATER_GRANT, 7U); - uint32_t srcId = (m_buffer[5U] << 16) | (m_buffer[6U] << 8) | (m_buffer[7U] << 0); + __SET_UINT32(srcId, buffer, 11U); // Source Address + __SET_UINT32(dstId, buffer, 15U); // Destination Address + buffer[19U] = slot; // Slot Number - uint32_t dstId = (m_buffer[8U] << 16) | (m_buffer[9U] << 8) | (m_buffer[10U] << 0); + if (unitToUnit) + buffer[19U] |= 0x80U; - uint8_t flco = (m_buffer[15U] & 0x40U) == 0x40U ? dmr::FLCO_PRIVATE : dmr::FLCO_GROUP; + buffer[20U] = mode; // DVM Mode State - uint32_t slotNo = (m_buffer[15U] & 0x80U) == 0x80U ? 2U : 1U; + m_frameQueue->enqueueMessage(buffer, 21U, 0U, m_peerId, + { NET_FUNC_GRANT, NET_SUBFUNC_NOP }, 0U, m_addr, m_addrLen); + return m_frameQueue->flushQueue(); +} - // DMO mode slot disabling - if (slotNo == 1U && !m_duplex) +/// +/// Writes the local activity log to the network. +/// +/// +/// +bool BaseNetwork::writeActLog(const char* message) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - // Individual slot disabling - if (slotNo == 1U && !m_slot1) - return false; - if (slotNo == 2U && !m_slot2) + if (!m_allowActivityTransfer) return false; - data.setSeqNo(seqNo); - data.setSlotNo(slotNo); - data.setSrcId(srcId); - data.setDstId(dstId); - data.setFLCO(flco); + assert(message != nullptr); - bool dataSync = (m_buffer[15U] & 0x20U) == 0x20U; - bool voiceSync = (m_buffer[15U] & 0x10U) == 0x10U; + char buffer[DATA_PACKET_LENGTH]; + uint32_t len = ::strlen(message); - if (m_debug) { - LogDebug(LOG_NET, "DMR, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u", seqNo, srcId, dstId, flco, slotNo, length); - } + ::memcpy(buffer + 0U, TAG_TRANSFER_ACT_LOG, 7U); - if (dataSync) { - uint8_t dataType = m_buffer[15U] & 0x0FU; - data.setData(m_buffer + 20U); - data.setDataType(dataType); - data.setN(0U); - } - else if (voiceSync) { - data.setData(m_buffer + 20U); - data.setDataType(dmr::DT_VOICE_SYNC); - data.setN(0U); - } - else { - uint8_t n = m_buffer[15U] & 0x0FU; - data.setData(m_buffer + 20U); - data.setDataType(dmr::DT_VOICE); - data.setN(n); - } + ::strcpy(buffer + 11U, message); - return true; + m_frameQueue->enqueueMessage((uint8_t*)buffer, (uint32_t)len + 12U, 0U, m_peerId, + { NET_FUNC_TRANSFER, NET_TRANSFER_SUBFUNC_ACTIVITY }, 0U, m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// -/// Reads P25 frame data from the P25 ring buffer. +/// Writes the local diagnostics log to the network. /// -/// -/// -/// -/// -/// -/// +/// /// -uint8_t* BaseNetwork::readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpeedData& lsd, uint8_t& duid, uint8_t& frameType, uint32_t& len) +bool BaseNetwork::writeDiagLog(const char* message) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) { - ret = false; - return nullptr; - } - - if (m_rxP25Data.isEmpty()) { - ret = false; - return nullptr; - } + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; - uint8_t length = 0U; - m_rxP25Data.getData(&length, 1U); - m_rxP25Data.getData(m_buffer, length); + if (!m_allowDiagnosticTransfer) + return false; - uint8_t lco = m_buffer[4U]; + assert(message != nullptr); - uint32_t srcId = (m_buffer[5U] << 16) | (m_buffer[6U] << 8) | (m_buffer[7U] << 0); + char buffer[DATA_PACKET_LENGTH]; + uint32_t len = ::strlen(message); - uint32_t dstId = (m_buffer[8U] << 16) | (m_buffer[9U] << 8) | (m_buffer[10U] << 0); + ::memcpy(buffer + 0U, TAG_TRANSFER_DIAG_LOG, 8U); - uint8_t MFId = m_buffer[15U]; + ::strcpy(buffer + 12U, message); - uint8_t lsd1 = m_buffer[20U]; - uint8_t lsd2 = m_buffer[21U]; + m_frameQueue->enqueueMessage((uint8_t*)buffer, (uint32_t)len + 13U, 0U, m_peerId, + { NET_FUNC_TRANSFER, NET_TRANSFER_SUBFUNC_DIAG }, 0U, m_addr, m_addrLen); + return m_frameQueue->flushQueue(); +} - duid = m_buffer[22U]; - frameType = p25::P25_FT_DATA_UNIT; +/// +/// Resets the DMR ring buffer for the given slot. +/// +/// DMR slot ring buffer to reset. +void BaseNetwork::resetDMR(uint32_t slotNo) +{ + assert(slotNo == 1U || slotNo == 2U); - if (m_debug) { - LogDebug(LOG_NET, "P25, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", duid, lco, MFId, srcId, dstId, length); + if (slotNo == 1U) { + m_dmrStreamId[0U] = createStreamId(); + } + else { + m_dmrStreamId[1U] = createStreamId(); } - // is this a LDU1, is this the first of a call? - if (duid == p25::P25_DUID_LDU1) { - frameType = m_buffer[180U]; - - if (m_debug) { - LogDebug(LOG_NET, "P25, frameType = $%02X", frameType); - } - - if (frameType == p25::P25_FT_HDU_VALID) { - uint8_t algId = m_buffer[181U]; - uint32_t kid = (m_buffer[182U] << 8) | (m_buffer[183U] << 0); - - // copy MI data - uint8_t mi[p25::P25_MI_LENGTH_BYTES]; - ::memset(mi, 0x00U, p25::P25_MI_LENGTH_BYTES); - - for (uint8_t i = 0; i < p25::P25_MI_LENGTH_BYTES; i++) { - mi[i] = m_buffer[184U + i]; - } - - if (m_debug) { - LogDebug(LOG_NET, "P25, HDU algId = $%02X, kId = $%02X", algId, kid); - Utils::dump(1U, "P25 HDU MI decoded from network", mi, p25::P25_MI_LENGTH_BYTES); - } + m_pktSeq = 0U; + m_rxDMRData.clear(); +} - control.setAlgId(algId); - control.setKId(kid); - control.setMI(mi); - } - } +/// +/// Resets the P25 ring buffer. +/// +void BaseNetwork::resetP25() +{ + m_p25StreamId = createStreamId(); + m_pktSeq = 0U; + m_rxP25Data.clear(); +} - control.setLCO(lco); - control.setSrcId(srcId); - control.setDstId(dstId); - control.setMFId(MFId); +/// +/// Resets the NXDN ring buffer. +/// +void BaseNetwork::resetNXDN() +{ + m_nxdnStreamId = createStreamId(); + m_pktSeq = 0U; + m_rxNXDNData.clear(); +} - lsd.setLSD1(lsd1); - lsd.setLSD2(lsd2); +/// +/// Gets the current DMR stream ID. +/// +/// DMR slot to get stream ID for. +/// Stream ID for the given DMR slot. +uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const +{ + assert(slotNo == 1U || slotNo == 2U); - uint8_t* data = nullptr; - len = m_buffer[23U]; - if (duid == p25::P25_DUID_PDU) { - data = new uint8_t[length]; - ::memset(data, 0x00U, length); - ::memcpy(data, m_buffer, length); + if (slotNo == 1U) { + return m_dmrStreamId[0U]; } else { - if (len <= 24) { - data = new uint8_t[len]; - ::memset(data, 0x00U, len); - } - else { - data = new uint8_t[len]; - ::memset(data, 0x00U, len); - ::memcpy(data, m_buffer + 24U, len); - } + return m_dmrStreamId[1U]; } - - ret = true; - return data; } /// -/// Reads NXDN frame data from the NXDN ring buffer. +/// Reads DMR raw frame data from the DMR ring buffer. /// /// -/// -/// +/// /// -uint8_t* BaseNetwork::readNXDN(bool& ret, nxdn::lc::RTCH& lc, uint32_t& len) +UInt8Array BaseNetwork::readDMR(bool& ret, uint32_t& frameLength) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) { - ret = false; + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return nullptr; - } - if (m_rxNXDNData.isEmpty()) { + ret = true; + if (m_rxDMRData.isEmpty()) { ret = false; return nullptr; } uint8_t length = 0U; - m_rxNXDNData.getData(&length, 1U); - m_rxNXDNData.getData(m_buffer, length); - - uint8_t messageType = m_buffer[4U]; - - uint32_t srcId = (m_buffer[5U] << 16) | (m_buffer[6U] << 8) | (m_buffer[7U] << 0); - - uint32_t dstId = (m_buffer[8U] << 16) | (m_buffer[9U] << 8) | (m_buffer[10U] << 0); - - lc.setMessageType(messageType); - lc.setSrcId((uint16_t)srcId & 0xFFFFU); - lc.setDstId((uint16_t)dstId & 0xFFFFU); - - bool group = (m_buffer[15U] & 0x40U) == 0x40U ? false : true; - lc.setGroup(group); - - uint8_t* data = nullptr; - len = m_buffer[23U]; - - if (len <= 24) { - data = new uint8_t[len]; - ::memset(data, 0x00U, len); - } - else { - data = new uint8_t[len]; - ::memset(data, 0x00U, len); - ::memcpy(data, m_buffer + 24U, len); + m_rxDMRData.getData(&length, 1U); + if (length == 0U) { + ret = false; + return nullptr; } - ret = true; - return data; + UInt8Array buffer; + frameLength = length; + buffer = std::unique_ptr(new uint8_t[length]); + ::memset(buffer.get(), 0x00U, length); + m_rxDMRData.getData(buffer.get(), length); + + return buffer; } /// @@ -360,16 +309,71 @@ bool BaseNetwork::writeDMR(const dmr::data::Data& data) uint32_t slotIndex = slotNo - 1U; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); + bool resetSeq = false; if (dataType == dmr::DT_VOICE_LC_HEADER) { - m_streamId[slotIndex] = dist(m_random); + resetSeq = true; + m_dmrStreamId[slotIndex] = createStreamId(); } if (dataType == dmr::DT_CSBK || dataType == dmr::DT_DATA_HEADER) { - m_streamId[slotIndex] = dist(m_random); + resetSeq = true; + m_dmrStreamId[slotIndex] = createStreamId(); } - return writeDMR(m_id, m_streamId[slotIndex], data); + uint32_t messageLength = 0U; + UInt8Array message = createDMR_Message(messageLength, m_dmrStreamId[slotIndex], data); + if (message == nullptr) { + return false; + } + + m_frameQueue->enqueueMessage(message.get(), messageLength, m_dmrStreamId[slotIndex], m_peerId, + { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_DMR }, pktSeq(resetSeq), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); +} + +/// +/// Helper to test if the DMR ring buffer has data. +/// +/// True if ring buffer contains data, otherwise false. +bool BaseNetwork::hasDMRData() const +{ + if (m_rxDMRData.isEmpty()) + return false; + + return true; +} + +/// +/// Reads P25 raw frame data from the P25 ring buffer. +/// +/// +/// +/// +UInt8Array BaseNetwork::readP25(bool& ret, uint32_t& frameLength) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return nullptr; + + ret = true; + if (m_rxP25Data.isEmpty()) { + ret = false; + return nullptr; + } + + uint8_t length = 0U; + m_rxP25Data.getData(&length, 1U); + if (length == 0U) { + ret = false; + return nullptr; + } + + UInt8Array buffer; + frameLength = length; + buffer = std::unique_ptr(new uint8_t[length]); + ::memset(buffer.get(), 0x00U, length); + m_rxP25Data.getData(buffer.get(), length); + + return buffer; } /// @@ -385,13 +389,21 @@ bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowS if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - if (m_p25StreamId == 0U) - m_p25StreamId = dist(m_random); + bool resetSeq = false; + if (m_p25StreamId == 0U) { + resetSeq = true; + m_p25StreamId = createStreamId(); + } - m_streamId[0] = m_p25StreamId; + uint32_t messageLength = 0U; + UInt8Array message = createP25_LDU1Message(messageLength, control, lsd, data, frameType); + if (message == nullptr) { + return false; + } - return writeP25LDU1(m_id, m_p25StreamId, control, lsd, data, frameType); + m_frameQueue->enqueueMessage(message.get(), messageLength, m_p25StreamId, m_peerId, + { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, pktSeq(resetSeq), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// @@ -406,13 +418,21 @@ bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowS if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - if (m_p25StreamId == 0U) - m_p25StreamId = dist(m_random); + bool resetSeq = false; + if (m_p25StreamId == 0U) { + resetSeq = true; + m_p25StreamId = createStreamId(); + } - m_streamId[0] = m_p25StreamId; + uint32_t messageLength = 0U; + UInt8Array message = createP25_LDU2Message(messageLength, control, lsd, data); + if (message == nullptr) { + return false; + } - return writeP25LDU2(m_id, m_p25StreamId, control, lsd, data); + m_frameQueue->enqueueMessage(message.get(), messageLength, m_p25StreamId, m_peerId, + { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, pktSeq(resetSeq), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// @@ -426,13 +446,21 @@ bool BaseNetwork::writeP25TDU(const p25::lc::LC& control, const p25::data::LowSp if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - if (m_p25StreamId == 0U) - m_p25StreamId = dist(m_random); + bool resetSeq = false; + if (m_p25StreamId == 0U) { + resetSeq = true; + m_p25StreamId = createStreamId(); + } - m_streamId[0] = m_p25StreamId; + uint32_t messageLength = 0U; + UInt8Array message = createP25_TDUMessage(messageLength, control, lsd); + if (message == nullptr) { + return false; + } - return writeP25TDU(m_id, m_p25StreamId, control, lsd); + m_frameQueue->enqueueMessage(message.get(), messageLength, m_p25StreamId, m_peerId, + { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, pktSeq(resetSeq), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// @@ -446,13 +474,21 @@ bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data) if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - if (m_p25StreamId == 0U) - m_p25StreamId = dist(m_random); + bool resetSeq = false; + if (m_p25StreamId == 0U) { + resetSeq = true; + m_p25StreamId = createStreamId(); + } - m_streamId[0] = m_p25StreamId; + uint32_t messageLength = 0U; + UInt8Array message = createP25_TSDUMessage(messageLength, control, data); + if (message == nullptr) { + return false; + } - return writeP25TSDU(m_id, m_p25StreamId, control, data); + m_frameQueue->enqueueMessage(message.get(), messageLength, m_p25StreamId, m_peerId, + { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, pktSeq(resetSeq), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// @@ -470,176 +506,143 @@ bool BaseNetwork::writeP25PDU(const p25::data::DataHeader& header, const p25::da if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - if (m_p25StreamId == 0U) - m_p25StreamId = dist(m_random); + bool resetSeq = false; + if (m_p25StreamId == 0U) { + resetSeq = true; + m_p25StreamId = createStreamId(); + } - m_streamId[0] = m_p25StreamId; + uint32_t messageLength = 0U; + UInt8Array message = createP25_PDUMessage(messageLength, header, secHeader, currentBlock, data, len); + if (message == nullptr) { + return false; + } - return writeP25PDU(m_id, m_p25StreamId, header, secHeader, currentBlock, data, len); + m_frameQueue->enqueueMessage(message.get(), messageLength, m_p25StreamId, m_peerId, + { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, pktSeq(resetSeq), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// -/// Writes NXDN frame data to the network. +/// Helper to test if the P25 ring buffer has data. /// -/// -/// -/// -/// -bool BaseNetwork::writeNXDN(const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len) +/// True if ring buffer contains data, otherwise false. +bool BaseNetwork::hasP25Data() const { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + if (m_rxP25Data.isEmpty()) return false; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - if (m_nxdnStreamId == 0U) - m_nxdnStreamId = dist(m_random); - - m_streamId[0] = m_nxdnStreamId; - - return writeNXDN(m_id, m_nxdnStreamId, lc, data, len); + return true; } /// -/// Writes grant request to the network. +/// Reads NXDN raw frame data from the NXDN ring buffer. /// -/// -/// -/// -/// -/// +/// +/// /// -bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, const uint8_t slot, const bool unitToUnit) +UInt8Array BaseNetwork::readNXDN(bool& ret, uint32_t& frameLength) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) - return false; - - uint8_t buffer[DATA_PACKET_LENGTH]; - ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); - - ::memcpy(buffer + 0U, TAG_REPEATER_GRANT, 7U); - __SET_UINT32(m_id, buffer, 7U); + return nullptr; - __SET_UINT32(srcId, buffer, 11U); // Source Address - __SET_UINT32(dstId, buffer, 15U); // Destination Address - buffer[19U] = slot; // Slot Number + ret = true; + if (m_rxNXDNData.isEmpty()) { + ret = false; + return nullptr; + } - if (unitToUnit) - buffer[19U] |= 0x80U; + uint8_t length = 0U; + m_rxNXDNData.getData(&length, 1U); + if (length == 0U) { + ret = false; + return nullptr; + } - buffer[20U] = mode; // DVM Mode State + UInt8Array buffer; + frameLength = length; + buffer = std::unique_ptr(new uint8_t[length]); + ::memset(buffer.get(), 0x00U, length); + m_rxNXDNData.getData(buffer.get(), length); - return write((uint8_t*)buffer, 21U); + return buffer; } /// -/// Writes the local activity log to the network. +/// Writes NXDN frame data to the network. /// -/// +/// +/// +/// /// -bool BaseNetwork::writeActLog(const char* message) +bool BaseNetwork::writeNXDN(const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len) { - if (!m_allowActivityTransfer) - return false; - if (m_status != NET_STAT_RUNNING) + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; - assert(message != nullptr); - - char buffer[DATA_PACKET_LENGTH]; - uint32_t len = ::strlen(message); + bool resetSeq = false; + if (m_nxdnStreamId == 0U) { + resetSeq = true; + m_nxdnStreamId = createStreamId(); + } - ::memcpy(buffer + 0U, TAG_TRANSFER_ACT_LOG, 7U); - __SET_UINT32(m_id, buffer, 7U); - ::strcpy(buffer + 11U, message); + uint32_t messageLength = 0U; + UInt8Array message = createNXDN_Message(messageLength, lc, data, len); + if (message == nullptr) { + return false; + } - return write((uint8_t*)buffer, (uint32_t)len + 12U); + m_frameQueue->enqueueMessage(message.get(), messageLength, m_nxdnStreamId, m_peerId, + { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_NXDN }, pktSeq(resetSeq), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// -/// Writes the local diagnostics log to the network. +/// Helper to test if the NXDN ring buffer has data. /// -/// -/// -bool BaseNetwork::writeDiagLog(const char* message) +/// True if ring buffer contains data, otherwise false. +bool BaseNetwork::hasNXDNData() const { - if (!m_allowDiagnosticTransfer) + if (m_rxNXDNData.isEmpty()) return false; - if (m_status != NET_STAT_RUNNING) - return false; - - assert(message != nullptr); - - char buffer[DATA_PACKET_LENGTH]; - uint32_t len = ::strlen(message); - ::memcpy(buffer + 0U, TAG_TRANSFER_DIAG_LOG, 8U); - __SET_UINT32(m_id, buffer, 8U); - ::strcpy(buffer + 12U, message); - - return write((uint8_t*)buffer, (uint32_t)len + 13U); + return true; } -/// -/// Resets the DMR ring buffer for the given slot. -/// -/// DMR slot ring buffer to reset. -void BaseNetwork::resetDMR(uint32_t slotNo) -{ - assert(slotNo == 1U || slotNo == 2U); - - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - if (slotNo == 1U) { - m_streamId[0U] = dist(m_random); - } - else { - m_streamId[1U] = dist(m_random); - } - - m_rxDMRData.clear(); -} +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- /// -/// Resets the P25 ring buffer. +/// Helper to update the RTP packet sequence. /// -void BaseNetwork::resetP25() +/// +/// RTP packet sequence. +uint16_t BaseNetwork::pktSeq(bool reset) { - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - m_p25StreamId = dist(m_random); - m_streamId[0] = m_p25StreamId; - - m_rxP25Data.clear(); -} + if (reset) { + m_pktSeq = 0U; + } -/// -/// Resets the NXDN ring buffer. -/// -void BaseNetwork::resetNXDN() -{ - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - m_nxdnStreamId = dist(m_random); - m_streamId[0] = m_nxdnStreamId; + uint16_t curr = m_pktSeq; + ++m_pktSeq; + if (m_pktSeq > UINT16_MAX) { + m_pktSeq = 0U; + } - m_rxNXDNData.clear(); + return curr; } -// --------------------------------------------------------------------------- -// Protected Class Members -// --------------------------------------------------------------------------- - /// -/// Writes DMR frame data to the network. +/// Creates an DMR frame message. /// -/// +/// /// /// /// -bool BaseNetwork::writeDMR(const uint32_t id, const uint32_t streamId, const dmr::data::Data& data) +UInt8Array BaseNetwork::createDMR_Message(uint32_t& length, const uint32_t streamId, const dmr::data::Data& data) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) - return false; - - uint8_t buffer[DATA_PACKET_LENGTH]; + uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_DMR_DATA, 4U); @@ -650,23 +653,19 @@ bool BaseNetwork::writeDMR(const uint32_t id, const uint32_t streamId, const dmr uint32_t dstId = data.getDstId(); // Target Address __SET_UINT16(dstId, buffer, 8U); - __SET_UINT32(id, buffer, 11U); // Peer ID - uint32_t slotNo = data.getSlotNo(); // Individual slot disabling if (slotNo == 1U && !m_slot1) - return false; + return nullptr; if (slotNo == 2U && !m_slot2) - return false; + return nullptr; buffer[15U] = slotNo == 1U ? 0x00U : 0x80U; // Slot Number uint8_t flco = data.getFLCO(); buffer[15U] |= flco == dmr::FLCO_GROUP ? 0x00U : 0x40U; // Group - uint32_t count = 1U; - uint8_t dataType = data.getDataType(); if (dataType == dmr::DT_VOICE_SYNC) { buffer[15U] |= 0x10U; @@ -675,56 +674,40 @@ bool BaseNetwork::writeDMR(const uint32_t id, const uint32_t streamId, const dmr buffer[15U] |= data.getN(); } else { - if (dataType == dmr::DT_VOICE_LC_HEADER) { - count = 2U; - } - - if (dataType == dmr::DT_CSBK || dataType == dmr::DT_DATA_HEADER) { - count = 1U; - } - buffer[15U] |= (0x20U | dataType); } buffer[4U] = data.getSeqNo(); // Sequence Number - __SET_UINT32(streamId, buffer, 16U); // Stream ID - data.getData(buffer + 20U); buffer[53U] = data.getBER(); // Bit Error Rate buffer[54U] = data.getRSSI(); // RSSI if (m_debug) - Utils::dump(1U, "Network Transmitted, DMR", buffer, (DMR_PACKET_SIZE + PACKET_PAD)); + Utils::dump(1U, "Network Message, DMR", buffer, (DMR_PACKET_SIZE + PACKET_PAD)); - for (uint32_t i = 0U; i < count; i++) - write(buffer, (DMR_PACKET_SIZE + PACKET_PAD)); - - return true; + length = (DMR_PACKET_SIZE + PACKET_PAD); + return UInt8Array(buffer); } /// -/// Writes P25 LDU1 frame data to the network. +/// Creates an P25 LDU1 frame message. /// -/// -/// +/// /// /// /// /// /// -bool BaseNetwork::writeP25LDU1(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, +UInt8Array BaseNetwork::createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, uint8_t frameType) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) - return false; - assert(data != nullptr); p25::dfsi::LC dfsiLC = p25::dfsi::LC(control, lsd); - uint8_t buffer[DATA_PACKET_LENGTH]; + uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); @@ -737,12 +720,8 @@ bool BaseNetwork::writeP25LDU1(const uint32_t id, const uint32_t streamId, const uint32_t dstId = control.getDstId(); // Target Address __SET_UINT16(dstId, buffer, 8U); - __SET_UINT32(id, buffer, 11U); // Peer ID - buffer[15U] = control.getMFId(); // MFId - __SET_UINT32(streamId, buffer, 16U); // Stream ID - buffer[20U] = lsd.getLSD1(); // LSD 1 buffer[21U] = lsd.getLSD2(); // LSD 2 @@ -824,33 +803,28 @@ bool BaseNetwork::writeP25LDU1(const uint32_t id, const uint32_t streamId, const buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Transmitted, P25 LDU1", buffer, (count + 15U + PACKET_PAD)); - - write(buffer, (count + 15U + PACKET_PAD)); + Utils::dump(1U, "Network Message, P25 LDU1", buffer, (count + 15U + PACKET_PAD)); - return true; + length = (count + 15U + PACKET_PAD); + return UInt8Array(buffer); } /// -/// Writes P25 LDU2 frame data to the network. +/// Creates an P25 LDU2 frame message. /// -/// -/// +/// /// /// /// /// -bool BaseNetwork::writeP25LDU2(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, +UInt8Array BaseNetwork::createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) - return false; - assert(data != nullptr); p25::dfsi::LC dfsiLC = p25::dfsi::LC(control, lsd); - uint8_t buffer[DATA_PACKET_LENGTH]; + uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); @@ -863,12 +837,8 @@ bool BaseNetwork::writeP25LDU2(const uint32_t id, const uint32_t streamId, const uint32_t dstId = control.getDstId(); // Target Address __SET_UINT16(dstId, buffer, 8U); - __SET_UINT32(id, buffer, 11U); // Peer ID - buffer[15U] = control.getMFId(); // MFId - __SET_UINT32(streamId, buffer, 16U); // Stream ID - buffer[20U] = lsd.getLSD1(); // LSD 1 buffer[21U] = lsd.getLSD2(); // LSD 2 @@ -925,27 +895,22 @@ bool BaseNetwork::writeP25LDU2(const uint32_t id, const uint32_t streamId, const buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Transmitted, P25 LDU2", buffer, (count + PACKET_PAD)); - - write(buffer, (count + PACKET_PAD)); + Utils::dump(1U, "Network Message, P25 LDU2", buffer, (count + PACKET_PAD)); - return true; + length = (count + PACKET_PAD); + return UInt8Array(buffer); } /// -/// Writes P25 TDU frame data to the network. +/// Creates an P25 TDU frame message. /// -/// -/// +/// /// /// /// -bool BaseNetwork::writeP25TDU(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd) +UInt8Array BaseNetwork::createP25_TDUMessage(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) - return false; - - uint8_t buffer[DATA_PACKET_LENGTH]; + uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); @@ -958,12 +923,8 @@ bool BaseNetwork::writeP25TDU(const uint32_t id, const uint32_t streamId, const uint32_t dstId = control.getDstId(); // Target Address __SET_UINT16(dstId, buffer, 8U); - __SET_UINT32(id, buffer, 11U); // Peer ID - buffer[15U] = control.getMFId(); // MFId - __SET_UINT32(streamId, buffer, 16U); // Stream ID - buffer[20U] = lsd.getLSD1(); // LSD 1 buffer[21U] = lsd.getLSD2(); // LSD 2 @@ -973,29 +934,24 @@ bool BaseNetwork::writeP25TDU(const uint32_t id, const uint32_t streamId, const buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Transmitted, P25 TDU", buffer, (count + PACKET_PAD)); - - write(buffer, (count + PACKET_PAD)); + Utils::dump(1U, "Network Message, P25 TDU", buffer, (count + PACKET_PAD)); - return true; + length = (count + PACKET_PAD); + return UInt8Array(buffer); } /// -/// Writes P25 TSDU frame data to the network. +/// Creates an P25 TSDU frame message. /// -/// -/// +/// /// /// /// -bool BaseNetwork::writeP25TSDU(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const uint8_t* data) +UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) - return false; - assert(data != nullptr); - uint8_t buffer[DATA_PACKET_LENGTH]; + uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); @@ -1008,12 +964,8 @@ bool BaseNetwork::writeP25TSDU(const uint32_t id, const uint32_t streamId, const uint32_t dstId = control.getDstId(); // Target Address __SET_UINT16(dstId, buffer, 8U); - __SET_UINT32(id, buffer, 11U); // Peer ID - buffer[15U] = control.getMFId(); // MFId - __SET_UINT32(streamId, buffer, 16U); // Stream ID - buffer[20U] = 0U; // Reserved (LSD 1) buffer[21U] = 0U; // Reserved (LSD 2) @@ -1027,30 +979,25 @@ bool BaseNetwork::writeP25TSDU(const uint32_t id, const uint32_t streamId, const buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Transmitted, P25 TDSU", buffer, (count + PACKET_PAD)); + Utils::dump(1U, "Network Message, P25 TDSU", buffer, (count + PACKET_PAD)); - write(buffer, (count + PACKET_PAD)); - - return true; + length = (count + PACKET_PAD); + return UInt8Array(buffer); } /// /// Writes P25 PDU frame data to the network. /// -/// -/// +/// /// /// /// /// /// /// -bool BaseNetwork::writeP25PDU(const uint32_t id, const uint32_t streamId, const p25::data::DataHeader& header, const p25::data::DataHeader& secHeader, +UInt8Array BaseNetwork::createP25_PDUMessage(uint32_t& length, const p25::data::DataHeader& header, const p25::data::DataHeader& secHeader, const uint8_t currentBlock, const uint8_t* data, const uint32_t len) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) - return false; - bool useSecondHeader = false; // process second header if we're using enhanced addressing @@ -1061,7 +1008,7 @@ bool BaseNetwork::writeP25PDU(const uint32_t id, const uint32_t streamId, const assert(data != nullptr); - uint8_t buffer[DATA_PACKET_LENGTH]; + uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); @@ -1076,12 +1023,8 @@ bool BaseNetwork::writeP25PDU(const uint32_t id, const uint32_t streamId, const __SET_UINT16(len, buffer, 8U); // PDU Length [bytes] - __SET_UINT32(id, buffer, 11U); // Peer ID - buffer[15U] = header.getMFId(); // MFId - __SET_UINT32(streamId, buffer, 16U); // Stream ID - buffer[20U] = header.getBlocksToFollow(); // Blocks To Follow buffer[21U] = currentBlock; // Current Block @@ -1095,30 +1038,25 @@ bool BaseNetwork::writeP25PDU(const uint32_t id, const uint32_t streamId, const buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Transmitted, P25 PDU", buffer, (count + PACKET_PAD)); - - write(buffer, (count + PACKET_PAD)); + Utils::dump(1U, "Network Message, P25 PDU", buffer, (count + PACKET_PAD)); - return true; + length = (count + PACKET_PAD); + return UInt8Array(buffer); } /// /// Writes NXDN frame data to the network. /// -/// -/// +/// /// /// /// /// -bool BaseNetwork::writeNXDN(const uint32_t id, const uint32_t streamId, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len) +UInt8Array BaseNetwork::createNXDN_Message(uint32_t& length, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len) { - if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) - return false; - assert(data != nullptr); - uint8_t buffer[DATA_PACKET_LENGTH]; + uint8_t* buffer = new uint8_t[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_NXDN_DATA, 4U); @@ -1133,8 +1071,6 @@ bool BaseNetwork::writeNXDN(const uint32_t id, const uint32_t streamId, const nx buffer[15U] |= lc.getGroup() ? 0x00U : 0x40U; // Group - __SET_UINT32(streamId, buffer, 16U); // Stream ID - uint32_t count = 24U; ::memcpy(buffer + 24U, data, len); @@ -1143,32 +1079,8 @@ bool BaseNetwork::writeNXDN(const uint32_t id, const uint32_t streamId, const nx buffer[23U] = count; if (m_debug) - Utils::dump(1U, "Network Transmitted, NXDN", buffer, (count + PACKET_PAD)); - - write(buffer, (count + PACKET_PAD)); + Utils::dump(1U, "Network Message, NXDN", buffer, (count + PACKET_PAD)); - return true; -} - -/// -/// Writes data to the network. -/// -/// Buffer to write to the network. -/// Length of buffer to write. -/// True, if buffer is written to the network, otherwise false. -bool BaseNetwork::write(const uint8_t* data, uint32_t length) -{ - assert(data != nullptr); - assert(length > 0U); - - // if (m_debug) - // Utils::dump(1U, "Network Transmitted", data, length); - - bool ret = m_socket.write(data, length, m_addr, m_addrLen); - if (!ret) { - LogError(LOG_NET, "Socket has failed when writing data to the peer, retrying connection"); - return false; - } - - return true; + length = (count + PACKET_PAD); + return UInt8Array(buffer); } diff --git a/src/network/BaseNetwork.h b/src/network/BaseNetwork.h index 7fbcb9e5..65b296dc 100644 --- a/src/network/BaseNetwork.h +++ b/src/network/BaseNetwork.h @@ -12,7 +12,7 @@ // /* * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX -* Copyright (C) 2020-2022 by Bryan Biedenkapp N2PLL +* Copyright (C) 2020-2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -45,6 +45,7 @@ #include "p25/lc/TDULC.h" #include "p25/Audio.h" #include "nxdn/lc/RTCH.h" +#include "network/FrameQueue.h" #include "network/UDPSocket.h" #include "RingBuffer.h" #include "Timer.h" @@ -92,10 +93,41 @@ namespace network // Constants // --------------------------------------------------------------------------- - const uint32_t DATA_PACKET_LENGTH = 8192U; const uint32_t DMR_PACKET_SIZE = 55U; const uint32_t PACKET_PAD = 8U; + const uint8_t NET_SUBFUNC_NOP = 0xFFU; // No Operation Sub-Function + + const uint8_t NET_FUNC_PROTOCOL = 0x00U; // Network Protocol Function + const uint8_t NET_PROTOCOL_SUBFUNC_DMR = 0x00U; // DMR + const uint8_t NET_PROTOCOL_SUBFUNC_P25 = 0x01U; // P25 + const uint8_t NET_PROTOCOL_SUBFUNC_NXDN = 0x02U; // NXDN + + const uint8_t NET_FUNC_MASTER = 0x01U; // Network Master Function + const uint8_t NET_MASTER_SUBFUNC_WL_RID = 0x00U; // Whitelist RIDs + const uint8_t NET_MASTER_SUBFUNC_BL_RID = 0x01U; // Blacklist RIDs + const uint8_t NET_MASTER_SUBFUNC_ACTIVE_TGS = 0x02U; // Active TGIDs + const uint8_t NET_MASTER_SUBFUNC_DEACTIVE_TGS = 0x03U; // Deactive TGIDs + + const uint8_t NET_FUNC_RPTL = 0x60U; // Repeater Login + const uint8_t NET_FUNC_RPTK = 0x61U; // Repeater Authorisation + const uint8_t NET_FUNC_RPTC = 0x62U; // Repeater Configuration + + const uint8_t NET_FUNC_RPT_CLOSING = 0x70U; // Repeater Closing + const uint8_t NET_FUNC_MST_CLOSING = 0x71U; // Master Closing + + const uint8_t NET_FUNC_PING = 0x74U; // Ping + const uint8_t NET_FUNC_PONG = 0x75U; // Pong + + const uint8_t NET_FUNC_GRANT = 0x7AU; // Grant Request + + const uint8_t NET_FUNC_ACK = 0x7EU; // Packet Acknowledge + const uint8_t NET_FUNC_NAK = 0x7FU; // Packet Negative Acknowledge + + const uint8_t NET_FUNC_TRANSFER = 0x90U; // Network Transfer Function + const uint8_t NET_TRANSFER_SUBFUNC_ACTIVITY = 0x01U; // Activity Log Transfer + const uint8_t NET_TRANSFER_SUBFUNC_DIAG = 0x02U; // Diagnostic Log Transfer + // --------------------------------------------------------------------------- // Network Peer Connection Status // --------------------------------------------------------------------------- @@ -125,39 +157,16 @@ namespace network class HOST_SW_API BaseNetwork { public: /// Initializes a new instance of the BaseNetwork class. - BaseNetwork(uint16_t localPort, uint32_t id, bool duplex, bool debug, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer); + BaseNetwork(uint32_t peerId, bool duplex, bool debug, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, + uint16_t localPort = 0U); /// Finalizes a instance of the BaseNetwork class. virtual ~BaseNetwork(); - /// Gets the current status of the network. - NET_CONN_STATUS getStatus() { return m_status; } - - /// Reads DMR frame data from the DMR ring buffer. - virtual bool readDMR(dmr::data::Data& data); - /// Reads P25 frame data from the P25 ring buffer. - virtual uint8_t* readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpeedData& lsd, uint8_t& duid, uint8_t& frameType, uint32_t& len); - /// Reads NXDN frame data from the NXDN ring buffer. - virtual uint8_t* readNXDN(bool& ret, nxdn::lc::RTCH& lc, uint32_t& len); - - /// Writes DMR frame data to the network. - virtual bool writeDMR(const dmr::data::Data& data); - /// Writes P25 LDU1 frame data to the network. - virtual bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, uint8_t frameType); - /// Writes P25 LDU2 frame data to the network. - virtual bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); - /// Writes P25 TDU frame data to the network. - virtual bool writeP25TDU(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd); - /// Writes P25 TSDU frame data to the network. - virtual bool writeP25TSDU(const p25::lc::LC& control, const uint8_t* data); - /// Writes P25 PDU frame data to the network. - virtual bool writeP25PDU(const p25::data::DataHeader& header, const p25::data::DataHeader& secHeader, const uint8_t currentBlock, - const uint8_t* data, const uint32_t len); - - /// Writes NXDN frame data to the network. - virtual bool writeNXDN(const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); + /// Gets the frame queue for the network. + FrameQueue* getFrameQueue() const { return m_frameQueue; } /// Writes a grant request to the network. - virtual bool writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, const uint8_t slot, const bool unitToUnit); + bool writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, const uint8_t slot, const bool unitToUnit); /// Writes the local activity log to the network. virtual bool writeActLog(const char* message); @@ -181,61 +190,121 @@ namespace network /// Resets the NXDN ring buffer. virtual void resetNXDN(); - protected: - uint32_t m_id; + /// Gets the current DMR stream ID. + uint32_t getDMRStreamId(uint32_t slotNo) const; + /// Gets the current P25 stream ID. + uint32_t getP25StreamId() const { return m_p25StreamId; } + /// Gets the current NXDN stream ID. + uint32_t getNXDNStreamId() const { return m_nxdnStreamId; } - bool m_slot1; - bool m_slot2; + /** Digital Mobile Radio */ + /// Reads DMR raw frame data from the DMR ring buffer. + UInt8Array readDMR(bool& ret, uint32_t& frameLength); + /// Writes DMR frame data to the network. + bool writeDMR(const dmr::data::Data& data); - bool m_allowActivityTransfer; - bool m_allowDiagnosticTransfer; + /// Helper to test if the DMR ring buffer has data. + bool hasDMRData() const; - bool m_duplex; - bool m_debug; + /** Project 25 */ + /// Reads P25 raw frame data from the P25 ring buffer. + UInt8Array readP25(bool& ret, uint32_t& frameLength); + /// Writes P25 LDU1 frame data to the network. + bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, + uint8_t frameType); + /// Writes P25 LDU2 frame data to the network. + bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); + /// Writes P25 TDU frame data to the network. + bool writeP25TDU(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd); + /// Writes P25 TSDU frame data to the network. + bool writeP25TSDU(const p25::lc::LC& control, const uint8_t* data); + /// Writes P25 PDU frame data to the network. + bool writeP25PDU(const p25::data::DataHeader& header, const p25::data::DataHeader& secHeader, + const uint8_t currentBlock, const uint8_t* data, const uint32_t len); - sockaddr_storage m_addr; - uint32_t m_addrLen; - UDPSocket m_socket; - NET_CONN_STATUS m_status; + /// Helper to test if the P25 ring buffer has data. + bool hasP25Data() const; - Timer m_retryTimer; - Timer m_timeoutTimer; + /** Next Generation Digital Narrowband */ + /// Reads NXDN raw frame data from the NXDN ring buffer. + UInt8Array readNXDN(bool& ret, uint32_t& frameLength); + /// Writes NXDN frame data to the network. + bool writeNXDN(const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); - uint8_t* m_buffer; - uint8_t* m_salt; + /// Helper to test if the NXDN ring buffer has data. + bool hasNXDNData() const; - uint32_t* m_streamId; - uint32_t m_p25StreamId; - uint32_t m_nxdnStreamId; + public: + /// Gets the peer ID of the network. + __PROTECTED_READONLY_PROPERTY(uint32_t, peerId, PeerId); + /// Gets the current status of the network. + __PROTECTED_READONLY_PROPERTY(NET_CONN_STATUS, status, Status); + + /// Unix socket storage containing the connected address. + __PROTECTED_READONLY_PROPERTY_PLAIN(sockaddr_storage, addr, addr); + /// Length of the sockaddr_storage structure. + __PROTECTED_READONLY_PROPERTY_PLAIN(uint32_t, addrLen, addrLen); + + /// Flag indicating whether network DMR slot 1 traffic is permitted. + __PROTECTED_READONLY_PROPERTY(bool, slot1, DMRSlot1); + /// Flag indicating whether network DMR slot 2 traffic is permitted. + __PROTECTED_READONLY_PROPERTY(bool, slot2, DMRSlot2); + + /// Flag indicating whether network traffic is duplex. + __PROTECTED_READONLY_PROPERTY(bool, duplex, Duplex); + + protected: + bool m_allowActivityTransfer; + bool m_allowDiagnosticTransfer; + + bool m_debug; + + UDPSocket* m_socket; + FrameQueue* m_frameQueue; RingBuffer m_rxDMRData; RingBuffer m_rxP25Data; RingBuffer m_rxNXDNData; - RingBuffer m_rxGrantData; - - p25::Audio m_audio; - std::mt19937 m_random; - /// Writes DMR frame data to the network. - bool writeDMR(const uint32_t id, const uint32_t streamId, const dmr::data::Data& data); - /// Writes P25 LDU1 frame data to the network. - bool writeP25LDU1(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, uint8_t frameType); - /// Writes P25 LDU2 frame data to the network. - bool writeP25LDU2(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); - /// Writes P25 TDU frame data to the network. - bool writeP25TDU(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd); - /// Writes P25 TSDU frame data to the network. - bool writeP25TSDU(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const uint8_t* data); - /// Writes P25 PDU frame data to the network. - bool writeP25PDU(const uint32_t id, const uint32_t streamId, const p25::data::DataHeader& header, const p25::data::DataHeader& secHeader, const uint8_t currentBlock, - const uint8_t* data, const uint32_t len); - /// Writes NXDN frame data to the network. - bool writeNXDN(const uint32_t id, const uint32_t streamId, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); + /// Helper to update the RTP packet sequence. + uint16_t pktSeq(bool reset = false); + + /// Generates a new stream ID. + uint32_t createStreamId() { std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); return dist(m_random); } + + /// Creates an DMR frame message. + UInt8Array createDMR_Message(uint32_t& length, const uint32_t streamId, const dmr::data::Data& data); + + /// Creates an P25 LDU1 frame message. + UInt8Array createP25_LDU1Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, + const uint8_t* data, uint8_t frameType); + /// Creates an P25 LDU2 frame message. + UInt8Array createP25_LDU2Message(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, + const uint8_t* data); + + /// Creates an P25 TDU frame message. + UInt8Array createP25_TDUMessage(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd); + + /// Creates an P25 TSDU frame message. + UInt8Array createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data); + + /// Creates an P25 PDU frame message. + UInt8Array createP25_PDUMessage(uint32_t& length, const p25::data::DataHeader& header, const p25::data::DataHeader& secHeader, + const uint8_t currentBlock, const uint8_t* data, const uint32_t len); + + /// Creates an NXDN frame message. + UInt8Array createNXDN_Message(uint32_t& length, const nxdn::lc::RTCH& lc, const uint8_t* data, const uint32_t len); + + private: + uint32_t* m_dmrStreamId; + uint32_t m_p25StreamId; + uint32_t m_nxdnStreamId; + + uint16_t m_pktSeq; - /// Writes data to the network. - virtual bool write(const uint8_t* data, uint32_t length); + p25::Audio m_audio; }; } // namespace network diff --git a/src/network/FNENetwork.cpp b/src/network/FNENetwork.cpp new file mode 100644 index 00000000..e8ef3db1 --- /dev/null +++ b/src/network/FNENetwork.cpp @@ -0,0 +1,1071 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/SHA256.h" +#include "host/fne/HostFNE.h" +#include "network/FNENetwork.h" +#include "network/fne/TagDMRData.h" +#include "network/fne/TagP25Data.h" +#include "network/fne/TagNXDNData.h" +#include "network/json/json.h" +#include "Log.h" +#include "StopWatch.h" +#include "Utils.h" + +using namespace network; +using namespace network::fne; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the FNENetwork class. +/// +/// +/// Network Hostname/IP address to listen on. +/// Network port number. +/// Unique ID on the network. +/// Network authentication password. +/// Flag indicating whether network debug is enabled. +/// Flag indicating whether network verbose logging is enabled. +/// Flag indicating whether DMR is enabled. +/// Flag indicating whether P25 is enabled. +/// Flag indicating whether NXDN is enabled. +/// Delay for end of call to parrot TG playback. +/// Flag indicating that the system activity logs will be sent to the network. +/// Flag indicating that the system diagnostic logs will be sent to the network. +/// Flag indicating if traffic should be repeated from this master. +/// +/// +FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, + bool debug, bool verbose, bool dmr, bool p25, bool nxdn, uint32_t parrotDelay, bool allowActivityTransfer, bool allowDiagnosticTransfer, + uint32_t pingTime, uint32_t updateLookupTime) : + BaseNetwork(peerId, true, debug, true, true, allowActivityTransfer, allowDiagnosticTransfer), + m_tagDMR(nullptr), + m_tagP25(nullptr), + m_tagNXDN(nullptr), + m_host(host), + m_address(address), + m_port(port), + m_password(password), + m_dmrEnabled(dmr), + m_p25Enabled(p25), + m_nxdnEnabled(nxdn), + m_parrotDelay(parrotDelay), + m_ridLookup(nullptr), + m_tidLookup(nullptr), + m_status(NET_STAT_INVALID), + m_peers(), + m_maintainenceTimer(1000U, pingTime), + m_updateLookupTimer(1000U, (updateLookupTime * 60U)), + m_verbose(verbose) +{ + assert(host != nullptr); + assert(!address.empty()); + assert(port > 0U); + assert(!password.empty()); + + m_tagDMR = new TagDMRData(this, debug); + m_tagP25 = new TagP25Data(this, debug); + m_tagNXDN = new TagNXDNData(this, debug); +} + +/// +/// Finalizes a instance of the FNENetwork class. +/// +FNENetwork::~FNENetwork() +{ + delete m_tagDMR; + delete m_tagP25; + delete m_tagNXDN; +} + +/// +/// Sets the instances of the Radio ID and Talkgroup Rules lookup tables. +/// +/// Radio ID Lookup Table Instance +/// Talkgroup Rules Lookup Table Instance +void FNENetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup) +{ + m_ridLookup = ridLookup; + m_tidLookup = tidLookup; +} + +/// +/// Updates the timer by the passed number of milliseconds. +/// +/// +void FNENetwork::clock(uint32_t ms) +{ + if (m_status != NET_STAT_MST_RUNNING) { + return; + } + + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + m_maintainenceTimer.clock(ms); + if (m_maintainenceTimer.isRunning() && m_maintainenceTimer.hasExpired()) { + // check to see if any peers have been quiet (no ping) longer than allowed + std::vector peersToRemove = std::vector(); + for (auto peer : m_peers) { + uint32_t id = peer.first; + FNEPeerConnection connection = peer.second; + + if (connection.connected()) { + uint64_t dt = connection.lastPing() + (m_host->m_pingTime * m_host->m_maxMissedPings); + if (dt < now) { + LogInfoEx(LOG_NET, "PEER %u timed out, dt = %u, now = %u", id, dt, now); + peersToRemove.push_back(id); + } + } + } + + // remove any peers + for (uint32_t peerId : peersToRemove) { + m_peers.erase(peerId); + } + + m_maintainenceTimer.start(); + } + + m_updateLookupTimer.clock(ms); + if (m_updateLookupTimer.isRunning() && m_updateLookupTimer.hasExpired()) { + writeWhitelistRIDs(); + writeBlacklistRIDs(); + m_frameQueue->flushQueue(); + + writeTGIDs(); + writeDeactiveTGIDs(); + m_frameQueue->flushQueue(); + + m_updateLookupTimer.start(); + } + +#if defined(ENABLE_DMR) + // if the DMR handler has parrot frames to playback, playback a frame + if (m_tagDMR->hasParrotFrames()) { + m_tagDMR->playbackParrot(); + } +#endif // defined(ENABLE_DMR) + +#if defined(ENABLE_P25) + // if the P25 handler has parrot frames to playback, playback a frame + if (m_tagP25->hasParrotFrames()) { + m_tagP25->playbackParrot(); + } +#endif // defined(ENABLE_P25) + +#if defined(ENABLE_NXDN) + // if the NXDN handler has parrot frames to playback, playback a frame + if (m_tagNXDN->hasParrotFrames()) { + m_tagNXDN->playbackParrot(); + } +#endif // defined(ENABLE_NXDN) + + sockaddr_storage address; + uint32_t addrLen; + frame::RTPHeader rtpHeader; + frame::RTPFNEHeader fneHeader; + int length = 0U; + + // read message + UInt8Array buffer = m_frameQueue->read(length, address, addrLen, &rtpHeader, &fneHeader); + if (length > 0) { + if (m_debug) + Utils::dump(1U, "Network Message", buffer.get(), length); + + uint32_t peerId = fneHeader.getPeerId(); + uint32_t streamId = fneHeader.getStreamId(); + + // update current peer packet sequence and stream ID + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end()) && streamId != 0U) { + FNEPeerConnection connection = m_peers[peerId]; + + uint16_t pktSeq = rtpHeader.getSequence(); + + if ((connection.currStreamId() == streamId) && (pktSeq != connection.pktNextSeq())) { + LogWarning(LOG_NET, "PEER %u Stream %u out-of-sequence; %u != %u", peerId, streamId, pktSeq, connection.pktNextSeq()); + } + + connection.currStreamId(streamId); + connection.pktLastSeq(pktSeq); + connection.pktNextSeq(pktSeq + 1); + if (connection.pktNextSeq() > UINT16_MAX) { + connection.pktNextSeq(0U); + } + + m_peers[peerId] = connection; + } + + // if we don't have a stream ID and are receiving call data -- throw an error and discard + if (streamId == 0 && fneHeader.getFunction() == NET_FUNC_PROTOCOL) + { + LogError(LOG_NET, "PEER %u Malformed packet (no stream ID for a call?)", peerId); + return; + } + + // process incoming message frame opcodes + switch (fneHeader.getFunction()) { + case NET_FUNC_PROTOCOL: + { + if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_DMR) { // Encapsulated DMR data frame + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + std::string ip = UDPSocket::address(address); + + // validate peer (simple validation really) + if (connection.connected() && connection.address() == ip) { +#if defined(ENABLE_DMR) + if (m_dmrEnabled) { + if (m_tagDMR != nullptr) { + m_tagDMR->processFrame(buffer.get(), length, peerId, rtpHeader.getSequence(), streamId); + } + } +#endif // defined(ENABLE_DMR) + } + } + } + else if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_P25) { // Encapsulated P25 data frame + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + std::string ip = UDPSocket::address(address); + + // validate peer (simple validation really) + if (connection.connected() && connection.address() == ip) { +#if defined(ENABLE_P25) + if (m_p25Enabled) { + if (m_tagP25 != nullptr) { + m_tagP25->processFrame(buffer.get(), length, peerId, rtpHeader.getSequence(), streamId); + } + } +#endif // defined(ENABLE_P25) + } + } + } + else if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_NXDN) { // Encapsulated NXDN data frame + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + std::string ip = UDPSocket::address(address); + + // validate peer (simple validation really) + if (connection.connected() && connection.address() == ip) { +#if defined(ENABLE_NXDN) + if (m_nxdnEnabled) { + if (m_tagNXDN != nullptr) { + m_tagNXDN->processFrame(buffer.get(), length, peerId, rtpHeader.getSequence(), streamId); + } + } +#endif // defined(ENABLE_NXDN) + } + } + } + else { + Utils::dump("Unknown protocol opcode from peer", buffer.get(), length); + } + } + break; + + case NET_FUNC_RPTL: // Repeater Login + { + if (peerId > 0 && (m_peers.find(peerId) == m_peers.end())) { + FNEPeerConnection connection = FNEPeerConnection(peerId, address, addrLen); + connection.lastPing(now); + connection.currStreamId(streamId); + + std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); + connection.salt(dist(m_random)); + + LogInfoEx(LOG_NET, "Repeater logging in with PEER %u, %s:%u", peerId, connection.address().c_str(), connection.port()); + + connection.connectionState(NET_STAT_WAITING_AUTHORISATION); + m_peers[peerId] = connection; + + // transmit salt to peer + uint8_t salt[4U]; + ::memset(salt, 0x00U, 4U); + __SET_UINT32(connection.salt(), salt, 0U); + + writePeerACK(peerId, salt, 4U); + LogInfoEx(LOG_NET, "Challenge response send to PEER %u for login", peerId); + } + else { + writePeerNAK(peerId, TAG_REPEATER_LOGIN, address, addrLen); + } + } + break; + case NET_FUNC_RPTK: // Repeater Authentication + { + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + connection.lastPing(now); + + if (connection.connectionState() == NET_STAT_WAITING_AUTHORISATION) { + // get the hash from the frame message + uint8_t hash[length - 8U]; + ::memset(hash, 0x00U, length - 8U); + ::memcpy(hash, buffer.get() + 8U, length - 8U); + + // generate our own hash + uint8_t salt[4U]; + ::memset(salt, 0x00U, 4U); + __SET_UINT32(connection.salt(), salt, 0U); + + size_t size = m_password.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, salt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = m_password.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool valid = false; + if (length - 8U == 32U) { + valid = true; + for (uint8_t i = 0; i < 32U; i++) { + if (hash[i] != out[i]) { + valid = false; + break; + } + } + } + + if (valid) { + connection.connectionState(NET_STAT_WAITING_CONFIG); + writePeerACK(peerId); + LogInfoEx(LOG_NET, "PEER %u has completed the login exchange", peerId); + } + else { + LogWarning(LOG_NET, "PEER %u has failed the login exchange", peerId); + writePeerNAK(peerId, TAG_REPEATER_AUTH); + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + m_peers.erase(peerId); + } + } + + m_peers[peerId] = connection; + } + else { + LogWarning(LOG_NET, "PEER %u tried login exchange while in an incorrect state?", peerId); + writePeerNAK(peerId, TAG_REPEATER_AUTH); + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + m_peers.erase(peerId); + } + } + } + else { + writePeerNAK(peerId, TAG_REPEATER_AUTH, address, addrLen); + } + } + break; + case NET_FUNC_RPTC: // Repeater Configuration + { + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + connection.lastPing(now); + + if (connection.connectionState() == NET_STAT_WAITING_CONFIG) { + uint8_t rawPayload[length - 8U]; + ::memset(rawPayload, 0x00U, length - 8U); + ::memcpy(rawPayload, buffer.get() + 8U, length - 8U); + std::string payload(rawPayload, rawPayload + sizeof(rawPayload)); + + // parse JSON body + json::value v; + std::string err = json::parse(v, payload); + if (!err.empty()) { + LogWarning(LOG_NET, "PEER %u has supplied invalid configuration data", peerId); + writePeerNAK(peerId, TAG_REPEATER_AUTH); + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + m_peers.erase(peerId); + } + } + else { + // ensure parsed JSON is an object + if (!v.is()) { + LogWarning(LOG_NET, "PEER %u has supplied invalid configuration data", peerId); + writePeerNAK(peerId, TAG_REPEATER_AUTH); + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + m_peers.erase(peerId); + } + } + else { + connection.config(v.get()); + connection.connectionState(NET_STAT_RUNNING); + connection.connected(true); + connection.pingsReceived(0U); + connection.lastPing(now); + m_peers[peerId] = connection; + + writePeerACK(peerId); + LogInfoEx(LOG_NET, "PEER %u has completed the configuration exchange", peerId); + + // queue final update messages and flush + writeWhitelistRIDs(peerId, true); + writeBlacklistRIDs(peerId, true); + m_frameQueue->flushQueue(); + + writeTGIDs(peerId, true); + writeDeactiveTGIDs(peerId, true); + m_frameQueue->flushQueue(); + } + } + } + else { + LogWarning(LOG_NET, "PEER %u tried login exchange while in an incorrect state?", peerId); + writePeerNAK(peerId, TAG_REPEATER_CONFIG); + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + m_peers.erase(peerId); + } + } + } + else { + writePeerNAK(peerId, TAG_REPEATER_CONFIG, address, addrLen); + } + } + break; + + case NET_FUNC_RPT_CLOSING: // Repeater Closing (Disconnect) + { + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + std::string ip = UDPSocket::address(address); + + // validate peer (simple validation really) + if (connection.connected() && connection.address() == ip) { + LogInfoEx(LOG_NET, "PEER %u is closing down", peerId); + + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + m_peers.erase(peerId); + } + } + } + } + break; + case NET_FUNC_PING: // Repeater Ping + { + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + std::string ip = UDPSocket::address(address); + + // validate peer (simple validation really) + if (connection.connected() && connection.address() == ip) { + uint32_t pingsRx = connection.pingsReceived(); + connection.pingsReceived(pingsRx++); + connection.lastPing(now); + connection.pktLastSeq(connection.pktLastSeq() + 1); + + m_peers[peerId] = connection; + writePeerTagged(peerId, { NET_FUNC_PONG, NET_SUBFUNC_NOP }, TAG_MASTER_PONG); + + if (m_debug) { + LogDebug(LOG_NET, "PEER %u ping received and answered", peerId); + } + } + else { + writePeerNAK(peerId, TAG_REPEATER_PING); + } + } + } + break; + + case NET_FUNC_GRANT: // Repeater Grant Request + { + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + std::string ip = UDPSocket::address(address); + + // validate peer (simple validation really) + if (connection.connected() && connection.address() == ip) { + // TODO TODO TODO + // TODO: handle repeater grant request + } + else { + writePeerNAK(peerId, TAG_REPEATER_GRANT); + } + } + } + break; + + case NET_FUNC_TRANSFER: + { + if (fneHeader.getSubFunction() == NET_TRANSFER_SUBFUNC_ACTIVITY) { // Peer Activity Log Transfer + if (m_allowActivityTransfer) { + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + std::string ip = UDPSocket::address(address); + + // validate peer (simple validation really) + if (connection.connected() && connection.address() == ip) { + uint8_t rawPayload[length - 11U]; + ::memset(rawPayload, 0x00U, length - 11U); + ::memcpy(rawPayload, buffer.get() + 11U, length - 11U); + std::string payload(rawPayload, rawPayload + sizeof(rawPayload)); + + std::stringstream ss; + ss << peerId << " " << payload; + + ::ActivityLog("", false, ss.str().c_str()); + } + else { + writePeerNAK(peerId, TAG_TRANSFER_ACT_LOG); + } + } + } + } + else if (fneHeader.getSubFunction() == NET_TRANSFER_SUBFUNC_DIAG) { // Peer Diagnostic Log Transfer + if (m_allowDiagnosticTransfer) { + if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { + FNEPeerConnection connection = m_peers[peerId]; + std::string ip = UDPSocket::address(address); + + // validate peer (simple validation really) + if (connection.connected() && connection.address() == ip) { + uint8_t rawPayload[length - 12U]; + ::memset(rawPayload, 0x00U, length - 12U); + ::memcpy(rawPayload, buffer.get() + 12U, length - 12U); + std::string payload(rawPayload, rawPayload + sizeof(rawPayload)); + + bool currState = g_disableTimeDisplay; + g_disableTimeDisplay = true; + ::Log(9999U, nullptr, "%u %s", peerId, payload.c_str()); + g_disableTimeDisplay = currState; + } + else { + writePeerNAK(peerId, TAG_TRANSFER_DIAG_LOG); + } + } + } + } + else { + Utils::dump("Unknown transfer opcode from the peer", buffer.get(), length); + } + } + break; + + default: + Utils::dump("Unknown opcode from the peer", buffer.get(), length); + break; + } + } + + return; +} + +/// +/// Opens connection to the network. +/// +/// +bool FNENetwork::open() +{ + if (m_debug) + LogMessage(LOG_NET, "Opening Network"); + + m_status = NET_STAT_MST_RUNNING; + m_maintainenceTimer.start(); + + m_socket = new UDPSocket(m_address, m_port); + + // reinitialize the frame queue + if (m_frameQueue != nullptr) { + delete m_frameQueue; + m_frameQueue = new FrameQueue(m_socket, m_peerId, m_debug); + } + + bool ret = m_socket->open(); + if (!ret) { + m_status = NET_STAT_INVALID; + } + + return ret; +} + +/// +/// Closes connection to the network. +/// +void FNENetwork::close() +{ + if (m_debug) + LogMessage(LOG_NET, "Closing Network"); + + if (m_status == NET_STAT_MST_RUNNING) { + uint8_t buffer[9U]; + ::memcpy(buffer + 0U, TAG_MASTER_CLOSING, 5U); + writePeers({ NET_FUNC_MST_CLOSING, NET_SUBFUNC_NOP }, buffer, 9U); + } + + m_socket->close(); + + m_maintainenceTimer.stop(); + m_updateLookupTimer.stop(); + + m_status = NET_STAT_INVALID; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Helper to send the list of whitelisted RIDs to the specified peer. +/// +/// +/// +void FNENetwork::writeWhitelistRIDs(uint32_t peerId, bool queueOnly) +{ + // send radio ID white/black lists + std::vector ridWhitelist; + + auto ridLookups = m_ridLookup->table(); + for (auto entry : ridLookups) { + uint32_t id = entry.first; + if (entry.second.radioEnabled()) { + ridWhitelist.push_back(id); + } + } + + if (ridWhitelist.size() == 0U) { + return; + } + + // build dataset + uint8_t payload[4U + (ridWhitelist.size() * 4U)]; + ::memset(payload, 0x00U, 4U + (ridWhitelist.size() * 4U)); + + __SET_UINT32(ridWhitelist.size(), payload, 0U); + + // write whitelisted IDs to whitelist payload + uint32_t offs = 4U; + for (uint32_t id : ridWhitelist) { + if (m_debug) + LogDebug(LOG_NET, "PEER %u Whitelisting RID %u", peerId, id); + + __SET_UINT32(id, payload, offs); + offs += 4U; + } + + writePeerTagged(peerId, { NET_FUNC_MASTER, NET_MASTER_SUBFUNC_WL_RID }, TAG_MASTER_WL_RID, + payload, 4U + (ridWhitelist.size() * 4U), queueOnly, true); +} + +/// +/// Helper to send the list of whitelisted RIDs to connected peers. +/// +void FNENetwork::writeWhitelistRIDs() +{ + if (m_ridLookup->table().size() == 0U) { + return; + } + + for (auto peer : m_peers) { + writeWhitelistRIDs(peer.first, true); + } +} + +/// +/// Helper to send the list of whitelisted RIDs to the specified peer. +/// +/// +/// +void FNENetwork::writeBlacklistRIDs(uint32_t peerId, bool queueOnly) +{ + // send radio ID blacklist + std::vector ridBlacklist; + + auto ridLookups = m_ridLookup->table(); + for (auto entry : ridLookups) { + uint32_t id = entry.first; + if (!entry.second.radioEnabled()) { + ridBlacklist.push_back(id); + } + } + + if (ridBlacklist.size() == 0U) { + return; + } + + // build dataset + uint8_t payload[4U + (ridBlacklist.size() * 4U)]; + ::memset(payload, 0x00U, 4U + (ridBlacklist.size() * 4U)); + + __SET_UINT32(ridBlacklist.size(), payload, 0U); + + // write blacklisted IDs to blacklist payload + uint32_t offs = 4U; + for (uint32_t id : ridBlacklist) { + if (m_debug) + LogDebug(LOG_NET, "PEER %u Blacklisting RID %u", peerId, id); + + __SET_UINT32(id, payload, offs); + offs += 4U; + } + + writePeerTagged(peerId, { NET_FUNC_MASTER, NET_MASTER_SUBFUNC_BL_RID }, TAG_MASTER_BL_RID, + payload, 4U + (ridBlacklist.size() * 4U), queueOnly, true); +} + +/// +/// Helper to send the list of whitelisted RIDs to connected peers. +/// +void FNENetwork::writeBlacklistRIDs() +{ + if (m_ridLookup->table().size() == 0U) { + return; + } + + for (auto peer : m_peers) { + writeBlacklistRIDs(peer.first, true); + } +} + +/// +/// Helper to send the list of active TGIDs to the specified peer. +/// +/// +/// +void FNENetwork::writeTGIDs(uint32_t peerId, bool queueOnly) +{ + if (!m_tidLookup->sendTalkgroups()) { + return; + } + + std::vector> tgidList; + auto groupVoice = m_tidLookup->groupVoice(); + for (auto entry : groupVoice) { + if (entry.config().active()) { + tgidList.push_back({ entry.source().tgId(), entry.source().tgSlot() }); + } + } + + // build dataset + uint8_t payload[4U + (tgidList.size() * 5U)]; + ::memset(payload, 0x00U, 4U + (tgidList.size() * 5U)); + + __SET_UINT32(tgidList.size(), payload, 0U); + + // write talkgroup IDs to active TGID payload + uint32_t offs = 4U; + for (std::pair tg : tgidList) { + if (m_debug) + LogDebug(LOG_NET, "PEER %u Activating TGID %u TS %u", peerId, tg.first, tg.second); + __SET_UINT32(tg.first, payload, offs); + payload[offs + 4U] = tg.second; + offs += 5U; + } + + writePeerTagged(peerId, { NET_FUNC_MASTER, NET_MASTER_SUBFUNC_ACTIVE_TGS }, TAG_MASTER_ACTIVE_TGS, + payload, 4U + (tgidList.size() * 5U), queueOnly, true); +} + +/// +/// Helper to send the list of active TGIDs to connected peers. +/// +void FNENetwork::writeTGIDs() +{ + for (auto peer : m_peers) { + writeTGIDs(peer.first, true); + } +} + +/// +/// Helper to send the list of deactivated TGIDs to the specified peer. +/// +/// +/// +void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, bool queueOnly) +{ + if (!m_tidLookup->sendTalkgroups()) { + return; + } + + std::vector> tgidList; + auto groupVoice = m_tidLookup->groupVoice(); + for (auto entry : groupVoice) { + if (!entry.config().active()) { + tgidList.push_back({ entry.source().tgId(), entry.source().tgSlot() }); + } + } + + // build dataset + uint8_t payload[4U + (tgidList.size() * 5U)]; + ::memset(payload, 0x00U, 4U + (tgidList.size() * 5U)); + + __SET_UINT32(tgidList.size(), payload, 0U); + + // write talkgroup IDs to deactive TGID payload + uint32_t offs = 4U; + for (std::pair tg : tgidList) { + if (m_debug) + LogDebug(LOG_NET, "PEER %u Deactivating TGID %u TS %u", peerId, tg.first, tg.second); + __SET_UINT32(tg.first, payload, offs); + payload[offs + 4U] = tg.second; + offs += 5U; + } + + writePeerTagged(peerId, { NET_FUNC_MASTER, NET_MASTER_SUBFUNC_DEACTIVE_TGS }, TAG_MASTER_DEACTIVE_TGS, + payload, 4U + (tgidList.size() * 5U), queueOnly, true); +} + +/// +/// Helper to send the list of deactivated TGIDs to connected peers. +/// +void FNENetwork::writeDeactiveTGIDs() +{ + for (auto peer : m_peers) { + writeDeactiveTGIDs(peer.first, true); + } +} + +/// +/// Helper to send a raw message to the specified peer. +/// +/// Peer ID. +/// Opcode. +/// Buffer to write to the network. +/// Length of buffer to write. +/// +/// +bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, bool queueOnly) +{ + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + uint32_t streamId = m_peers[peerId].currStreamId(); + sockaddr_storage addr = m_peers[peerId].socketStorage(); + uint32_t addrLen = m_peers[peerId].sockStorageLen(); + + m_frameQueue->enqueueMessage(data, length, streamId, peerId, opcode, pktSeq, addr, addrLen); + if (queueOnly) + return true; + return m_frameQueue->flushQueue(); + } + + return false; +} + +/// +/// Helper to send a raw message to the specified peer. +/// +/// Peer ID. +/// Opcode. +/// Buffer to write to the network. +/// Length of buffer to write. +/// +/// +bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, bool queueOnly, bool incPktSeq) +{ + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + if (incPktSeq) { + m_peers[peerId].pktLastSeq(m_peers[peerId].pktLastSeq() + 1); + } + uint16_t pktSeq = m_peers[peerId].pktLastSeq(); + + return writePeer(peerId, opcode, data, length, pktSeq, queueOnly); + } + + return false; +} + +/// +/// Helper to send a tagged message to the specified peer. +/// +/// Peer ID. +/// Opcode. +/// Message tag. +/// Buffer to write to the network. +/// Length of buffer to write. +/// +/// +bool FNENetwork::writePeerTagged(uint32_t peerId, FrameQueue::OpcodePair opcode, const char* tag, + const uint8_t* data, uint32_t length, bool queueOnly, bool incPktSeq) +{ + assert(peerId > 0); + assert(tag != nullptr); + + if (strlen(tag) + length > DATA_PACKET_LENGTH) { + return false; + } + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, tag, strlen(tag)); + __SET_UINT32(peerId, buffer, strlen(tag)); // Peer ID + + if (data != nullptr && length > 0U) { + ::memcpy(buffer + strlen(tag), data, length); + } + + uint32_t len = length + (strlen(tag) + 4U); + return writePeer(peerId, opcode, buffer, len, queueOnly, incPktSeq); +} + +/// +/// Helper to send a ACK response to the specified peer. +/// +/// +/// Buffer to write to the network. +/// Length of buffer to write. +bool FNENetwork::writePeerACK(uint32_t peerId, const uint8_t* data, uint32_t length) +{ + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_REPEATER_ACK, 6U); + __SET_UINT32(peerId, buffer, 6U); // Peer ID + + if (data != nullptr && length > 0U) { + ::memcpy(buffer + 6U, data, length); + } + + return writePeer(peerId, { NET_FUNC_ACK, NET_SUBFUNC_NOP }, buffer, 10U + length, false, true); +} + +/// +/// Helper to send a NAK response to the specified peer. +/// +/// +/// +bool FNENetwork::writePeerNAK(uint32_t peerId, const char* tag) +{ + assert(peerId > 0); + assert(tag != nullptr); + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_MASTER_NAK, 6U); + __SET_UINT32(peerId, buffer, 6U); // Peer ID + + LogWarning(LOG_NET, "%s from unauth PEER %u", tag, peerId); + return writePeer(peerId, { NET_FUNC_NAK, NET_SUBFUNC_NOP }, buffer, 10U, false, true); +} + +/// +/// Helper to send a NAK response to the specified peer. +/// +/// +/// +/// +/// +bool FNENetwork::writePeerNAK(uint32_t peerId, const char* tag, sockaddr_storage& addr, uint32_t addrLen) +{ + assert(peerId > 0); + assert(tag != nullptr); + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_MASTER_NAK, 6U); + __SET_UINT32(peerId, buffer, 6U); // Peer ID + + LogWarning(LOG_NET, "%s from unauth PEER %u", tag, peerId); + + m_frameQueue->enqueueMessage(buffer, 10U, createStreamId(), peerId, + { NET_FUNC_NAK, NET_SUBFUNC_NOP }, 0U, addr, addrLen); + return m_frameQueue->flushQueue(); +} + +/// +/// Helper to send a raw message to the connected peers. +/// +/// Buffer to write to the network. +/// Length of buffer to write. +void FNENetwork::writePeers(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length) +{ + for (auto peer : m_peers) { + uint32_t peerId = peer.first; + uint32_t streamId = peer.second.currStreamId(); + sockaddr_storage addr = peer.second.socketStorage(); + uint32_t addrLen = peer.second.sockStorageLen(); + uint16_t pktSeq = peer.second.pktLastSeq(); + + m_frameQueue->enqueueMessage(data, length, streamId, peerId, opcode, pktSeq, addr, addrLen); + } + + m_frameQueue->flushQueue(); +} + +/// +/// Helper to send a raw message to the connected peers. +/// +/// Buffer to write to the network. +/// Length of buffer to write. +/// +void FNENetwork::writePeers(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq) +{ + for (auto peer : m_peers) { + uint32_t peerId = peer.first; + uint32_t streamId = peer.second.currStreamId(); + sockaddr_storage addr = peer.second.socketStorage(); + uint32_t addrLen = peer.second.sockStorageLen(); + + m_frameQueue->enqueueMessage(data, length, streamId, peerId, opcode, pktSeq, addr, addrLen); + } + + m_frameQueue->flushQueue(); +} + +/// +/// Helper to send a tagged message to the connected peers. +/// +/// +/// Buffer to write to the network. +/// Length of buffer to write. +void FNENetwork::writePeersTagged(FrameQueue::OpcodePair opcode, const char* tag, const uint8_t* data, uint32_t length) +{ + assert(tag != nullptr); + + if (strlen(tag) + length > DATA_PACKET_LENGTH) { + return; + } + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, tag, strlen(tag)); + + ::memcpy(buffer + strlen(tag), data, length); + + uint32_t len = length + strlen(tag); + writePeers(opcode, buffer, len); +} diff --git a/src/network/FNENetwork.h b/src/network/FNENetwork.h new file mode 100644 index 00000000..0a3a1a76 --- /dev/null +++ b/src/network/FNENetwork.h @@ -0,0 +1,284 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__FNE_NETWORK_H__) +#define __FNE_NETWORK_H__ + +#include "Defines.h" +#include "network/BaseNetwork.h" +#include "network/Network.h" +#include "network/json/json.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Class Prototypes +// --------------------------------------------------------------------------- + +class HOST_SW_API HostFNE; +namespace network { namespace fne { class HOST_SW_API TagDMRData; } } +namespace network { namespace fne { class HOST_SW_API TagP25Data; } } +namespace network { namespace fne { class HOST_SW_API TagNXDNData; } } + +namespace network +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an peer connection to the FNE. + // --------------------------------------------------------------------------- + + class HOST_SW_API FNEPeerConnection { + public: + /// Initializes a new insatnce of the FNEPeerConnection class. + FNEPeerConnection() : + m_id(0U), + m_currStreamId(0U), + m_socketStorage(), + m_sockStorageLen(0U), + m_address(), + m_port(), + m_salt(0U), + m_connected(false), + m_connectionState(NET_STAT_INVALID), + m_pingsReceived(0U), + m_config(), + m_pktLastSeq(0U), + m_pktNextSeq(1U) + { + /* stub */ + } + /// Initializes a new insatnce of the FNEPeerConnection class. + /// Unique ID of this modem on the network. + /// + /// + FNEPeerConnection(uint32_t id, sockaddr_storage& socketStorage, uint32_t sockStorageLen) : + m_id(id), + m_currStreamId(0U), + m_socketStorage(socketStorage), + m_sockStorageLen(sockStorageLen), + m_address(UDPSocket::address(socketStorage)), + m_port(UDPSocket::port(socketStorage)), + m_salt(0U), + m_connected(false), + m_connectionState(NET_STAT_INVALID), + m_pingsReceived(0U), + m_config(), + m_pktLastSeq(0U), + m_pktNextSeq(1U) + { + assert(id > 0U); + assert(sockStorageLen > 0U); + assert(!m_address.empty()); + assert(m_port > 0U); + } + + /// Equals operator. Copies this FNEPeerConnection to another FNEPeerConnection. + virtual FNEPeerConnection& operator=(const FNEPeerConnection& data) + { + if (this != &data) { + m_id = data.m_id; + + m_currStreamId = data.m_currStreamId; + + m_socketStorage = data.m_socketStorage; + m_sockStorageLen = data.m_sockStorageLen; + + m_address = data.m_address; + m_port = data.m_port; + + m_salt = data.m_salt; + + m_connected = data.m_connected; + m_connectionState = data.m_connectionState; + + m_pingsReceived = data.m_pingsReceived; + m_lastPing = data.m_lastPing; + + m_config = data.m_config; + + m_pktLastSeq = data.m_pktLastSeq; + m_pktNextSeq = data.m_pktNextSeq; + } + + return *this; + } + + public: + /// Peer ID. + __PROPERTY_PLAIN(uint32_t, id, id); + + /// Current Stream ID. + __PROPERTY_PLAIN(uint32_t, currStreamId, currStreamId); + + /// Unix socket storage containing the connected address. + __PROPERTY_PLAIN(sockaddr_storage, socketStorage, socketStorage); + /// Length of the sockaddr_storage structure. + __PROPERTY_PLAIN(uint32_t, sockStorageLen, sockStorageLen); + + /// IP address peer connected with. + __PROPERTY_PLAIN(std::string, address, address); + /// Port number peer connected with. + __PROPERTY_PLAIN(uint16_t, port, port); + + /// Salt value used for peer authentication. + __PROPERTY_PLAIN(uint32_t, salt, salt); + + /// Flag indicating whether or not the peer is connected. + __PROPERTY_PLAIN(bool, connected, connected); + /// Connection state. + __PROPERTY_PLAIN(NET_CONN_STATUS, connectionState, connectionState); + + /// Number of pings received. + __PROPERTY_PLAIN(uint32_t, pingsReceived, pingsReceived); + /// Last ping received. + __PROPERTY_PLAIN(uint64_t, lastPing, lastPing); + + /// JSON objecting containing peer configuration information. + __PROPERTY_PLAIN(json::object, config, config); + + /// Last received RTP sequence. + __PROPERTY_PLAIN(uint16_t, pktLastSeq, pktLastSeq); + /// Calculated next RTP sequence. + __PROPERTY_PLAIN(uint16_t, pktNextSeq, pktNextSeq); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the core FNE networking logic. + // --------------------------------------------------------------------------- + + class HOST_SW_API FNENetwork : public BaseNetwork { + public: + /// Initializes a new instance of the FNENetwork class. + FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, + bool debug, bool verbose, bool dmr, bool p25, bool nxdn, uint32_t parrotDelay, bool allowActivityTransfer, + bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime); + /// Finalizes a instance of the FNENetwork class. + ~FNENetwork(); + + /// Gets the current status of the network. + NET_CONN_STATUS getStatus() { return m_status; } + + /// Gets the instance of the DMR traffic handler. + fne::TagDMRData* dmrTrafficHandler() const { return m_tagDMR; } + /// Gets the instance of the P25 traffic handler. + fne::TagP25Data* p25TrafficHandler() const { return m_tagP25; } + /// Gets the instance of the NXDN traffic handler. + fne::TagNXDNData* nxdnTrafficHandler() const { return m_tagNXDN; } + + /// Sets the instances of the Radio ID and Talkgroup Rules lookup tables. + void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup); + + /// Updates the timer by the passed number of milliseconds. + void clock(uint32_t ms); + + /// Opens connection to the network. + bool open(); + + /// Closes connection to the network. + void close(); + + private: + friend class fne::TagDMRData; + fne::TagDMRData* m_tagDMR; + friend class fne::TagP25Data; + fne::TagP25Data* m_tagP25; + friend class fne::TagNXDNData; + fne::TagNXDNData* m_tagNXDN; + + HostFNE* m_host; + + std::string m_address; + uint16_t m_port; + + std::string m_password; + + bool m_dmrEnabled; + bool m_p25Enabled; + bool m_nxdnEnabled; + + uint32_t m_parrotDelay; + + lookups::RadioIdLookup* m_ridLookup; + lookups::TalkgroupRulesLookup* m_tidLookup; + + NET_CONN_STATUS m_status; + + typedef std::pair PeerMapPair; + std::unordered_map m_peers; + + Timer m_maintainenceTimer; + Timer m_updateLookupTimer; + + bool m_verbose; + + /// Helper to send the list of whitelisted RIDs to the specified peer. + void writeWhitelistRIDs(uint32_t peerId, bool queueOnly = false); + /// Helper to send the list of whitelisted RIDs to connected peers. + void writeWhitelistRIDs(); + /// Helper to send the list of blacklisted RIDs to the specified peer. + void writeBlacklistRIDs(uint32_t peerId, bool queueOnly = false); + /// Helper to send the list of blacklisted RIDs to connected peers. + void writeBlacklistRIDs(); + + /// Helper to send the list of active TGIDs to the specified peer. + void writeTGIDs(uint32_t peerId, bool queueOnly = false); + /// Helper to send the list of active TGIDs to connected peers. + void writeTGIDs(); + /// Helper to send the list of deactivated TGIDs to the specified peer. + void writeDeactiveTGIDs(uint32_t peerId, bool queueOnly = false); + /// Helper to send the list of deactivated TGIDs to connected peers. + void writeDeactiveTGIDs(); + + /// Helper to send a raw message to the specified peer. + bool writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, + uint16_t pktSeq, bool queueOnly = false); + /// Helper to send a raw message to the specified peer. + bool writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, + bool queueOnly = false, bool incPktSeq = false); + /// Helper to send a tagged message to the specified peer. + bool writePeerTagged(uint32_t peerId, FrameQueue::OpcodePair opcode, const char* tag, const uint8_t* data = nullptr, uint32_t length = 0U, + bool queueOnly = false, bool incPktSeq = false); + /// Helper to send a ACK response to the specified peer. + bool writePeerACK(uint32_t peerId, const uint8_t* data = nullptr, uint32_t length = 0U); + /// Helper to send a NAK response to the specified peer. + bool writePeerNAK(uint32_t peerId, const char* tag); + /// Helper to send a NAK response to the specified peer. + bool writePeerNAK(uint32_t peerId, const char* tag, sockaddr_storage& addr, uint32_t addrLen); + + /// Helper to send a raw message to the connected peers. + void writePeers(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length); + /// Helper to send a raw message to the connected peers. + void writePeers(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq); + /// Helper to send a tagged message to the connected peers. + void writePeersTagged(FrameQueue::OpcodePair opcode, const char* tag, const uint8_t* data, uint32_t length); + }; +} // namespace network + +#endif // __FNE_NETWORK_H__ diff --git a/src/network/FrameQueue.cpp b/src/network/FrameQueue.cpp new file mode 100644 index 00000000..5a889120 --- /dev/null +++ b/src/network/FrameQueue.cpp @@ -0,0 +1,263 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/CRC.h" +#include "network/BaseNetwork.h" +#include "network/FrameQueue.h" +#include "Log.h" +#include "Utils.h" + +using namespace network; +using namespace network::frame; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the FrameQueue class. +/// +/// Local port used to listen for incoming data. +/// Unique ID of this modem on the network. +FrameQueue::FrameQueue(UDPSocket* socket, uint32_t peerId, bool debug) : + m_peerId(peerId), + m_socket(socket), + m_buffers(), + m_debug(debug) +{ + assert(peerId > 1000U); +} + +/// +/// Finalizes a instance of the FrameQueue class. +/// +FrameQueue::~FrameQueue() +{ + /* stub */ +} + +/// +/// Read message from the received UDP packet. +/// +/// Actual length of message read from packet. +/// IP address data read from. +/// +/// RTP Header. +/// FNE Header. +/// Buffer containing message read. +UInt8Array FrameQueue::read(int& messageLength, sockaddr_storage& address, uint32_t& addrLen, + RTPHeader* rtpHeader, RTPFNEHeader* fneHeader) +{ + RTPHeader _rtpHeader = RTPHeader(); + RTPFNEHeader _fneHeader = RTPFNEHeader(); + + messageLength = -1; + + // read message from socket + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + int length = m_socket->read(buffer, DATA_PACKET_LENGTH, address, addrLen); + if (length < 0) { + LogError(LOG_NET, "Failed reading data from the network"); + return nullptr; + } + + if (length > 0) { + if (m_debug) + Utils::dump(1U, "Network Packet", buffer, length); + + if (length < RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES) { + LogError(LOG_NET, "FrameQueue::read(), message received from network is malformed! %u bytes != %u bytes", + RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES, length); + return nullptr; + } + + // decode RTP header + if (!_rtpHeader.decode(buffer)) { + LogError(LOG_NET, "FrameQueue::read(), invalid RTP packet received from network"); + return nullptr; + } + + // ensure the RTP header has extension header (otherwise abort) + if (!_rtpHeader.getExtension()) { + LogError(LOG_NET, "FrameQueue::read(), invalid RTP header received from network"); + return nullptr; + } + + // ensure payload type is correct + if ((_rtpHeader.getPayloadType() != DVM_RTP_PAYLOAD_TYPE) && + (_rtpHeader.getPayloadType() != DVM_CTRL_RTP_PAYLOAD_TYPE)) { + LogError(LOG_NET, "FrameQueue::read(), invalid RTP payload type received from network"); + return nullptr; + } + + if (rtpHeader != nullptr) { + *rtpHeader = _rtpHeader; + } + + // decode FNE RTP header + if (!_fneHeader.decode(buffer + RTP_HEADER_LENGTH_BYTES)) { + LogError(LOG_NET, "FrameQueue::read(), invalid RTP packet received from network"); + return nullptr; + } + + if (fneHeader != nullptr) { + *fneHeader = _fneHeader; + } + + // ensure the RTP synchronization source ID matches the FNE stream ID + if (_rtpHeader.getSSRC() != _fneHeader.getStreamId()) { + LogWarning(LOG_NET, "FrameQueue::read(), RTP header and FNE header do not agree on stream ID? %u != %u", _rtpHeader.getSSRC(), _fneHeader.getStreamId()); + } + + // copy message + messageLength = _fneHeader.getMessageLength(); + __UNIQUE_UINT8_ARRAY(message, messageLength); + ::memcpy(message.get(), buffer + (RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES), messageLength); + + uint16_t calc = edac::CRC::createCRC16(message.get(), messageLength * 8U); + if (calc != _fneHeader.getCRC()) { + LogError(LOG_NET, "FrameQueue::read(), failed CRC CCITT-162 check"); + messageLength = -1; + return nullptr; + } + + // LogDebug(LOG_NET, "message buffer, addr %p len %u", message.get(), messageLength); + return message; + } + + return nullptr; +} + +/// +/// Cache "message" to frame queue. +/// +/// Message buffer to frame and queue. +/// Length of message. +/// Message stream ID. +/// Peer ID. +/// Opcode. +/// RTP Sequence. +/// IP address to write data to. +/// +/// +void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, + OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) +{ + assert(message != nullptr); + assert(length > 0U); + + uint32_t bufferLen = RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES + length; + uint8_t* buffer = new uint8_t[bufferLen]; + ::memset(buffer, 0x00U, bufferLen); + + RTPHeader header = RTPHeader(); + header.setExtension(true); + + header.setPayloadType(DVM_RTP_PAYLOAD_TYPE); + header.setSequence(rtpSeq); + header.setSSRC(streamId); + + // properly flag control opcodes + if ((opcode.first == NET_FUNC_TRANSFER) || (opcode.first == NET_FUNC_GRANT)) { + header.setPayloadType(DVM_CTRL_RTP_PAYLOAD_TYPE); + header.setSequence(0U); + header.setSSRC(0U); + } + + header.encode(buffer); + + RTPFNEHeader fneHeader = RTPFNEHeader(); + fneHeader.setCRC(edac::CRC::createCRC16(message, length * 8U)); + fneHeader.setStreamId(streamId); + fneHeader.setPeerId(peerId); + fneHeader.setMessageLength(length); + + fneHeader.setFunction(opcode.first); + fneHeader.setSubFunction(opcode.second); + + fneHeader.encode(buffer + RTP_HEADER_LENGTH_BYTES); + + ::memcpy(buffer + RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES, message, length); + + if (m_debug) + Utils::dump(1U, "FrameQueue::enqueueMessage() Buffered Message", buffer, bufferLen); + + UDPDatagram* dgram = new UDPDatagram; + dgram->buffer = buffer; + dgram->length = bufferLen; + dgram->address = addr; + dgram->addrLen = addrLen; + + m_buffers.push_back(dgram); +} + +/// +/// Flush the message queue. +/// +/// +bool FrameQueue::flushQueue() +{ + if (m_buffers.empty()) { + return false; + } + + // bryanb: this is the same as above -- but for some assinine reason prevents + // weirdness + if (m_buffers.size() == 0U) { + return false; + } + + // LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size()); + + bool ret = true; + if (!m_socket->write(m_buffers)) { + LogError(LOG_NET, "Failed writing data to the network"); + ret = false; + } + + for (auto& buffer : m_buffers) { + if (buffer != nullptr) { + // LogDebug(LOG_NET, "deleting buffer, addr %p len %u", buffer->buffer, buffer->length); + if (buffer->buffer != nullptr) { + delete buffer->buffer; + buffer->length = 0; + buffer->buffer = nullptr; + } + + delete buffer; + buffer = nullptr; + } + } + m_buffers.clear(); + + return ret; +} diff --git a/src/network/FrameQueue.h b/src/network/FrameQueue.h new file mode 100644 index 00000000..fccd99f3 --- /dev/null +++ b/src/network/FrameQueue.h @@ -0,0 +1,83 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__FRAME_QUEUE_H__) +#define __FRAME_QUEUE_H__ + +#include "Defines.h" +#include "network/UDPSocket.h" +#include "network/RTPHeader.h" +#include "network/RTPExtensionHeader.h" +#include "network/RTPFNEHeader.h" + +namespace network +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t DATA_PACKET_LENGTH = 8192U; + + const uint8_t DVM_RTP_PAYLOAD_TYPE = 0x56U; + const uint8_t DVM_CTRL_RTP_PAYLOAD_TYPE = 0x57U; // these are still RTP, but do not carry stream IDs or sequence data + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the network frame queuing logic. + // --------------------------------------------------------------------------- + + class HOST_SW_API FrameQueue { + public: typedef std::pair OpcodePair; + public: + /// Initializes a new instance of the FrameQueue class. + FrameQueue(UDPSocket* socket, uint32_t peerId, bool debug); + /// Finalizes a instance of the FrameQueue class. + virtual ~FrameQueue(); + + /// Read message from the received UDP packet. + UInt8Array read(int& messageLength, sockaddr_storage& address, uint32_t& addrLen, + frame::RTPHeader* rtpHeader = nullptr, frame::RTPFNEHeader* fneHeader = nullptr); + + /// Cache "message" to frame queue. + void enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, + OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen); + + /// Flush the message queue. + bool flushQueue(); + + private: + uint32_t m_peerId; + + sockaddr_storage m_addr; + uint32_t m_addrLen; + UDPSocket* m_socket; + + BufferVector m_buffers; + + bool m_debug; + }; +} // namespace network + +#endif // __FRAME_QUEUE_H__ \ No newline at end of file diff --git a/src/network/Network.cpp b/src/network/Network.cpp index e7897a4b..bc059e7e 100644 --- a/src/network/Network.cpp +++ b/src/network/Network.cpp @@ -12,7 +12,7 @@ // /* * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX -* Copyright (C) 2017-2022 by Bryan Biedenkapp N2PLL +* Copyright (C) 2017-2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,6 +31,8 @@ #include "Defines.h" #include "edac/SHA256.h" #include "network/Network.h" +#include "network/RTPHeader.h" +#include "network/RTPFNEHeader.h" #include "network/json/json.h" #include "Log.h" #include "StopWatch.h" @@ -52,9 +54,10 @@ using namespace network; /// Network Hostname/IP address to connect to. /// Network port number. /// -/// Unique ID of this modem on the network. +/// Unique ID on the network. /// Network authentication password. /// Flag indicating full-duplex operation. +/// Flag indicating whether network debug is enabled. /// Flag indicating whether DMR is enabled. /// Flag indicating whether P25 is enabled. /// Flag indicating whether NXDN is enabled. @@ -63,9 +66,10 @@ using namespace network; /// Flag indicating that the system activity logs will be sent to the network. /// Flag indicating that the system diagnostic logs will be sent to the network. /// Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. -Network::Network(const std::string& address, uint16_t port, uint16_t local, uint32_t id, const std::string& password, +Network::Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup) : - BaseNetwork(local, id, duplex, debug, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer), + BaseNetwork(peerId, duplex, debug, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, localPort), + m_pktLastSeq(0U), m_address(address), m_port(port), m_password(password), @@ -74,6 +78,13 @@ Network::Network(const std::string& address, uint16_t port, uint16_t local, uint m_p25Enabled(p25), m_nxdnEnabled(nxdn), m_updateLookup(updateLookup), + m_ridLookup(nullptr), + m_tidLookup(nullptr), + m_salt(nullptr), + m_retryTimer(1000U, 10U), + m_timeoutTimer(1000U, 60U), + m_pktSeq(0U), + m_loginStreamId(0U), m_identity(), m_rxFrequency(0U), m_txFrequency(0U), @@ -92,6 +103,14 @@ Network::Network(const std::string& address, uint16_t port, uint16_t local, uint assert(!address.empty()); assert(port > 0U); assert(!password.empty()); + + m_salt = new uint8_t[sizeof(uint32_t)]; + + m_rxDMRStreamId = new uint32_t[2U]; + m_rxDMRStreamId[0U] = 0U; + m_rxDMRStreamId[1U] = 0U; + m_rxP25StreamId = 0U; + m_rxNXDNStreamId = 0U; } /// @@ -99,15 +118,51 @@ Network::Network(const std::string& address, uint16_t port, uint16_t local, uint /// Network::~Network() { - /* stub */ + delete[] m_salt; + delete[] m_rxDMRStreamId; +} + +/// +/// Resets the DMR ring buffer for the given slot. +/// +/// DMR slot ring buffer to reset. +void Network::resetDMR(uint32_t slotNo) +{ + assert(slotNo == 1U || slotNo == 2U); + + BaseNetwork::resetDMR(slotNo); + if (slotNo == 1U) { + m_rxDMRStreamId[0U] = 0U; + } + else { + m_rxDMRStreamId[1U] = 0U; + } +} + +/// +/// Resets the P25 ring buffer. +/// +void Network::resetP25() +{ + BaseNetwork::resetP25(); + m_rxP25StreamId = 0U; +} + +/// +/// Resets the NXDN ring buffer. +/// +void Network::resetNXDN() +{ + BaseNetwork::resetNXDN(); + m_rxNXDNStreamId = 0U; } /// /// Sets the instances of the Radio ID and Talkgroup ID lookup tables. /// /// Radio ID Lookup Table Instance -/// Talkgroup ID Lookup Table Instance -void Network::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup) +/// Talkgroup Rules Lookup Table Instance +void Network::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; @@ -158,15 +213,6 @@ void Network::setRESTAPIData(const std::string& password, uint16_t port) m_restApiPort = port; } -/// -/// Gets the current status of the network. -/// -/// -uint8_t Network::getStatus() -{ - return m_status; -} - /// /// Updates the timer by the passed number of milliseconds. /// @@ -176,14 +222,18 @@ void Network::clock(uint32_t ms) if (m_status == NET_STAT_WAITING_CONNECT) { m_retryTimer.clock(ms); if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { - bool ret = m_socket.open(m_addr.ss_family); - if (ret) { - ret = writeLogin(); - if (!ret) - return; + if (m_enabled) { + bool ret = m_socket->open(m_addr.ss_family); + if (ret) { + ret = writeLogin(); + if (!ret) { + m_retryTimer.start(); + return; + } - m_status = NET_STAT_WAITING_LOGIN; - m_timeoutTimer.start(); + m_status = NET_STAT_WAITING_LOGIN; + m_timeoutTimer.start(); + } } m_retryTimer.start(); @@ -192,183 +242,291 @@ void Network::clock(uint32_t ms) return; } - sockaddr_storage address; - uint32_t addrLen; - int length = m_socket.read(m_buffer, DATA_PACKET_LENGTH, address, addrLen); - if (length < 0) { - LogError(LOG_NET, "Socket has failed, retrying connection to the master"); - close(); - open(); + // if we aren't enabled -- bail + if (!m_enabled) { return; } + sockaddr_storage address; + uint32_t addrLen; + + frame::RTPHeader rtpHeader; + frame::RTPFNEHeader fneHeader; + int length = 0U; + + // read message + UInt8Array buffer = m_frameQueue->read(length, address, addrLen, &rtpHeader, &fneHeader); if (length > 0) { if (!UDPSocket::match(m_addr, address)) { LogError(LOG_NET, "Packet received from an invalid source"); return; } - if (::memcmp(m_buffer, TAG_DMR_DATA, 4U) == 0) { -#if defined(ENABLE_DMR) - if (m_enabled && m_dmrEnabled) { - if (m_debug) - Utils::dump(1U, "Network Received, DMR", m_buffer, length); - - uint8_t len = length; - m_rxDMRData.addData(&len, 1U); - m_rxDMRData.addData(m_buffer, len); - } -#endif + if (m_debug) { + LogDebug(LOG_NET, "RTP, peerId = %u, seq = %u, streamId = %u, func = %02X, subFunc = %02X", fneHeader.getPeerId(), rtpHeader.getSequence(), + fneHeader.getStreamId(), fneHeader.getFunction(), fneHeader.getSubFunction()); } - else if (::memcmp(m_buffer, TAG_P25_DATA, 4U) == 0) { -#if defined(ENABLE_P25) - if (m_enabled && m_p25Enabled) { - if (m_debug) - Utils::dump(1U, "Network Received, P25", m_buffer, length); - uint8_t len = length; - m_rxP25Data.addData(&len, 1U); - m_rxP25Data.addData(m_buffer, len); - } -#endif + // is this RTP packet destined for us? + uint32_t peerId = fneHeader.getPeerId(); + if (m_peerId != peerId) { + LogError(LOG_NET, "Packet received was not destined for us? peerId = %u", peerId); + return; } - else if (::memcmp(m_buffer, TAG_NXDN_DATA, 4U) == 0) { -#if defined(ENABLE_NXDN) - if (m_enabled && m_nxdnEnabled) { - if (m_debug) - Utils::dump(1U, "Network Received, NXDN", m_buffer, length); - uint8_t len = length; - m_rxNXDNData.addData(&len, 1U); - m_rxNXDNData.addData(m_buffer, len); - } -#endif + // peer connections should never encounter no stream ID + uint32_t streamId = fneHeader.getStreamId(); + if (streamId == 0U) { + LogWarning(LOG_NET, "BUGBUG: strange RTP packet with no stream ID?"); } - else if (::memcmp(m_buffer, TAG_MASTER_WL_RID, 7U) == 0) { - if (m_enabled && m_updateLookup) { - if (m_debug) - Utils::dump(1U, "Network Received, WL RID", m_buffer, length); - - // update RID lists - uint32_t len = (m_buffer[7U] << 16) | (m_buffer[8U] << 8) | (m_buffer[9U] << 0); - uint32_t j = 0U; - for (uint8_t i = 0; i < len; i++) { - uint32_t id = (m_buffer[11U + j] << 16) | (m_buffer[12U + j] << 8) | (m_buffer[13U + j] << 0); - m_ridLookup->toggleEntry(id, true); - j += 4U; + + m_pktSeq = rtpHeader.getSequence(); + + // process incoming message frame opcodes + switch (fneHeader.getFunction()) { + case NET_FUNC_PROTOCOL: + { + if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_DMR) { // Encapsulated DMR data frame +#if defined(ENABLE_DMR) + if (m_enabled && m_dmrEnabled) { + uint32_t slotNo = (buffer[15U] & 0x80U) == 0x80U ? 2U : 1U; + if (m_rxDMRStreamId[slotNo] == 0U) { + m_rxDMRStreamId[slotNo] = streamId; + } + else { + if (m_rxDMRStreamId[slotNo] == streamId) { + if (m_pktLastSeq != 0U && m_pktSeq != m_pktLastSeq + 1) { + LogWarning(LOG_NET, "Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); + } + } + else { + m_rxDMRStreamId[slotNo] = streamId; + } + } + m_pktLastSeq = m_pktSeq; + + if (m_debug) + Utils::dump(1U, "Network Received, DMR", buffer.get(), length); + + uint8_t len = length; + m_rxDMRData.addData(&len, 1U); + m_rxDMRData.addData(buffer.get(), len); + } +#endif // defined(ENABLE_DMR) } - } - } - else if (::memcmp(m_buffer, TAG_MASTER_BL_RID, 7U) == 0) { - if (m_enabled && m_updateLookup) { - if (m_debug) - Utils::dump(1U, "Network Received, BL RID", m_buffer, length); - - // update RID lists - uint32_t len = (m_buffer[7U] << 16) | (m_buffer[8U] << 8) | (m_buffer[9U] << 0); - uint32_t j = 0U; - for (uint8_t i = 0; i < len; i++) { - uint32_t id = (m_buffer[11U + j] << 16) | (m_buffer[12U + j] << 8) | (m_buffer[13U + j] << 0); - m_ridLookup->toggleEntry(id, false); - j += 4U; + else if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_P25) { // Encapsulated P25 data frame +#if defined(ENABLE_P25) + if (m_enabled && m_p25Enabled) { + if (m_rxP25StreamId == 0U) { + m_rxP25StreamId = streamId; + } + else { + if (m_rxP25StreamId == streamId) { + if (m_pktLastSeq != 0U && m_pktSeq != m_pktLastSeq + 1) { + LogWarning(LOG_NET, "Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); + } + } + else { + m_rxP25StreamId = streamId; + } + } + m_pktLastSeq = m_pktSeq; + + if (m_debug) + Utils::dump(1U, "Network Received, P25", buffer.get(), length); + + uint8_t len = length; + m_rxP25Data.addData(&len, 1U); + m_rxP25Data.addData(buffer.get(), len); + } +#endif // defined(ENABLE_P25) } - } - } - else if (::memcmp(m_buffer, TAG_MASTER_ACTIVE_TGS, 6U) == 0) { - if (m_enabled && m_updateLookup) { - if (m_debug) - Utils::dump(1U, "Network Received, ACTIVE TGS", m_buffer, length); - - // update TGID lists - uint32_t len = (m_buffer[7U] << 16) | (m_buffer[8U] << 8) | (m_buffer[9U] << 0); - uint32_t j = 0U; - for (uint8_t i = 0; i < len; i++) { - uint32_t id = (m_buffer[11U + j] << 16) | (m_buffer[12U + j] << 8) | (m_buffer[13U + j] << 0); - uint8_t slot = (m_buffer[14U + j]); - - lookups::TalkgroupId tid = m_tidLookup->find(id); - if (tid.tgEnabled() == false && tid.tgDefault() == true) { - LogMessage(LOG_NET, "Activated TG %u TS %u in TGID table", id, slot); + else if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_NXDN) { // Encapsulated NXDN data frame +#if defined(ENABLE_NXDN) + if (m_enabled && m_nxdnEnabled) { + if (m_rxNXDNStreamId == 0U) { + m_rxNXDNStreamId = streamId; + } + else { + if (m_rxNXDNStreamId == streamId) { + if (m_pktLastSeq != 0U && m_pktSeq != m_pktLastSeq + 1) { + LogWarning(LOG_NET, "Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); + } + } + else { + m_rxNXDNStreamId = streamId; + } + } + m_pktLastSeq = m_pktSeq; + + if (m_debug) + Utils::dump(1U, "Network Received, NXDN", buffer.get(), length); + + uint8_t len = length; + m_rxNXDNData.addData(&len, 1U); + m_rxNXDNData.addData(buffer.get(), len); } - - m_tidLookup->addEntry(id, slot, true); - j += 5U; +#endif // defined(ENABLE_NXDN) + } + else { + Utils::dump("Unknown protocol opcode from the master", buffer.get(), length); } } - } - else if (::memcmp(m_buffer, TAG_MASTER_DEACTIVE_TGS, 7U) == 0) { - if (m_enabled && m_updateLookup) { - if (m_debug) - Utils::dump(1U, "Network Received, DEACTIVE TGS", m_buffer, length); - - // update TGID lists - uint32_t len = (m_buffer[7U] << 16) | (m_buffer[8U] << 8) | (m_buffer[9U] << 0); - uint32_t j = 0U; - for (uint8_t i = 0; i < len; i++) { - uint32_t id = (m_buffer[11U + j] << 16) | (m_buffer[12U + j] << 8) | (m_buffer[13U + j] << 0); - uint8_t slot = (m_buffer[14U + j]); - - lookups::TalkgroupId tid = m_tidLookup->find(id); - if (tid.tgEnabled() == true && tid.tgDefault() == false) { - LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); - m_tidLookup->addEntry(id, slot, false); + break; + + case NET_FUNC_MASTER: + { + if (fneHeader.getSubFunction() == NET_MASTER_SUBFUNC_WL_RID) { // Radio ID Whitelist + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, WL RID", buffer.get(), length); + + if (m_ridLookup != nullptr) { + // update RID lists + uint32_t len = __GET_UINT16(buffer, 7U); + uint32_t j = 0U; + for (uint8_t i = 0; i < len; i++) { + uint32_t id = __GET_UINT16(buffer, 11U + j); + m_ridLookup->toggleEntry(id, true); + j += 4U; + } + } + } + } + else if (fneHeader.getSubFunction() == NET_MASTER_SUBFUNC_BL_RID) { // Radio ID Blacklist + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, BL RID", buffer.get(), length); + + if (m_ridLookup != nullptr) { + // update RID lists + uint32_t len = __GET_UINT16(buffer, 7U); + uint32_t j = 0U; + for (uint8_t i = 0; i < len; i++) { + uint32_t id = __GET_UINT16(buffer, 11U + j); + m_ridLookup->toggleEntry(id, false); + j += 4U; + } + } + } + } + else if (fneHeader.getSubFunction() == NET_MASTER_SUBFUNC_ACTIVE_TGS) { // Talkgroup Active IDs + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, ACTIVE TGS", buffer.get(), length); + + if (m_tidLookup != nullptr) { + // update TGID lists + uint32_t len = __GET_UINT16(buffer, 7U); + uint32_t j = 0U; + for (uint8_t i = 0; i < len; i++) { + uint32_t id = __GET_UINT16(buffer, 11U + j); + uint8_t slot = (buffer[14U + j]); + + lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); + if (tid.isInvalid()) { + if (!tid.config().active()) { + m_tidLookup->eraseEntry(id, slot); + } + + LogMessage(LOG_NET, "Activated TG %u TS %u in TGID table", id, slot); + m_tidLookup->addEntry(id, slot, true); + } + + j += 5U; + } + } } + } + else if (fneHeader.getSubFunction() == NET_MASTER_SUBFUNC_DEACTIVE_TGS) { // Talkgroup Deactivated IDs + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, DEACTIVE TGS", buffer.get(), length); + + if (m_tidLookup != nullptr) { + // update TGID lists + uint32_t len = __GET_UINT16(buffer, 7U); + uint32_t j = 0U; + for (uint8_t i = 0; i < len; i++) { + uint32_t id = __GET_UINT16(buffer, 11U + j); + uint8_t slot = (buffer[14U + j]); + + lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); + if (!tid.isInvalid()) { + LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); + m_tidLookup->eraseEntry(id, slot); + } + + j += 5U; + } + } + } + } + else { + Utils::dump("Unknown master control opcode from the master", buffer.get(), length); + } + } + break; - j += 5U; + case NET_FUNC_NAK: // Master Negative Ack + { + if (m_status == NET_STAT_RUNNING) { + LogWarning(LOG_NET, "Master returned a NAK; attemping to relogin ..."); + m_status = NET_STAT_WAITING_LOGIN; + m_timeoutTimer.start(); + m_retryTimer.start(); + } + else { + LogError(LOG_NET, "Master returned a NAK; network reconnect ..."); + close(); + open(); + return; } } - } - else if (::memcmp(m_buffer, TAG_MASTER_NAK, 6U) == 0) { - if (m_status == NET_STAT_RUNNING) { - LogWarning(LOG_NET, "Master returned a NAK; attemping to relogin ..."); - m_status = NET_STAT_WAITING_LOGIN; - m_timeoutTimer.start(); - m_retryTimer.start(); + break; + case NET_FUNC_ACK: // Repeater Ack + { + switch (m_status) { + case NET_STAT_WAITING_LOGIN: + LogDebug(LOG_NET, "Sending authorisation"); + ::memcpy(m_salt, buffer.get() + 6U, sizeof(uint32_t)); + writeAuthorisation(); + m_status = NET_STAT_WAITING_AUTHORISATION; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + case NET_STAT_WAITING_AUTHORISATION: + LogDebug(LOG_NET, "Sending configuration"); + writeConfig(); + m_status = NET_STAT_WAITING_CONFIG; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + case NET_STAT_WAITING_CONFIG: + LogMessage(LOG_NET, "Logged into the master successfully"); + m_loginStreamId = 0U; + pktSeq(true); + m_status = NET_STAT_RUNNING; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + default: + break; + } } - else { - LogError(LOG_NET, "Master returned a NAK; network reconnect ..."); + break; + case NET_FUNC_MST_CLOSING: // Master Shutdown + { + LogError(LOG_NET, "Master is closing down"); close(); open(); - return; - } - } - else if (::memcmp(m_buffer, TAG_REPEATER_ACK, 6U) == 0) { - switch (m_status) { - case NET_STAT_WAITING_LOGIN: - LogDebug(LOG_NET, "Sending authorisation"); - ::memcpy(m_salt, m_buffer + 6U, sizeof(uint32_t)); - writeAuthorisation(); - m_status = NET_STAT_WAITING_AUTHORISATION; - m_timeoutTimer.start(); - m_retryTimer.start(); - break; - case NET_STAT_WAITING_AUTHORISATION: - LogDebug(LOG_NET, "Sending configuration"); - writeConfig(); - m_status = NET_STAT_WAITING_CONFIG; - m_timeoutTimer.start(); - m_retryTimer.start(); - break; - case NET_STAT_WAITING_CONFIG: - LogMessage(LOG_NET, "Logged into the master successfully"); - m_status = NET_STAT_RUNNING; - m_timeoutTimer.start(); - m_retryTimer.start(); - break; - default: - break; } - } - else if (::memcmp(m_buffer, TAG_MASTER_CLOSING, 5U) == 0) { - LogError(LOG_NET, "Master is closing down"); - close(); - open(); - } - else if (::memcmp(m_buffer, TAG_MASTER_PONG, 7U) == 0) { + break; + case NET_FUNC_PONG: // Master Ping Response m_timeoutTimer.start(); - } - else { - Utils::dump("Unknown packet from the master", m_buffer, length); + break; + default: + Utils::dump("Unknown opcode from the master", buffer.get(), length); } } @@ -408,6 +566,8 @@ void Network::clock(uint32_t ms) /// bool Network::open() { + if (!m_enabled) + return false; if (m_debug) LogMessage(LOG_NET, "Opening Network"); @@ -443,11 +603,13 @@ void Network::close() if (m_status == NET_STAT_RUNNING) { uint8_t buffer[9U]; ::memcpy(buffer + 0U, TAG_REPEATER_CLOSING, 5U); - __SET_UINT32(m_id, buffer, 5U); - write(buffer, 9U); + + m_frameQueue->enqueueMessage(buffer, 9U, createStreamId(), m_peerId, + { NET_FUNC_RPT_CLOSING, NET_SUBFUNC_NOP }, pktSeq(true), m_addr, m_addrLen); + m_frameQueue->flushQueue(); } - m_socket.close(); + m_socket->close(); m_retryTimer.stop(); m_timeoutTimer.stop(); @@ -465,15 +627,21 @@ void Network::close() /// bool Network::writeLogin() { + if (!m_enabled) { + return false; + } + uint8_t buffer[8U]; ::memcpy(buffer + 0U, TAG_REPEATER_LOGIN, 4U); - __SET_UINT32(m_id, buffer, 4U); if (m_debug) - Utils::dump(1U, "Network Transmitted, Login", buffer, 8U); + Utils::dump(1U, "Network Message, Login", buffer, 8U); - return write(buffer, 8U); + m_loginStreamId = createStreamId(); + m_frameQueue->enqueueMessage(buffer, 8U, m_loginStreamId, m_peerId, + { NET_FUNC_RPTL, NET_SUBFUNC_NOP }, pktSeq(true), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// @@ -482,6 +650,11 @@ bool Network::writeLogin() /// bool Network::writeAuthorisation() { + if (m_loginStreamId == 0U) { + LogWarning(LOG_NET, "BUGBUG: tried to write network authorisation with no stream ID?"); + return false; + } + size_t size = m_password.size(); uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; @@ -491,7 +664,7 @@ bool Network::writeAuthorisation() uint8_t out[40U]; ::memcpy(out + 0U, TAG_REPEATER_AUTH, 4U); - __SET_UINT32(m_id, out, 4U); + __SET_UINT32(m_peerId, out, 4U); // Peer ID edac::SHA256 sha256; sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out + 8U); @@ -499,9 +672,11 @@ bool Network::writeAuthorisation() delete[] in; if (m_debug) - Utils::dump(1U, "Network Transmitted, Authorisation", out, 40U); + Utils::dump(1U, "Network Message, Authorisation", out, 40U); - return write(out, 40U); + m_frameQueue->enqueueMessage(out, 40U, m_loginStreamId, m_peerId, + { NET_FUNC_RPTK, NET_SUBFUNC_NOP }, pktSeq(), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// @@ -510,6 +685,11 @@ bool Network::writeAuthorisation() /// bool Network::writeConfig() { + if (m_loginStreamId == 0U) { + LogWarning(LOG_NET, "BUGBUG: tried to write network authorisation with no stream ID?"); + return false; + } + const char* software = __NET_NAME__; json::object config = json::object(); @@ -551,14 +731,15 @@ bool Network::writeConfig() char buffer[json.length() + 8U]; ::memcpy(buffer + 0U, TAG_REPEATER_CONFIG, 4U); - __SET_UINT32(m_id, buffer, 4U); ::sprintf(buffer + 8U, "%s", json.c_str()); if (m_debug) { - Utils::dump(1U, "Network Transmitted, Configuration", (uint8_t*)buffer, json.length() + 8U); + Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U); } - return write((uint8_t*)buffer, json.length() + 8U); + m_frameQueue->enqueueMessage((uint8_t*)buffer, json.length() + 8U, m_loginStreamId, m_peerId, + { NET_FUNC_RPTC, NET_SUBFUNC_NOP }, pktSeq(), m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } /// @@ -569,10 +750,11 @@ bool Network::writePing() uint8_t buffer[11U]; ::memcpy(buffer + 0U, TAG_REPEATER_PING, 7U); - __SET_UINT32(m_id, buffer, 7U); if (m_debug) - Utils::dump(1U, "Network Transmitted, Ping", buffer, 11U); + Utils::dump(1U, "Network Message, Ping", buffer, 11U); - return write(buffer, 11U); + m_frameQueue->enqueueMessage(buffer, 11U, createStreamId(), m_peerId, + { NET_FUNC_PING, NET_SUBFUNC_NOP }, 0U, m_addr, m_addrLen); + return m_frameQueue->flushQueue(); } diff --git a/src/network/Network.h b/src/network/Network.h index 0698396c..453dea94 100644 --- a/src/network/Network.h +++ b/src/network/Network.h @@ -12,7 +12,7 @@ // /* * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX -* Copyright (C) 2017-2022 by Bryan Biedenkapp N2PLL +* Copyright (C) 2017-2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ #include "Defines.h" #include "network/BaseNetwork.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" #include #include @@ -43,26 +43,31 @@ namespace network { // --------------------------------------------------------------------------- // Class Declaration - // Implements the core networking logic. + // Implements the core peer networking logic. // --------------------------------------------------------------------------- class HOST_SW_API Network : public BaseNetwork { public: /// Initializes a new instance of the Network class. - Network(const std::string& address, uint16_t port, uint16_t local, uint32_t id, const std::string& password, - bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool transferActivityLog, bool transferDiagnosticLog, bool updateLookup); + Network(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password, + bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup); /// Finalizes a instance of the Network class. ~Network(); + /// Resets the DMR ring buffer for the given slot. + void resetDMR(uint32_t slotNo) override; + /// Resets the P25 ring buffer. + void resetP25() override; + /// Resets the NXDN ring buffer. + void resetNXDN() override; + /// Sets the instances of the Radio ID and Talkgroup ID lookup tables. - void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup); + void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup); /// Sets metadata configuration settings from the modem. void setMetadata(const std::string& callsign, uint32_t rxFrequency, uint32_t txFrequency, float txOffsetMhz, float chBandwidthKhz, uint8_t channelId, uint32_t channelNo, uint32_t power, float latitude, float longitude, int height, const std::string& location); /// Sets REST API configuration settings from the modem. void setRESTAPIData(const std::string& password, uint16_t port); - /// Gets the current status of the network. - uint8_t getStatus(); /// Updates the timer by the passed number of milliseconds. void clock(uint32_t ms); @@ -70,11 +75,15 @@ namespace network /// Opens connection to the network. bool open(); + /// Closes connection to the network. + void close(); + /// Sets flag enabling network communication. void enable(bool enabled); - /// Closes connection to the network. - void close(); + public: + /// Last received RTP sequence number. + __READONLY_PROPERTY_PLAIN(uint16_t, pktLastSeq, pktLastSeq); private: std::string m_address; @@ -91,7 +100,19 @@ namespace network bool m_updateLookup; lookups::RadioIdLookup* m_ridLookup; - lookups::TalkgroupIdLookup* m_tidLookup; + lookups::TalkgroupRulesLookup* m_tidLookup; + + uint8_t* m_salt; + + Timer m_retryTimer; + Timer m_timeoutTimer; + + uint32_t* m_rxDMRStreamId; + uint32_t m_rxP25StreamId; + uint32_t m_rxNXDNStreamId; + + uint16_t m_pktSeq; + uint32_t m_loginStreamId; /** station metadata */ std::string m_identity; diff --git a/src/network/RESTAPI.cpp b/src/network/RESTAPI.cpp index 2102f4f6..47435d47 100644 --- a/src/network/RESTAPI.cpp +++ b/src/network/RESTAPI.cpp @@ -208,8 +208,8 @@ RESTAPI::~RESTAPI() /// Sets the instances of the Radio ID and Talkgroup ID lookup tables. /// /// Radio ID Lookup Table Instance -/// Talkgroup ID Lookup Table Instance -void RESTAPI::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup) +/// Talkgroup Rules Lookup Table Instance +void RESTAPI::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; @@ -281,6 +281,9 @@ void RESTAPI::initializeEndpoints() m_dispatcher.match(GET_RELEASE_GRNTS).get(REST_API_BIND(RESTAPI::restAPI_GetReleaseGrants, this)); m_dispatcher.match(GET_RELEASE_AFFS).get(REST_API_BIND(RESTAPI::restAPI_GetReleaseAffs, this)); + m_dispatcher.match(PUT_RELEASE_TG).put(REST_API_BIND(RESTAPI::restAPI_PutReleaseGrant, this)); + m_dispatcher.match(PUT_TOUCH_TG).put(REST_API_BIND(RESTAPI::restAPI_PutTouchGrant, this)); + m_dispatcher.match(GET_RID_WHITELIST, true).get(REST_API_BIND(RESTAPI::restAPI_GetRIDWhitelist, this)); m_dispatcher.match(GET_RID_BLACKLIST, true).get(REST_API_BIND(RESTAPI::restAPI_GetRIDBlacklist, this)); @@ -1076,7 +1079,7 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPPayload& request, HTTPPayload& reply, { if (m_nxdn != nullptr) { // TODO TODO - //nxdn->grantTG(dstId, unitToUnit); + //m_nxdn->grantTG(dstId, unitToUnit); } else { errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); @@ -1153,6 +1156,234 @@ void RESTAPI::restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& re #endif // defined(ENABLE_NXDN) } +/// +/// +/// +/// +/// +/// +void RESTAPI::restAPI_PutReleaseGrant(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object req = json::object(); + if (!parseRequestBody(request, reply, req)) { + return; + } + + errorPayload(reply, "OK", HTTPPayload::OK); + + if (!m_host->m_dmrTSCCData && !m_host->m_p25CCData && !m_host->m_nxdnCCData) { + errorPayload(reply, "Host is not a control channel, cannot release TG grant"); + return; + } + + // validate state is a string within the JSON blob + if (!req["state"].is()) { + errorPayload(reply, "state was not a valid integer"); + return; + } + + DVM_STATE state = (DVM_STATE)req["state"].get(); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + errorPayload(reply, "destination ID was not a valid integer"); + return; + } + + uint32_t dstId = req["dstId"].get(); + + if (dstId == 0U) { + errorPayload(reply, "destination ID is an illegal TGID"); + return; + } + + // LogDebug(LOG_REST, "restAPI_PutReleaseGrant(): callback, state = %u, dstId = %u", state, dstId); + + switch (state) { + case STATE_DMR: +#if defined(ENABLE_DMR) + { + // validate slot is a integer within the JSON blob + if (!req["slot"].is()) { + errorPayload(reply, "slot was not a valid integer"); + return; + } + + uint8_t slot = (uint8_t)req["slot"].get(); + + if (slot == 0U || slot > 2U) { + errorPayload(reply, "illegal DMR slot"); + return; + } + + if (m_dmr != nullptr) { + m_dmr->releaseGrantTG(dstId, slot); + } + else { + errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); + } + } +#else + { + errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); + } +#endif // defined(ENABLE_DMR) + break; + case STATE_P25: +#if defined(ENABLE_P25) + { + if (m_p25 != nullptr) { + m_p25->releaseGrantTG(dstId); + } + else { + errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); + } + } +#else + { + errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); + } +#endif // defined(ENABLE_P25) + break; + case STATE_NXDN: +#if defined(ENABLE_NXDN) + { + if (m_nxdn != nullptr) { + m_nxdn->releaseGrantTG(dstId); + } + else { + errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); + } + } +#else + { + errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); + } +#endif // defined(ENABLE_NXDN) + break; + default: + errorPayload(reply, "invalid mode"); + } +} + +/// +/// +/// +/// +/// +/// +void RESTAPI::restAPI_PutTouchGrant(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object req = json::object(); + if (!parseRequestBody(request, reply, req)) { + return; + } + + errorPayload(reply, "OK", HTTPPayload::OK); + + if (!m_host->m_dmrTSCCData && !m_host->m_p25CCData && !m_host->m_nxdnCCData) { + errorPayload(reply, "Host is not a control channel, cannot touch TG grant"); + return; + } + + // validate state is a string within the JSON blob + if (!req["state"].is()) { + errorPayload(reply, "state was not a valid integer"); + return; + } + + DVM_STATE state = (DVM_STATE)req["state"].get(); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + errorPayload(reply, "destination ID was not a valid integer"); + return; + } + + uint32_t dstId = req["dstId"].get(); + + if (dstId == 0U) { + errorPayload(reply, "destination ID is an illegal TGID"); + return; + } + + // LogDebug(LOG_REST, "restAPI_PutTouchGrant(): callback, state = %u, dstId = %u", state, dstId); + + switch (state) { + case STATE_DMR: +#if defined(ENABLE_DMR) + { + // validate slot is a integer within the JSON blob + if (!req["slot"].is()) { + errorPayload(reply, "slot was not a valid integer"); + return; + } + + uint8_t slot = (uint8_t)req["slot"].get(); + + if (slot == 0U || slot > 2U) { + errorPayload(reply, "illegal DMR slot"); + return; + } + + if (m_dmr != nullptr) { + m_dmr->touchGrantTG(dstId, slot); + } + else { + errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); + } + } +#else + { + errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); + } +#endif // defined(ENABLE_DMR) + break; + case STATE_P25: +#if defined(ENABLE_P25) + { + if (m_p25 != nullptr) { + m_p25->touchGrantTG(dstId); + } + else { + errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); + } + } +#else + { + errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); + } +#endif // defined(ENABLE_P25) + break; + case STATE_NXDN: +#if defined(ENABLE_NXDN) + { + if (m_nxdn != nullptr) { + m_nxdn->touchGrantTG(dstId); + } + else { + errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); + } + } +#else + { + errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); + } +#endif // defined(ENABLE_NXDN) + break; + default: + errorPayload(reply, "invalid mode"); + } +} + /// /// /// diff --git a/src/network/RESTAPI.h b/src/network/RESTAPI.h index 012e7e83..ffda3984 100644 --- a/src/network/RESTAPI.h +++ b/src/network/RESTAPI.h @@ -32,7 +32,7 @@ #include "network/rest/RequestDispatcher.h" #include "network/rest/http/HTTPServer.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" #include "Thread.h" #include @@ -61,7 +61,7 @@ public: ~RESTAPI(); /// Sets the instances of the Radio ID and Talkgroup ID lookup tables. - void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup); + void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup); /// Sets the instances of the digital radio protocols. void setProtocols(dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn); @@ -91,7 +91,7 @@ private: nxdn::Control* m_nxdn; ::lookups::RadioIdLookup* m_ridLookup; - ::lookups::TalkgroupIdLookup* m_tidLookup; + ::lookups::TalkgroupRulesLookup* m_tidLookup; typedef std::unordered_map::value_type AuthTokenValueType; std::unordered_map m_authTokens; @@ -133,6 +133,11 @@ private: /// void restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /// + void restAPI_PutReleaseGrant(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /// + void restAPI_PutTouchGrant(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + /// void restAPI_GetRIDWhitelist(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); /// diff --git a/src/network/RESTDefines.h b/src/network/RESTDefines.h index 20591f4e..2e3e9349 100644 --- a/src/network/RESTDefines.h +++ b/src/network/RESTDefines.h @@ -64,6 +64,9 @@ #define GET_RELEASE_GRNTS "/release-grants" #define GET_RELEASE_AFFS "/release-affs" +#define PUT_RELEASE_TG "/release-tg-grant" +#define PUT_TOUCH_TG "/touch-tg-grant" + #define GET_RID_WHITELIST_BASE "/rid-whitelist/" #define GET_RID_WHITELIST GET_RID_WHITELIST_BASE"(\\d+)" #define GET_RID_BLACKLIST_BASE "/rid-blacklist/" diff --git a/src/network/RTPExtensionHeader.cpp b/src/network/RTPExtensionHeader.cpp new file mode 100644 index 00000000..1f1dbfde --- /dev/null +++ b/src/network/RTPExtensionHeader.cpp @@ -0,0 +1,80 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "network/RTPExtensionHeader.h" + +using namespace network::frame; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the RTPExtensionHeader class. +/// +RTPExtensionHeader::RTPExtensionHeader() : + m_payloadType(0U), + m_payloadLength(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the RTPExtensionHeader class. +/// +RTPExtensionHeader::~RTPExtensionHeader() +{ + /* stub */ +} + +/// +/// Decode a RTP header. +/// +/// +bool RTPExtensionHeader::decode(const uint8_t* data) +{ + assert(data != nullptr); + + m_payloadType = (data[0U] << 8) | (data[1U] << 0); // Payload Type + m_payloadLength = (data[2U] << 8) | (data[3U] << 0); // Payload Length + + return true; +} + +/// +/// Encode a RTP header. +/// +/// +void RTPExtensionHeader::encode(uint8_t* data) +{ + assert(data != nullptr); + + data[0U] = (m_payloadType >> 8) & 0xFFU; // Payload Type MSB + data[1U] = (m_payloadType >> 0) & 0xFFU; // Payload Type LSB + data[2U] = (m_payloadLength >> 8) & 0xFFU; // Payload Length MSB + data[3U] = (m_payloadLength >> 0) & 0xFFU; // Payload Length LSB +} diff --git a/src/network/RTPExtensionHeader.h b/src/network/RTPExtensionHeader.h new file mode 100644 index 00000000..e681153c --- /dev/null +++ b/src/network/RTPExtensionHeader.h @@ -0,0 +1,71 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__RTP_EXTENSION_HEADER_H__) +#define __RTP_EXTENSION_HEADER_H__ + +#include "Defines.h" + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define RTP_EXTENSION_HEADER_LENGTH_BYTES 4 + +namespace network +{ + namespace frame + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an RTP Extension header. + // --------------------------------------------------------------------------- + + class RTPExtensionHeader { + public: + /// Initializes a new instance of the RTPExtensionHeader class. + RTPExtensionHeader(); + /// Finalizes a instance of the RTPExtensionHeader class. + ~RTPExtensionHeader(); + + /// Decode a RTP header. + virtual bool decode(const uint8_t* data); + /// Encode a RTP header. + virtual void encode(uint8_t* data); + + public: + /// Format of the extension header payload contained within the packet. + __PROTECTED_PROPERTY(uint16_t, payloadType, PayloadType); + /// Length of the extension header payload (in 32-bit units). + __PROTECTED_READONLY_PROPERTY(uint16_t, payloadLength, PayloadLength); + }; + } // namespace frame +} // namespace network + +#endif // __RTP_EXTENSION_HEADER_H__ \ No newline at end of file diff --git a/src/network/RTPFNEHeader.cpp b/src/network/RTPFNEHeader.cpp new file mode 100644 index 00000000..9ad134c8 --- /dev/null +++ b/src/network/RTPFNEHeader.cpp @@ -0,0 +1,107 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "modem/Modem.h" +#include "network/RTPFNEHeader.h" + +using namespace network::frame; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the RTPFNEHeader class. +/// +RTPFNEHeader::RTPFNEHeader() : + RTPExtensionHeader(), + m_crc16(0U), + m_func(0U), + m_subFunc(0U), + m_streamId(0U), + m_peerId(0U), + m_messageLength(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the RTPExtensionHeader class. +/// +RTPFNEHeader::~RTPFNEHeader() +{ + /* stub */ +} + +/// +/// Decode a RTP header. +/// +/// +bool RTPFNEHeader::decode(const uint8_t* data) +{ + assert(data != nullptr); + + RTPExtensionHeader::decode(data); + if (m_payloadLength != RTP_FNE_HEADER_LENGTH_EXT_LEN) { + return false; + } + + if (m_payloadType != modem::DVM_FRAME_START) { + return false; + } + + m_crc16 = (data[4U] << 8) | (data[5U] << 0); // CRC-16 + m_func = data[6U]; // Function + m_subFunc = data[7U]; // Sub-Function + m_streamId = __GET_UINT32(data, 8U); // Stream ID + m_peerId = __GET_UINT32(data, 12U); // Peer ID + m_messageLength = __GET_UINT32(data, 16U); // Message Length + + return true; +} + +/// +/// Encode a RTP header. +/// +/// +void RTPFNEHeader::encode(uint8_t* data) +{ + assert(data != nullptr); + + m_payloadType = modem::DVM_FRAME_START; + m_payloadLength = RTP_FNE_HEADER_LENGTH_EXT_LEN; + RTPExtensionHeader::encode(data); + + data[4U] = (m_crc16 >> 8) & 0xFFU; // CRC-16 MSB + data[5U] = (m_crc16 >> 0) & 0xFFU; // CRC-16 LSB + data[6U] = m_func; // Function + data[7U] = m_subFunc; // Sub-Function + + __SET_UINT32(m_streamId, data, 8U); // Stream ID + __SET_UINT32(m_peerId, data, 12U); // Peer ID + __SET_UINT32(m_messageLength, data, 16U); // Message Length +} diff --git a/src/network/RTPFNEHeader.h b/src/network/RTPFNEHeader.h new file mode 100644 index 00000000..9f7547ab --- /dev/null +++ b/src/network/RTPFNEHeader.h @@ -0,0 +1,81 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__RTP_FNE_HEADER_H__) +#define __RTP_FNE_HEADER_H__ + +#include "Defines.h" +#include "network/RTPExtensionHeader.h" + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define RTP_FNE_HEADER_LENGTH_BYTES 16 +#define RTP_FNE_HEADER_LENGTH_EXT_LEN 4 + +namespace network +{ + namespace frame + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents the FNE RTP Extension header. + // --------------------------------------------------------------------------- + + class RTPFNEHeader : public RTPExtensionHeader { + public: + /// Initializes a new instance of the RTPFNEHeader class. + RTPFNEHeader(); + /// Finalizes a instance of the RTPFNEHeader class. + ~RTPFNEHeader(); + + /// Decode a RTP header. + virtual bool decode(const uint8_t* data); + /// Encode a RTP header. + virtual void encode(uint8_t* data); + + public: + /// Traffic payload packet CRC-16. + __PROPERTY(uint16_t, crc16, CRC); + /// Function. + __PROPERTY(uint8_t, func, Function); + /// Sub-function. + __PROPERTY(uint8_t, subFunc, SubFunction); + /// Traffic Stream ID. + __PROPERTY(uint32_t, streamId, StreamId); + /// Traffic Peer ID. + __PROPERTY(uint32_t, peerId, PeerId); + /// Traffic Message Length. + __PROPERTY(uint32_t, messageLength, MessageLength); + }; + } // namespace frame +} // namespace network + +#endif // __RTP_FNE_HEADER_H__ \ No newline at end of file diff --git a/src/network/RTPHeader.cpp b/src/network/RTPHeader.cpp new file mode 100644 index 00000000..bc7fda86 --- /dev/null +++ b/src/network/RTPHeader.cpp @@ -0,0 +1,121 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "network/RTPHeader.h" +#include "Clock.h" + +using namespace system_clock; +using namespace network::frame; + +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +hrc::hrc_t RTPHeader::m_wcStart = hrc::hrc_t(); + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the RTPHeader class. +/// +RTPHeader::RTPHeader() : + m_version(2U), + m_padding(false), + m_extension(false), + m_cc(0U), + m_marker(false), + m_payloadType(0U), + m_seq(0U), + m_timestamp(INVALID_TS), + m_ssrc(0U) +{ + std::random_device rd; + std::mt19937 mt(rd()); + m_random = mt; +} + +/// +/// Finalizes a instance of the RTPHeader class. +/// +RTPHeader::~RTPHeader() +{ + /* stub */ +} + +/// +/// Decode a RTP header. +/// +/// +bool RTPHeader::decode(const uint8_t* data) +{ + assert(data != nullptr); + + // check for invalid version + if (((data[0U] >> 6) & 0x03) != 0x02U) { + return false; + } + + m_version = (data[0U] >> 6) & 0x03U; // RTP Version + m_padding = ((data[0U] & 0x20U) == 0x20U); // Padding Flag + m_extension = ((data[0U] & 0x10U) == 0x10U); // Extension Header Flag + m_cc = (data[0U] & 0x0FU); // CSRC Count + m_marker = ((data[1U] & 0x80U) == 0x80U); // Marker Flag + m_payloadType = (data[1U] & 0x7FU); // Payload Type + m_seq = (data[2U] << 8) | (data[3U] << 0); // Sequence + + m_timestamp = __GET_UINT32(data, 4U); // Timestamp + m_ssrc = __GET_UINT32(data, 8U); // Synchronization Source ID + + return true; +} + +/// +/// Encode a RTP header. +/// +/// +void RTPHeader::encode(uint8_t* data) +{ + assert(data != nullptr); + + data[0U] = (m_version << 6) + // RTP Version + (m_padding ? 0x20U : 0x00U) + // Padding Flag + (m_extension ? 0x10U : 0x00U) + // Extension Header Flag + (m_cc & 0x0FU); // CSRC Count + data[1U] = (m_marker ? 0x80U : 0x00U) + // Marker Flag + (m_payloadType & 0x7FU); // Payload Type + data[2U] = (m_seq >> 8) & 0xFFU; // Sequence MSB + data[3U] = (m_seq >> 0) & 0xFFU; // Sequence LSB + + uint64_t timeSinceStart = hrc::diffNow(m_wcStart); + uint64_t microSeconds = timeSinceStart * RTP_GENERIC_CLOCK_RATE; + m_timestamp = uint32_t(microSeconds / 1000000); + + __SET_UINT32(m_timestamp, data, 4U); // Timestamp + __SET_UINT32(m_ssrc, data, 8U); // Synchronization Source Identifier +} diff --git a/src/network/RTPHeader.h b/src/network/RTPHeader.h new file mode 100644 index 00000000..ddf7c706 --- /dev/null +++ b/src/network/RTPHeader.h @@ -0,0 +1,93 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__RTP_HEADER_H__) +#define __RTP_HEADER_H__ + +#include "Defines.h" + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define INVALID_TS UINT32_MAX + +#define RTP_HEADER_LENGTH_BYTES 12 +#define RTP_GENERIC_CLOCK_RATE 8000 + +namespace network +{ + namespace frame + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an RTP header. + // --------------------------------------------------------------------------- + + class RTPHeader { + public: + /// Initializes a new instance of the RTPHeader class. + RTPHeader(); + /// Finalizes a instance of the RTPHeader class. + ~RTPHeader(); + + /// Decode a RTP header. + virtual bool decode(const uint8_t* data); + /// Encode a RTP header. + virtual void encode(uint8_t* data); + + public: + /// RTP Protocol Version. + __READONLY_PROPERTY(uint8_t, version, Version); + /// Flag indicating if the packet has trailing padding. + __READONLY_PROPERTY(bool, padding, Padding); + /// Flag indicating the presense of an extension header. + __PROPERTY(bool, extension, Extension); + /// Count of contributing source IDs that follow the SSRC. + __READONLY_PROPERTY(uint8_t, cc, CSRCCount); + /// Flag indicating application-specific behavior. + __PROPERTY(bool, marker, Marker); + /// Format of the payload contained within the packet. + __PROPERTY(uint8_t, payloadType, PayloadType); + /// Sequence number for the RTP packet. + __PROPERTY(uint16_t, seq, Sequence); + /// RTP packet timestamp. + __READONLY_PROPERTY(uint32_t, timestamp, Timestamp); + /// Synchronization Source ID. + __PROPERTY(uint32_t, ssrc, SSRC); + + private: + static std::chrono::time_point m_wcStart; + + std::mt19937 m_random; + }; + } // namespace frame +} // namespace network + +#endif // __RTP_HEADER_H__ \ No newline at end of file diff --git a/src/network/UDPSocket.cpp b/src/network/UDPSocket.cpp index 37c7054b..df743d15 100644 --- a/src/network/UDPSocket.cpp +++ b/src/network/UDPSocket.cpp @@ -31,21 +31,19 @@ #include "Defines.h" #include "network/UDPSocket.h" #include "Log.h" +#include "Utils.h" -#include +using namespace network; -#if !defined(_WIN32) && !defined(_WIN64) +#include #include #include -#endif - -using namespace network; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- -#define MAX_BUFFER_COUNT 256 +#define MAX_BUFFER_COUNT 16384 // --------------------------------------------------------------------------- // Public Class Members @@ -58,6 +56,7 @@ using namespace network; UDPSocket::UDPSocket(const std::string& address, uint16_t port) : m_address_save(address), m_port_save(port), + m_isOpen(false), m_counter(0U) { for (int i = 0; i < UDP_SOCKET_MAX; i++) { @@ -75,6 +74,7 @@ UDPSocket::UDPSocket(const std::string& address, uint16_t port) : UDPSocket::UDPSocket(uint16_t port) : m_address_save(), m_port_save(port), + m_isOpen(false), m_counter(0U) { for (int i = 0; i < UDP_SOCKET_MAX; i++) { @@ -135,6 +135,7 @@ bool UDPSocket::open(const uint32_t index, const uint32_t af, const std::string& int err = lookup(address, port, addr, addrlen, hints); if (err != 0) { LogError(LOG_NET, "The local address is invalid - %s", address.c_str()); + m_isOpen = false; return false; } @@ -142,11 +143,8 @@ bool UDPSocket::open(const uint32_t index, const uint32_t af, const std::string& int fd = ::socket(addr.ss_family, SOCK_DGRAM, 0); if (fd < 0) { -#if defined(_WIN32) || defined(_WIN64) - LogError(LOG_NET, "Cannot create the UDP socket, err: %lu", ::GetLastError()); -#else LogError(LOG_NET, "Cannot create the UDP socket, err: %d", errno); -#endif + m_isOpen = false; return false; } @@ -158,26 +156,21 @@ bool UDPSocket::open(const uint32_t index, const uint32_t af, const std::string& if (port > 0U) { int reuse = 1; if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)& reuse, sizeof(reuse)) == -1) { -#if defined(_WIN32) || defined(_WIN64) - LogError(LOG_NET, "Cannot set the UDP socket option, err: %lu", ::GetLastError()); -#else LogError(LOG_NET, "Cannot set the UDP socket option, err: %d", errno); -#endif + m_isOpen = false; return false; } if (::bind(fd, (sockaddr*)& addr, addrlen) == -1) { -#if defined(_WIN32) || defined(_WIN64) - LogError(LOG_NET, "Cannot bind the UDP address, err: %lu", ::GetLastError()); -#else LogError(LOG_NET, "Cannot bind the UDP address, err: %d", errno); -#endif + m_isOpen = false; return false; } LogInfoEx(LOG_NET, "Opening UDP port on %u", port); } + m_isOpen = true; return true; } @@ -186,7 +179,7 @@ bool UDPSocket::open(const uint32_t index, const uint32_t af, const std::string& /// /// Buffer to read data into. /// Length of data to read. -/// IP address to read data from. +/// IP address data read from. /// /// Actual length of data read from remote UDP socket. int UDPSocket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address, uint32_t& addrLen) @@ -210,17 +203,9 @@ int UDPSocket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address, return 0; // Return immediately -#if defined(_WIN32) || defined(_WIN64) - int ret = WSAPoll(pfd, n, 0); -#else int ret = ::poll(pfd, n, 0); -#endif if (ret < 0) { -#if defined(_WIN32) || defined(_WIN64) - LogError(LOG_NET, "Error returned from UDP poll, err: %lu", ::GetLastError()); -#else LogError(LOG_NET, "Error returned from UDP poll, err: %d", errno); -#endif return -1; } @@ -234,21 +219,9 @@ int UDPSocket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address, if (i == n) return 0; -#if defined(_WIN32) || defined(_WIN64) - int size = sizeof(sockaddr_storage); -#else socklen_t size = sizeof(sockaddr_storage); -#endif - -#if defined(_WIN32) || defined(_WIN64) - int len = ::recvfrom(pfd[index].fd, (char*)buffer, length, 0, (sockaddr*)& address, &size); -#else ssize_t len = ::recvfrom(pfd[index].fd, (char*)buffer, length, 0, (sockaddr*)& address, &size); -#endif if (len <= 0) { -#if defined(_WIN32) || defined(_WIN64) - LogError(LOG_NET, "Error returned from recvfrom, err: %lu", ::GetLastError()); -#else LogError(LOG_NET, "Error returned from recvfrom, err: %d", errno); if (len == -1 && errno == ENOTSOCK) { @@ -256,7 +229,7 @@ int UDPSocket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address, close(); open(); } -#endif + return -1; } @@ -273,7 +246,7 @@ int UDPSocket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address, /// IP address to write data to. /// /// Total number of bytes written. -/// Actual length of data written to remote UDP socket. +/// True if message was sent, otherwise false. bool UDPSocket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storage& address, uint32_t addrLen, int* lenWritten) { assert(buffer != nullptr); @@ -285,30 +258,18 @@ bool UDPSocket::write(const uint8_t* buffer, uint32_t length, const sockaddr_sto if (m_fd[i] < 0 || m_af[i] != address.ss_family) continue; -#if defined(_WIN32) || defined(_WIN64) - int sent = ::sendto(m_fd[i], (char*)buffer, length, 0, (sockaddr*)& address, addrLen); -#else ssize_t sent = ::sendto(m_fd[i], (char*)buffer, length, 0, (sockaddr*)& address, addrLen); -#endif - if (sent < 0) { -#if defined(_WIN32) || defined(_WIN64) - LogError(LOG_NET, "Error returned from sendto, err: %lu", ::GetLastError()); -#else LogError(LOG_NET, "Error returned from sendto, err: %d", errno); -#endif + if (lenWritten != nullptr) { *lenWritten = -1; } } else { -#if defined(_WIN32) || defined(_WIN64) - if (sent == int(length)) - result = true; -#else if (sent == ssize_t(length)) result = true; -#endif + if (lenWritten != nullptr) { *lenWritten = sent; } @@ -325,70 +286,76 @@ bool UDPSocket::write(const uint8_t* buffer, uint32_t length, const sockaddr_sto /// IP address to write data to. /// /// Total number of bytes written. -/// Actual length of data written to remote UDP socket. -bool UDPSocket::write(BufferVector& buffers, const sockaddr_storage& address, uint32_t addrLen, int* lenWritten) +/// True if messages were sent, otherwise false. +bool UDPSocket::write(BufferVector& buffers, int* lenWritten) { bool result = false; + if (buffers.empty()) { + return false; + } + + // bryanb: this is the same as above -- but for some assinine reason prevents + // weirdness + if (buffers.size() == 0U) { + return false; + } -#if defined(_WIN32) || defined(_WIN64) - DWORD sent = 0; + // LogDebug(LOG_NET, "buffers len = %u", buffers.size()); if (buffers.size() > UINT16_MAX) { - // LOG_ERROR("Trying to send too large buffer"); + LogError(LOG_NET, "Trying to send too many buffers?"); return false; } -#else - int sent = 0; -#endif -#if defined(_WIN32) || defined(_WIN64) - WSABUF wsaBuffers[MAX_BUFFER_COUNT]; + // LogDebug(LOG_NET, "Sending message(s) (to %s:%u) addrLen %u", UDPSocket::address(address).c_str(), UDPSocket::port(address), addrLen); - // create WSABUFs from input buffers and send them at once - for (size_t i = 0; i < buffers.size(); ++i) { - wsaBuffers[i].len = (ULONG)buffers.at(i).first; - wsaBuffers[i].buf = (char*)buffers.at(i).second; - } -#else - struct mmsghdr header; + int sent = 0; + struct mmsghdr headers[MAX_BUFFER_COUNT]; struct iovec chunks[MAX_BUFFER_COUNT]; + + // create mmsghdrs from input buffers and send them at once + int size = buffers.size(); for (size_t i = 0; i < buffers.size(); ++i) { - chunks[i].iov_len = buffers.at(i).first; - chunks[i].iov_base = buffers.at(i).second; - sent += buffers.at(i).first; - } + if (buffers.at(i) == nullptr) { + --size; + continue; + } + + chunks[i].iov_len = buffers.at(i)->length; + chunks[i].iov_base = buffers.at(i)->buffer; + sent += buffers.at(i)->length; - header.msg_hdr.msg_name = (void *)&address; - header.msg_hdr.msg_namelen = addrLen; - header.msg_hdr.msg_iov = chunks; - header.msg_hdr.msg_iovlen = buffers.size(); - header.msg_hdr.msg_control = 0; - header.msg_hdr.msg_controllen = 0; -#endif + headers[i].msg_hdr.msg_name = (void*)&buffers.at(i)->address; + headers[i].msg_hdr.msg_namelen = buffers.at(i)->addrLen; + headers[i].msg_hdr.msg_iov = &chunks[i]; + headers[i].msg_hdr.msg_iovlen = 1; + headers[i].msg_hdr.msg_control = 0; + headers[i].msg_hdr.msg_controllen = 0; + } for (int i = 0; i < UDP_SOCKET_MAX; i++) { - if (m_fd[i] < 0 || m_af[i] != address.ss_family) + if (m_fd[i] < 0) continue; -#if defined(_WIN32) || defined(_WIN64) - int success = WSASendTo(m_fd[i], wsaBuffers, (DWORD)wsaBuffers.size(), &sent, 0, - (SOCKADDR *)&address, addrLen, nullptr, nullptr); - if (success != 0) { - LogError(LOG_NET, "Error returned from sendto, err: %lu", ::GetLastError()); - if (lenWritten != nullptr) { - *lenWritten = -1; + bool skip = false; + for (auto& buffer : buffers) { + if (m_af[i] != buffer->address.ss_family) { + skip = true; + break; } } -#else - if (sendmmsg(m_fd[i], &header, 1, 0) < 0) { + if (skip) + continue; + + if (sendmmsg(m_fd[i], headers, size, 0) < 0) { LogError(LOG_NET, "Error returned from sendmmsg, err: %d", errno); if (lenWritten != nullptr) { *lenWritten = -1; } } -#endif if (sent < 0) { + LogError(LOG_NET, "Error returned from sendmmsg, err: %d", errno); if (lenWritten != nullptr) { *lenWritten = -1; } @@ -411,6 +378,7 @@ void UDPSocket::close() { for (int i = 0; i < UDP_SOCKET_MAX; i++) close(i); + m_isOpen = false; } /// @@ -420,39 +388,11 @@ void UDPSocket::close() void UDPSocket::close(const uint32_t index) { if ((index < UDP_SOCKET_MAX) && (m_fd[index] >= 0)) { -#if defined(_WIN32) || defined(_WIN64) - ::closesocket(m_fd[index]); -#else ::close(m_fd[index]); -#endif m_fd[index] = -1; } } -/// -/// -/// -void UDPSocket::startup() -{ -#if defined(_WIN32) || defined(_WIN64) - WSAData data; - int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); - if (wsaRet != 0) { - LogError(LOG_NET, "Error from WSAStartup"); - } -#endif -} - -/// -/// -/// -void UDPSocket::shutdown() -{ -#if defined(_WIN32) || defined(_WIN64) - ::WSACleanup(); -#endif -} - /// /// Helper to lookup a hostname and resolve it to an IP address. /// diff --git a/src/network/UDPSocket.h b/src/network/UDPSocket.h index df57a012..87edd6ee 100644 --- a/src/network/UDPSocket.h +++ b/src/network/UDPSocket.h @@ -36,7 +36,6 @@ #include #include -#if !defined(_WIN32) && !defined(_WIN64) #include #include #include @@ -46,10 +45,6 @@ #include #include #include -#else -#include -#include -#endif #if !defined(UDP_SOCKET_MAX) #define UDP_SOCKET_MAX 1 @@ -85,8 +80,22 @@ namespace network } #endif + // --------------------------------------------------------------------------- + // Structure Declaration + // This structure represents a container for a network buffer. + // --------------------------------------------------------------------------- + + struct UDPDatagram { + uint8_t* buffer; + size_t length; + + sockaddr_storage address; + uint32_t addrLen; + + }; + /* Vector of buffers that contain a full frames */ - typedef std::vector> BufferVector; + typedef std::vector BufferVector; // --------------------------------------------------------------------------- // Class Declaration @@ -115,17 +124,15 @@ namespace network /// Write data to the UDP socket. bool write(const uint8_t* buffer, uint32_t length, const sockaddr_storage& address, uint32_t addrLen, int* lenWritten = nullptr); /// Write data to the UDP socket. - bool write(BufferVector& buffers, const sockaddr_storage& address, uint32_t addrLen, int* lenWritten = nullptr); + bool write(BufferVector& buffers, int* lenWritten = nullptr); /// Closes the UDP socket connection. void close(); /// Closes the UDP socket connection. void close(const uint32_t index); - /// - static void startup(); - /// - static void shutdown(); + /// Flag indicating the UDP socket(s) are open. + bool isOpen() const { return m_isOpen; } /// Helper to lookup a hostname and resolve it to an IP address. static int lookup(const std::string& hostName, uint16_t port, sockaddr_storage& address, uint32_t& addrLen); @@ -148,6 +155,8 @@ namespace network std::string m_address[UDP_SOCKET_MAX]; uint16_t m_port[UDP_SOCKET_MAX]; + bool m_isOpen; + uint32_t m_af[UDP_SOCKET_MAX]; int m_fd[UDP_SOCKET_MAX]; diff --git a/src/network/fne/TagDMRData.cpp b/src/network/fne/TagDMRData.cpp new file mode 100644 index 00000000..67d534b1 --- /dev/null +++ b/src/network/fne/TagDMRData.cpp @@ -0,0 +1,347 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "network/FNENetwork.h" +#include "network/fne/TagDMRData.h" +#include "Clock.h" +#include "Log.h" +#include "StopWatch.h" +#include "Utils.h" + +using namespace system_clock; +using namespace network; +using namespace network::fne; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the TagDMRData class. +/// +/// +/// +TagDMRData::TagDMRData(FNENetwork* network, bool debug) : + m_network(network), + m_parrotFrames(), + m_parrotFramesReady(false), + m_status(), + m_debug(debug) +{ + assert(network != nullptr); +} + +/// +/// Finalizes a instance of the TagDMRData class. +/// +TagDMRData::~TagDMRData() +{ + /* stub */ +} + +/// +/// Process a data frame from the network. +/// +/// Network data buffer. +/// Length of data. +/// Peer ID +/// +/// Stream ID +/// +bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +{ + hrc::hrc_t pktTime = hrc::now(); + + uint8_t seqNo = data[4U]; + + uint32_t srcId = __GET_UINT16(data, 5U); + uint32_t dstId = __GET_UINT16(data, 8U); + + uint8_t flco = (data[15U] & 0x40U) == 0x40U ? dmr::FLCO_PRIVATE : dmr::FLCO_GROUP; + + uint32_t slotNo = (data[15U] & 0x80U) == 0x80U ? 2U : 1U; + + uint8_t dataType = data[15U] & 0x0FU; + + dmr::data::Data dmrData; + dmrData.setSeqNo(seqNo); + dmrData.setSlotNo(slotNo); + dmrData.setSrcId(srcId); + dmrData.setDstId(dstId); + dmrData.setFLCO(flco); + + bool dataSync = (data[15U] & 0x20U) == 0x20U; + bool voiceSync = (data[15U] & 0x10U) == 0x10U; + + if (dataSync) { + dmrData.setData(data + 20U); + dmrData.setDataType(dataType); + dmrData.setN(0U); + } + else if (voiceSync) { + dmrData.setData(data + 20U); + dmrData.setDataType(dmr::DT_VOICE_SYNC); + dmrData.setN(0U); + } + else { + uint8_t n = data[15U] & 0x0FU; + dmrData.setData(data + 20U); + dmrData.setDataType(dmr::DT_VOICE); + dmrData.setN(n); + } + + // is the stream valid? + if (validate(peerId, dmrData, streamId)) { + // is this peer ignored? + if (!isPeerPermitted(peerId, dmrData, streamId)) { + return false; + } + + // is this the end of the call stream? + if (dataSync && (dataType == dmr::DT_TERMINATOR_WITH_LC)) { + RxStatus status; + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); + if (it == m_status.end()) { + LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, srcId = %u, dstId = %u, streamId = %u", + peerId, srcId, dstId, streamId); + } + else { + status = it->second; + } + + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }) != m_status.end()) { + m_status.erase(dstId); + } + + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + if (m_parrotFrames.size() > 0) { + m_parrotFramesReady = true; + Thread::sleep(m_network->m_parrotDelay); + LogMessage(LOG_NET, "DMR, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); + } + } + + LogMessage(LOG_NET, "DMR, Call End, peer = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u", + peerId, srcId, dstId, duration / 1000, streamId); + } + + // is this a new call stream? + if (dataSync && (dataType == dmr::DT_VOICE_LC_HEADER)) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); + if (it != m_status.end()) { + RxStatus status = it->second; + if (streamId != status.streamId) { + if (status.srcId != 0U && status.srcId != srcId) { + LogWarning(LOG_NET, "DMR, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + } + } + else { + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + m_parrotFramesReady = false; + if (m_parrotFrames.size() > 0) { + for (auto& pkt : m_parrotFrames) { + if (std::get<0>(pkt) != nullptr) { + delete std::get<0>(pkt); + } + } + m_parrotFrames.clear(); + } + } + + // this is a new call stream + RxStatus status = RxStatus(); + status.callStartTime = pktTime; + status.srcId = srcId; + status.dstId = dstId; + status.slotNo = slotNo; + status.streamId = streamId; + m_status[dstId] = status; // this *could* be an issue if a dstId appears on both slots somehow... + LogMessage(LOG_NET, "DMR, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + } + } + + // is this a parrot talkgroup? + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + uint8_t* copy = new uint8_t[len]; + ::memcpy(copy, data, len); + m_parrotFrames.push_back(std::make_tuple(copy, len, pktSeq)); + } + + // repeat traffic to the connected peers + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + // is this peer ignored? + if (!isPeerPermitted(peer.first, dmrData, streamId)) { + continue; + } + + m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, true); + if (m_network->m_verbose) { + LogDebug(LOG_NET, "DMR, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u", + peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId); + } + } + } + + m_network->m_frameQueue->flushQueue(); + return true; + } + + return false; +} + +/// +/// Helper to playback a parrot frame to the network. +/// +void TagDMRData::playbackParrot() +{ + if (m_parrotFrames.size() == 0) { + m_parrotFramesReady = false; + return; + } + + auto& pkt = m_parrotFrames[0]; + if (std::get<0>(pkt) != nullptr) { + m_network->writePeers({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_DMR }, std::get<0>(pkt), std::get<1>(pkt), std::get<2>(pkt)); + delete std::get<0>(pkt); + Thread::sleep(60); + } + m_parrotFrames.pop_front(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Helper to determine if the peer is permitted for traffic. +/// +/// Peer ID +/// +/// Stream ID +/// +bool TagDMRData::isPeerPermitted(uint32_t peerId, dmr::data::Data& data, uint32_t streamId) +{ + // private calls are always permitted + if (data.getDataType() == dmr::FLCO_PRIVATE) { + return true; + } + + // is this a group call? + if (data.getDataType() == dmr::FLCO_GROUP) { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); + + std::vector inclusion = tg.config().inclusion(); + std::vector exclusion = tg.config().exclusion(); + + // peer inclusion lists take priority over exclusion lists + if (inclusion.size() > 0) { + auto it = std::find(inclusion.begin(), inclusion.end(), peerId); + if (it == inclusion.end()) { + return false; + } + } + else { + if (exclusion.size() > 0) { + auto it = std::find(exclusion.begin(), exclusion.end(), peerId); + if (it != inclusion.end()) { + return false; + } + } + } + + // TODO TODO TODO + // TODO: handle checking group affiliations if affiliation option is enabled + } + + return true; +} + +/// +/// Helper to validate the DMR call stream. +/// +/// Peer ID +/// +/// Stream ID +/// +bool TagDMRData::validate(uint32_t peerId, dmr::data::Data& data, uint32_t streamId) +{ + // is the source ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(data.getSrcId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + return false; + } + } + + // always validate a terminator if the source is valid + if (data.getDataType() == dmr::DT_TERMINATOR_WITH_LC) + return true; + + // is this a private call? + if (data.getDataType() == dmr::FLCO_PRIVATE) { + // is the destination ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(data.getDstId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + return false; + } + } + } + + // is this a group call? + if (data.getDataType() == dmr::FLCO_GROUP) { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); + + // check the DMR slot number + if (tg.source().tgSlot() != data.getSlotNo()) { + return false; + } + + if (!tg.config().active()) { + return false; + } + + // TODO TODO TODO + // TODO: handle checking group affiliations if affiliation option is enabled + } + + return true; +} \ No newline at end of file diff --git a/src/network/fne/TagDMRData.h b/src/network/fne/TagDMRData.h new file mode 100644 index 00000000..b2898f00 --- /dev/null +++ b/src/network/fne/TagDMRData.h @@ -0,0 +1,88 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__FNE__TAG_DMR_DATA_H__) +#define __FNE__TAG_DMR_DATA_H__ + +#include "Defines.h" +#include "Clock.h" +#include "dmr/DMRDefines.h" +#include "dmr/data/Data.h" +#include "network/FNENetwork.h" + +#include + +namespace network +{ + namespace fne + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the DMR data FNE networking logic. + // --------------------------------------------------------------------------- + + class HOST_SW_API TagDMRData { + public: + /// Initializes a new instance of the TagDMRData class. + TagDMRData(FNENetwork* network, bool debug); + /// Finalizes a instance of the TagDMRData class. + ~TagDMRData(); + + /// Process a data frame from the network. + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + + /// Helper to playback a parrot frame to the network. + void playbackParrot(); + /// Helper to determine if there are stored parrot frames. + bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + + private: + FNENetwork* m_network; + + std::deque> m_parrotFrames; + bool m_parrotFramesReady; + + class RxStatus { + public: + system_clock::hrc::hrc_t callStartTime; + uint32_t srcId; + uint32_t dstId; + uint8_t slotNo; + uint32_t streamId; + }; + typedef std::pair StatusMapPair; + std::unordered_map m_status; + + bool m_debug; + + /// Helper to determine if the peer is permitted for traffic. + bool isPeerPermitted(uint32_t peerId, dmr::data::Data& data, uint32_t streamId); + /// Helper to validate the DMR call stream. + bool validate(uint32_t peerId, dmr::data::Data& data, uint32_t streamId); + }; + } // namespace fne +} // namespace network + +#endif // __FNE__TAG_DMR_DATA_H__ diff --git a/src/network/fne/TagNXDNData.cpp b/src/network/fne/TagNXDNData.cpp new file mode 100644 index 00000000..4722a6d7 --- /dev/null +++ b/src/network/fne/TagNXDNData.cpp @@ -0,0 +1,314 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "nxdn/NXDNDefines.h" +#include "network/FNENetwork.h" +#include "network/fne/TagNXDNData.h" +#include "Clock.h" +#include "Log.h" +#include "StopWatch.h" +#include "Utils.h" + +using namespace system_clock; +using namespace network; +using namespace network::fne; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the TagNXDNData class. +/// +/// +/// +TagNXDNData::TagNXDNData(FNENetwork* network, bool debug) : + m_network(network), + m_parrotFrames(), + m_parrotFramesReady(false), + m_status(), + m_debug(debug) +{ + assert(network != nullptr); +} + +/// +/// Finalizes a instance of the TagNXDNData class. +/// +TagNXDNData::~TagNXDNData() +{ + /* stub */ +} + +/// +/// Process a data frame from the network. +/// +/// Network data buffer. +/// Length of data. +/// Peer ID +/// +/// Stream ID +/// +bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +{ + hrc::hrc_t pktTime = hrc::now(); + + uint8_t messageType = data[4U]; + + uint32_t srcId = __GET_UINT16(data, 5U); + uint32_t dstId = __GET_UINT16(data, 8U); + + nxdn::lc::RTCH lc; + + lc.setMessageType(messageType); + lc.setSrcId((uint16_t)srcId & 0xFFFFU); + lc.setDstId((uint16_t)dstId & 0xFFFFU); + + bool group = (data[15U] & 0x40U) == 0x40U ? false : true; + lc.setGroup(group); + + // is the stream valid? + if (validate(peerId, lc, messageType, streamId)) { + // is this peer ignored? + if (!isPeerPermitted(peerId, lc, messageType, streamId)) { + return false; + } + + // specifically only check the following logic for end of call, voice or data frames + if ((messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL || messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL_EX) || + (messageType == nxdn::RTCH_MESSAGE_TYPE_VCALL || messageType == nxdn::RTCH_MESSAGE_TYPE_DCALL_HDR || + messageType == nxdn::RTCH_MESSAGE_TYPE_DCALL_DATA)) { + // is this the end of the call stream? + if (messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL || messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL_EX) { + RxStatus status = m_status[dstId]; + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) { + m_status.erase(dstId); + } + + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + if (m_parrotFrames.size() > 0) { + m_parrotFramesReady = true; + Thread::sleep(m_network->m_parrotDelay); + LogMessage(LOG_NET, "NXDN, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); + } + } + + LogMessage(LOG_NET, "NXDN, Call End, peer = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u", + peerId, srcId, dstId, duration / 1000, streamId); + } + + // is this a new call stream? + if ((messageType != nxdn::RTCH_MESSAGE_TYPE_TX_REL && messageType != nxdn::RTCH_MESSAGE_TYPE_TX_REL_EX)) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + if (it != m_status.end()) { + RxStatus status = m_status[dstId]; + if (streamId != status.streamId) { + if (status.srcId != 0U && status.srcId != srcId) { + LogWarning(LOG_NET, "NXDN, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + } + } + else { + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + m_parrotFramesReady = false; + if (m_parrotFrames.size() > 0) { + for (auto& pkt : m_parrotFrames) { + if (std::get<0>(pkt) != nullptr) { + delete std::get<0>(pkt); + } + } + m_parrotFrames.clear(); + } + } + + // this is a new call stream + RxStatus status = RxStatus(); + status.callStartTime = pktTime; + status.srcId = srcId; + status.dstId = dstId; + status.streamId = streamId; + m_status[dstId] = status; + LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + } + } + } + + // is this a parrot talkgroup? + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + uint8_t *copy = new uint8_t[len]; + ::memcpy(copy, data, len); + m_parrotFrames.push_back(std::make_tuple(copy, len, pktSeq)); + } + + // repeat traffic to the connected peers + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + // is this peer ignored? + if (!isPeerPermitted(peer.first, lc, messageType, streamId)) { + continue; + } + + m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_NXDN }, data, len, pktSeq, true); + if (m_network->m_verbose) { + LogDebug(LOG_NET, "NXDN, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u", + peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId); + } + } + } + + m_network->m_frameQueue->flushQueue(); + return true; + } + + return false; +} + +/// +/// Helper to playback a parrot frame to the network. +/// +void TagNXDNData::playbackParrot() +{ + if (m_parrotFrames.size() == 0) { + m_parrotFramesReady = false; + return; + } + + auto& pkt = m_parrotFrames[0]; + if (std::get<0>(pkt) != nullptr) { + m_network->writePeers({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_NXDN }, std::get<0>(pkt), std::get<1>(pkt), std::get<2>(pkt)); + delete std::get<0>(pkt); + Thread::sleep(60); + } + m_parrotFrames.pop_front(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Helper to determine if the peer is permitted for traffic. +/// +/// Peer ID +/// +/// +/// Stream ID +/// +bool TagNXDNData::isPeerPermitted(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId) +{ + // private calls are always permitted + if (!lc.getGroup()) { + return true; + } + + // is this a group call? + if (lc.getGroup()) { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(lc.getDstId()); + + std::vector inclusion = tg.config().inclusion(); + std::vector exclusion = tg.config().exclusion(); + + // peer inclusion lists take priority over exclusion lists + if (inclusion.size() > 0) { + auto it = std::find(inclusion.begin(), inclusion.end(), peerId); + if (it == inclusion.end()) { + return false; + } + } + else { + if (exclusion.size() > 0) { + auto it = std::find(exclusion.begin(), exclusion.end(), peerId); + if (it != inclusion.end()) { + return false; + } + } + } + + // TODO TODO TODO + // TODO: handle checking group affiliations if affiliation option is enabled + } + + return true; +} + +/// +/// Helper to validate the DMR call stream. +/// +/// Peer ID +/// +/// +/// Stream ID +/// +bool TagNXDNData::validate(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId) +{ + // is the source ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(lc.getSrcId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + return false; + } + } + + // always validate a terminator if the source is valid + if (messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL || messageType == nxdn::RTCH_MESSAGE_TYPE_TX_REL_EX) + return true; + + // is this a private call? + if (!lc.getGroup()) { + // is the destination ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(lc.getDstId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + return false; + } + } + + return true; + } + + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(lc.getDstId()); + if (!tg.config().active()) { + return false; + } + + // TODO TODO TODO + // TODO: handle checking group affiliations if affiliation option is enabled + + return true; +} \ No newline at end of file diff --git a/src/network/fne/TagNXDNData.h b/src/network/fne/TagNXDNData.h new file mode 100644 index 00000000..a07f5341 --- /dev/null +++ b/src/network/fne/TagNXDNData.h @@ -0,0 +1,87 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__FNE__TAG_NXDN_DATA_H__) +#define __FNE__TAG_NXDN_DATA_H__ + +#include "Defines.h" +#include "Clock.h" +#include "network/FNENetwork.h" +#include "nxdn/NXDNDefines.h" +#include "nxdn/lc/RTCH.h" + +#include + +namespace network +{ + namespace fne + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the NXDN data FNE networking logic. + // --------------------------------------------------------------------------- + + class HOST_SW_API TagNXDNData { + public: + /// Initializes a new instance of the TagNXDNData class. + TagNXDNData(FNENetwork* network, bool debug); + /// Finalizes a instance of the TagNXDNData class. + ~TagNXDNData(); + + /// Process a data frame from the network. + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + + /// Helper to playback a parrot frame to the network. + void playbackParrot(); + /// Helper to determine if there are stored parrot frames. + bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + + private: + FNENetwork* m_network; + + std::deque> m_parrotFrames; + bool m_parrotFramesReady; + + class RxStatus { + public: + system_clock::hrc::hrc_t callStartTime; + uint32_t srcId; + uint32_t dstId; + uint32_t streamId; + }; + typedef std::pair StatusMapPair; + std::unordered_map m_status; + + bool m_debug; + + /// Helper to determine if the peer is permitted for traffic. + bool isPeerPermitted(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId); + /// Helper to validate the NXDN call stream. + bool validate(uint32_t peerId, nxdn::lc::RTCH& control, uint8_t messageType, uint32_t streamId); + }; + } // namespace fne +} // namespace network + +#endif // __FNE__TAG_NXDN_DATA_H__ diff --git a/src/network/fne/TagP25Data.cpp b/src/network/fne/TagP25Data.cpp new file mode 100644 index 00000000..b1976aa8 --- /dev/null +++ b/src/network/fne/TagP25Data.cpp @@ -0,0 +1,363 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "network/FNENetwork.h" +#include "network/fne/TagP25Data.h" +#include "Clock.h" +#include "Log.h" +#include "StopWatch.h" +#include "Thread.h" +#include "Utils.h" + +using namespace system_clock; +using namespace network; +using namespace network::fne; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the TagP25Data class. +/// +/// +/// +TagP25Data::TagP25Data(FNENetwork* network, bool debug) : + m_network(network), + m_parrotFrames(), + m_parrotFramesReady(false), + m_status(), + m_debug(debug) +{ + assert(network != nullptr); +} + +/// +/// Finalizes a instance of the TagP25Data class. +/// +TagP25Data::~TagP25Data() +{ + /* stub */ +} + +/// +/// Process a data frame from the network. +/// +/// Network data buffer. +/// Length of data. +/// Peer ID +/// +/// Stream ID +/// +bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +{ + hrc::hrc_t pktTime = hrc::now(); + + uint8_t lco = data[4U]; + + uint32_t srcId = __GET_UINT16(data, 5U); + uint32_t dstId = __GET_UINT16(data, 8U); + + uint8_t MFId = data[15U]; + + uint8_t lsd1 = data[20U]; + uint8_t lsd2 = data[21U]; + + uint8_t duid = data[22U]; + uint8_t frameType = p25::P25_FT_DATA_UNIT; + + p25::lc::LC control; + p25::data::LowSpeedData lsd; + + // is this a LDU1, is this the first of a call? + if (duid == p25::P25_DUID_LDU1) { + frameType = data[180U]; + + if (m_debug) { + LogDebug(LOG_NET, "P25, frameType = $%02X", frameType); + } + + if (frameType == p25::P25_FT_HDU_VALID) { + uint8_t algId = data[181U]; + uint32_t kid = (data[182U] << 8) | (data[183U] << 0); + + // copy MI data + uint8_t mi[p25::P25_MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, p25::P25_MI_LENGTH_BYTES); + + for (uint8_t i = 0; i < p25::P25_MI_LENGTH_BYTES; i++) { + mi[i] = data[184U + i]; + } + + if (m_debug) { + LogDebug(LOG_NET, "P25, HDU algId = $%02X, kId = $%02X", algId, kid); + Utils::dump(1U, "P25 HDU Network MI", mi, p25::P25_MI_LENGTH_BYTES); + } + + control.setAlgId(algId); + control.setKId(kid); + control.setMI(mi); + } + } + + control.setLCO(lco); + control.setSrcId(srcId); + control.setDstId(dstId); + control.setMFId(MFId); + + lsd.setLSD1(lsd1); + lsd.setLSD2(lsd2); + + // is the stream valid? + if (validate(peerId, control, duid, streamId)) { + // is this peer ignored? + if (!isPeerPermitted(peerId, control, duid, streamId)) { + return false; + } + + // specifically only check the following logic for end of call or voice frames + if (duid != p25::P25_DUID_TSDU && duid != p25::P25_DUID_PDU) { + // is this the end of the call stream? + if ((duid == p25::P25_DUID_TDU) || (duid == p25::P25_DUID_TDULC)) { + RxStatus status = m_status[dstId]; + uint64_t duration = hrc::diff(pktTime, status.callStartTime); + + if (std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_status.end()) { + m_status.erase(dstId); + } + + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + if (m_parrotFrames.size() > 0) { + m_parrotFramesReady = true; + Thread::sleep(m_network->m_parrotDelay); + LogMessage(LOG_NET, "P25, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); + } + } + + LogMessage(LOG_NET, "P25, Call End, peer = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u", + peerId, srcId, dstId, duration / 1000, streamId); + } + + // is this a new call stream? + if ((duid != p25::P25_DUID_TDU) && (duid != p25::P25_DUID_TDULC)) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + if (it != m_status.end()) { + RxStatus status = m_status[dstId]; + if (streamId != status.streamId && ((duid != p25::P25_DUID_TDU) && (duid != p25::P25_DUID_TDULC))) { + if (status.srcId != 0U && status.srcId != srcId) { + LogWarning(LOG_NET, "P25, Call Collision, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + } + } + else { + // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + m_parrotFramesReady = false; + if (m_parrotFrames.size() > 0) { + for (auto& pkt : m_parrotFrames) { + if (std::get<0>(pkt) != nullptr) { + delete std::get<0>(pkt); + } + } + m_parrotFrames.clear(); + } + } + + // this is a new call stream + RxStatus status = RxStatus(); + status.callStartTime = pktTime; + status.srcId = srcId; + status.dstId = dstId; + status.streamId = streamId; + m_status[dstId] = status; + LogMessage(LOG_NET, "P25, Call Start, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + } + } + } + + // is this a parrot talkgroup? + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); + if (tg.config().parrot()) { + uint8_t *copy = new uint8_t[len]; + ::memcpy(copy, data, len); + m_parrotFrames.push_back(std::make_tuple(copy, len, pktSeq)); + } + + // repeat traffic to the connected peers + for (auto peer : m_network->m_peers) { + if (peerId != peer.first) { + // is this peer ignored? + if (!isPeerPermitted(peer.first, control, duid, streamId)) { + continue; + } + + m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, data, len, pktSeq, true); + if (m_network->m_verbose) { + LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u", + peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId); + } + } + } + + m_network->m_frameQueue->flushQueue(); + return true; + } + + return false; +} + +/// +/// Helper to playback a parrot frame to the network. +/// +void TagP25Data::playbackParrot() +{ + if (m_parrotFrames.size() == 0) { + m_parrotFramesReady = false; + return; + } + + auto& pkt = m_parrotFrames[0]; + if (std::get<0>(pkt) != nullptr) { + m_network->writePeers({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, std::get<0>(pkt), std::get<1>(pkt), std::get<2>(pkt)); + delete std::get<0>(pkt); + Thread::sleep(120); + } + m_parrotFrames.pop_front(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Helper to determine if the peer is permitted for traffic. +/// +/// Peer ID +/// +/// +/// Stream ID +/// +bool TagP25Data::isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId) +{ + // private calls are always permitted + if (control.getLCO() == p25::LC_PRIVATE) { + return true; + } + + // always permit a TSDU or PDU + if (duid == p25::P25_DUID_TSDU || duid == p25::P25_DUID_PDU) + return true; + + // always permit a terminator + if (duid == p25::P25_DUID_TDU || duid == p25::P25_DUID_TDULC) + return true; + + // is this a group call? + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); + + std::vector inclusion = tg.config().inclusion(); + std::vector exclusion = tg.config().exclusion(); + + // peer inclusion lists take priority over exclusion lists + if (inclusion.size() > 0) { + auto it = std::find(inclusion.begin(), inclusion.end(), peerId); + if (it == inclusion.end()) { + return false; + } + } + else { + if (exclusion.size() > 0) { + auto it = std::find(exclusion.begin(), exclusion.end(), peerId); + if (it != inclusion.end()) { + return false; + } + } + } + + // TODO TODO TODO + // TODO: handle checking group affiliations if affiliation option is enabled + + return true; +} + +/// +/// Helper to validate the DMR call stream. +/// +/// Peer ID +/// +/// +/// Stream ID +/// +bool TagP25Data::validate(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId) +{ + // is the source ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(control.getSrcId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + return false; + } + } + + // always validate a TSDU or PDU if the source is valid + if (duid == p25::P25_DUID_TSDU || duid == p25::P25_DUID_PDU) + return true; + + // always validate a terminator if the source is valid + if (duid == p25::P25_DUID_TDU || duid == p25::P25_DUID_TDULC) + return true; + + // is this a private call? + if (control.getLCO() == p25::LC_PRIVATE) { + // is the destination ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(control.getDstId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + return false; + } + } + + return true; + } + + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); + if (!tg.config().active()) { + return false; + } + + // TODO TODO TODO + // TODO: handle checking group affiliations if affiliation option is enabled + + return true; +} \ No newline at end of file diff --git a/src/network/fne/TagP25Data.h b/src/network/fne/TagP25Data.h new file mode 100644 index 00000000..14f5fc8b --- /dev/null +++ b/src/network/fne/TagP25Data.h @@ -0,0 +1,93 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__FNE__TAG_P25_DATA_H__) +#define __FNE__TAG_P25_DATA_H__ + +#include "Defines.h" +#include "Clock.h" +#include "network/FNENetwork.h" +#include "p25/P25Defines.h" +#include "p25/data/DataHeader.h" +#include "p25/data/LowSpeedData.h" +#include "p25/dfsi/DFSIDefines.h" +#include "p25/dfsi/LC.h" +#include "p25/lc/LC.h" +#include "p25/lc/TSBK.h" +#include "p25/lc/TDULC.h" + +#include + +namespace network +{ + namespace fne + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the P25 data FNE networking logic. + // --------------------------------------------------------------------------- + + class HOST_SW_API TagP25Data { + public: + /// Initializes a new instance of the TagP25Data class. + TagP25Data(FNENetwork* network, bool debug); + /// Finalizes a instance of the TagP25Data class. + ~TagP25Data(); + + /// Process a data frame from the network. + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + + /// Helper to playback a parrot frame to the network. + void playbackParrot(); + /// Helper to determine if there are stored parrot frames. + bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + + private: + FNENetwork* m_network; + + std::deque> m_parrotFrames; + bool m_parrotFramesReady; + + class RxStatus { + public: + system_clock::hrc::hrc_t callStartTime; + uint32_t srcId; + uint32_t dstId; + uint32_t streamId; + }; + typedef std::pair StatusMapPair; + std::unordered_map m_status; + + bool m_debug; + + /// Helper to determine if the peer is permitted for traffic. + bool isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId); + /// Helper to validate the P25 call stream. + bool validate(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId); + }; + } // namespace fne +} // namespace network + +#endif // __FNE__TAG_P25_DATA_H__ diff --git a/src/network/rest/http/HTTPClient.h b/src/network/rest/http/HTTPClient.h index df19cc76..3728444e 100644 --- a/src/network/rest/http/HTTPClient.h +++ b/src/network/rest/http/HTTPClient.h @@ -145,11 +145,14 @@ namespace network asio::ip::tcp::resolver resolver(m_ioContext); auto endpoints = resolver.resolve(m_address, std::to_string(m_port)); - connect(endpoints); + try { + connect(endpoints); - // the entry() call will block until all asynchronous operations - // have finished - m_ioContext.run(); + // the entry() call will block until all asynchronous operations + // have finished + m_ioContext.run(); + } + catch (std::exception&) { /* stub */ } if (m_connection != nullptr) { m_connection->stop(); diff --git a/src/nxdn/Control.cpp b/src/nxdn/Control.cpp index 59b7fe8b..515d0187 100644 --- a/src/nxdn/Control.cpp +++ b/src/nxdn/Control.cpp @@ -82,15 +82,15 @@ const uint8_t SCRAMBLER[] = { /// Instance of the BaseNetwork class. /// Flag indicating full-duplex operation. /// Instance of the RadioIdLookup class. -/// Instance of the TalkgroupIdLookup class. +/// Instance of the TalkgroupRulesLookup class. /// Instance of the IdenTableLookup class. /// Instance of the RSSIInterpolator class. /// Flag indicating whether RCCH data is dumped to the log. /// Flag indicating whether P25 debug is enabled. /// Flag indicating whether P25 verbose logging is enabled. Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t timeout, uint32_t tgHang, - modem::Modem* modem, network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, - lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, + modem::Modem* modem, network::Network* network, bool duplex, lookups::RadioIdLookup* ridLookup, + lookups::TalkgroupRulesLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, bool dumpRCCHData, bool debug, bool verbose) : m_voice(nullptr), m_data(nullptr), @@ -114,8 +114,10 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q m_ridLookup(ridLookup), m_tidLookup(tidLookup), m_affiliations("NXDN Affiliations", verbose), + m_controlChData(), m_idenEntry(), - m_queue(queueSize, "NXDN Frame"), + m_txImmQueue(queueSize, "NXDN Imm Frame"), + m_txQueue(queueSize, "NXDN Frame"), m_rfState(RS_RF_LISTENING), m_rfLastDstId(0U), m_netState(RS_NET_IDLE), @@ -190,7 +192,7 @@ void Control::reset() m_data->resetRF(); } - m_queue.clear(); + m_txQueue.clear(); m_rfMask = 0x00U; m_rfLC.reset(); @@ -209,14 +211,15 @@ void Control::reset() /// CW callsign of this host. /// Voice Channel Number list. /// Voice Channel data map. +/// Control Channel data. /// NXDN Site Code. /// NXDN System Code. /// Channel ID. /// Channel Number. /// void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector voiceChNo, - const std::unordered_map voiceChData, uint16_t siteId, uint32_t sysId, - uint8_t channelId, uint32_t channelNo, bool printOptions) + const std::unordered_map voiceChData, lookups::VoiceChData controlChData, + uint16_t siteId, uint32_t sysId, uint8_t channelId, uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; yaml::Node nxdnProtocol = conf["protocols"]["nxdn"]; @@ -276,6 +279,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw std::unordered_map chData = std::unordered_map(voiceChData); m_affiliations.setRFChData(chData); + m_controlChData = controlChData; + // set the grant release callback m_affiliations.setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { // callback REST API to clear TG permit for the granted TG on the specified voice channel @@ -361,8 +366,9 @@ bool Control::processFrame(uint8_t* data, uint32_t len) LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_voice->m_rfFrames, m_voice->m_rfBits, m_voice->m_rfUndecodableLC, m_voice->m_rfErrs, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); - if (m_control) { - m_affiliations.releaseGrant(m_rfLC.getDstId(), false); + m_affiliations.releaseGrant(m_rfLC.getDstId(), false); + if (!m_control) { + notifyCC_ReleaseGrant(m_rfLC.getDstId()); } writeEndRF(); @@ -504,12 +510,20 @@ uint32_t Control::getFrame(uint8_t* data) { assert(data != nullptr); - if (m_queue.isEmpty()) + if (m_txQueue.isEmpty() && m_txImmQueue.isEmpty()) return 0U; uint8_t len = 0U; - m_queue.getData(&len, 1U); - m_queue.getData(data, len); + + // tx immediate queue takes priority + if (!m_txImmQueue.isEmpty()) { + m_txImmQueue.getData(&len, 1U); + m_txImmQueue.getData(data, len); + } + else { + m_txQueue.getData(&len, 1U); + m_txQueue.getData(data, len); + } return len; } @@ -559,7 +573,7 @@ void Control::clock(uint32_t ms) } if (m_ccPrevRunning && !m_ccRunning) { - m_queue.clear(); + m_txQueue.clear(); m_ccPacketInterval.stop(); m_ccPrevRunning = m_ccRunning; } @@ -619,7 +633,7 @@ void Control::clock(uint32_t ms) // reset states if we're in a rejected state if (m_rfState == RS_RF_REJECTED) { - m_queue.clear(); + m_txQueue.clear(); m_voice->resetRF(); m_voice->resetNet(); @@ -649,12 +663,50 @@ void Control::permittedTG(uint32_t dstId) } if (m_verbose) { - LogDebug(LOG_NXDN, "non-authoritative TG permit, dstId = %u", dstId); + LogMessage(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId); } - + m_permittedDstId = dstId; } +/// +/// Releases a granted TG. +/// +/// +void Control::releaseGrantTG(uint32_t dstId) +{ + if (!m_control) { + return; + } + + if (m_affiliations.isGranted(dstId)) { + if (m_verbose) { + LogMessage(LOG_NXDN, "REST request, release TG grant, dstId = %u", dstId); + } + + m_affiliations.releaseGrant(dstId, false); + } +} + +/// +/// Touchs a granted TG to keep a channel grant alive. +/// +/// +void Control::touchGrantTG(uint32_t dstId) +{ + if (!m_control) { + return; + } + + if (m_affiliations.isGranted(dstId)) { + if (m_verbose) { + LogMessage(LOG_NXDN, "REST request, touch TG grant, dstId = %u", dstId); + } + + m_affiliations.touchGrant(dstId); + } +} + /// /// Flag indicating whether the process or is busy or not. /// @@ -693,10 +745,10 @@ void Control::setRCCHVerbose(bool verbose) /// /// Add data frame to the data ring buffer. /// -/// -/// -/// -void Control::addFrame(const uint8_t *data, uint32_t length, bool net) +/// Frame data to add to Tx queue. +/// Flag indicating whether the data came from the network or not +/// Flag indicating whether or not the data is priority and is added to the immediate queue. +void Control::addFrame(const uint8_t *data, bool net, bool imm) { assert(data != nullptr); @@ -708,27 +760,49 @@ void Control::addFrame(const uint8_t *data, uint32_t length, bool net) return; } - uint32_t space = m_queue.freeSpace(); - if (space < (length + 1U)) { + uint8_t len = NXDN_FRAME_LENGTH_BYTES + 2U; + if (m_debug) { + Utils::symbols("!!! *Tx NXDN", data + 2U, len - 2U); + } + + // is this immediate data? + if (imm) { + // resize immediate queue if necessary (this shouldn't really ever happen) + uint32_t space = m_txImmQueue.freeSpace(); + if (space < (len + 1U)) { + if (!net) { + uint32_t queueLen = m_txImmQueue.length(); + m_txImmQueue.resize(queueLen + len); + LogError(LOG_P25, "overflow in the NXDN queue while writing imm data; queue free is %u, needed %u; resized was %u is %u", space, len, queueLen, m_txImmQueue.length()); + return; + } + else { + LogError(LOG_P25, "overflow in the NXDN queue while writing imm network data; queue free is %u, needed %u", space, len); + return; + } + } + + m_txImmQueue.addData(&len, 1U); + m_txImmQueue.addData(data, len); + return; + } + + uint32_t space = m_txQueue.freeSpace(); + if (space < (len + 1U)) { if (!net) { - uint32_t queueLen = m_queue.length(); - m_queue.resize(queueLen + NXDN_FRAME_LENGTH_BYTES); - LogError(LOG_NXDN, "overflow in the NXDN queue while writing data; queue free is %u, needed %u; resized was %u is %u", space, length, queueLen, m_queue.length()); + uint32_t queueLen = m_txQueue.length(); + m_txQueue.resize(queueLen + len); + LogError(LOG_NXDN, "overflow in the NXDN queue while writing data; queue free is %u, needed %u; resized was %u is %u", space, len, queueLen, m_txQueue.length()); return; } else { - LogError(LOG_NXDN, "overflow in the NXDN queue while writing network data; queue free is %u, needed %u", space, length); + LogError(LOG_NXDN, "overflow in the NXDN queue while writing network data; queue free is %u, needed %u", space, len); return; } } - if (m_debug) { - Utils::symbols("!!! *Tx NXDN", data + 2U, length - 2U); - } - - uint8_t len = length; - m_queue.addData(&len, 1U); - m_queue.addData(data, len); + m_txQueue.addData(&len, 1U); + m_txQueue.addData(data, len); } /// @@ -739,30 +813,57 @@ void Control::processNetwork() if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) return; - lc::RTCH lc; - - uint32_t length = 100U; + uint32_t length = 0U; bool ret = false; - uint8_t* data = m_network->readNXDN(ret, lc, length); + UInt8Array buffer = m_network->readNXDN(ret, length); if (!ret) return; if (length == 0U) return; - if (data == nullptr) { + if (buffer == nullptr) { m_network->resetNXDN(); return; } + uint8_t messageType = buffer[4U]; + + uint32_t srcId = __GET_UINT16(buffer, 5U); + uint32_t dstId = __GET_UINT16(buffer, 8U); + + if (m_debug) { + LogDebug(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length); + } + + lc::RTCH lc; + lc.setMessageType(messageType); + lc.setSrcId((uint16_t)srcId & 0xFFFFU); + lc.setDstId((uint16_t)dstId & 0xFFFFU); + + bool group = (buffer[15U] & 0x40U) == 0x40U ? false : true; + lc.setGroup(group); + + UInt8Array data; + uint8_t frameLength = buffer[23U]; + if (frameLength <= 24) { + data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + } + else { + data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), buffer.get() + 24U, frameLength); + } + m_networkWatchdog.start(); if (m_debug) { - Utils::dump(2U, "!!! *NXDN Network Frame", data, length); + Utils::dump(2U, "!!! *NXDN Network Frame", data.get(), frameLength); } - NXDNUtils::scrambler(data + 2U); + NXDNUtils::scrambler(data.get() + 2U); channel::LICH lich; - bool valid = lich.decode(data + 2U); + bool valid = lich.decode(data.get() + 2U); if (valid) m_rfLastLICH = lich; @@ -772,14 +873,64 @@ void Control::processNetwork() switch (usc) { case NXDN_LICH_USC_UDCH: - ret = m_data->processNetwork(option, lc, data, length); + ret = m_data->processNetwork(option, lc, data.get(), frameLength); break; default: - ret = m_voice->processNetwork(usc, option, lc, data, length); + ret = m_voice->processNetwork(usc, option, lc, data.get(), frameLength); break; } +} - delete data; +/// +/// Helper to send a REST API request to the CC to release a channel grant at the end of a call. +/// +/// +void Control::notifyCC_ReleaseGrant(uint32_t dstId) +{ + // callback REST API to release the granted TG on the specified control channel + if (!m_controlChData.address().empty() && m_controlChData.port() > 0) { + if (m_controlChData.address() == "127.0.0.1") { + // cowardly ignore trying to send release grants to ourselves + return; + } + + json::object req = json::object(); + int state = modem::DVM_STATE::STATE_NXDN; + req["state"].set(state); + req["dstId"].set(dstId); + + int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), + HTTP_PUT, PUT_RELEASE_TG, req, m_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + } + } +} + +/// +/// Helper to send a REST API request to the CC to "touch" a channel grant to refresh grant timers. +/// +/// +void Control::notifyCC_TouchGrant(uint32_t dstId) +{ + // callback REST API to touch the granted TG on the specified control channel + if (!m_controlChData.address().empty() && m_controlChData.port() > 0) { + if (m_controlChData.address() == "127.0.0.1") { + // cowardly ignore trying to send touch grants to ourselves + return; + } + + json::object req = json::object(); + int state = modem::DVM_STATE::STATE_NXDN; + req["state"].set(state); + req["dstId"].set(dstId); + + int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), + HTTP_PUT, PUT_TOUCH_TG, req, m_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + } + } } /// @@ -797,7 +948,7 @@ bool Control::writeRF_ControlData() // don't add any frames if the queue is full uint8_t len = NXDN_FRAME_LENGTH_BYTES + 2U; - uint32_t space = m_queue.freeSpace(); + uint32_t space = m_txQueue.freeSpace(); if (space < (len + 1U)) { return false; } diff --git a/src/nxdn/Control.h b/src/nxdn/Control.h index db66d10b..f9f307b5 100644 --- a/src/nxdn/Control.h +++ b/src/nxdn/Control.h @@ -39,11 +39,11 @@ #include "nxdn/packet/Trunk.h" #include "nxdn/packet/Data.h" #include "nxdn/SiteData.h" -#include "network/BaseNetwork.h" +#include "network/Network.h" #include "lookups/RSSIInterpolator.h" #include "lookups/IdenTableLookup.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" #include "lookups/AffiliationLookup.h" #include "modem/Modem.h" #include "RingBuffer.h" @@ -72,8 +72,8 @@ namespace nxdn public: /// Initializes a new instance of the Control class. Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t timeout, uint32_t tgHang, - modem::Modem* modem, network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, - lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, + modem::Modem* modem, network::Network* network, bool duplex, lookups::RadioIdLookup* ridLookup, + lookups::TalkgroupRulesLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, bool dumpRCCHData, bool debug, bool verbose); /// Finalizes a instance of the Control class. ~Control(); @@ -83,8 +83,8 @@ namespace nxdn /// Helper to set NXDN configuration options. void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector voiceChNo, - const std::unordered_map voiceChData, uint16_t siteId, uint32_t sysId, - uint8_t channelId, uint32_t channelNo, bool printOptions); + const std::unordered_map voiceChData, lookups::VoiceChData controlChData, + uint16_t siteId, uint32_t sysId, uint8_t channelId, uint32_t channelNo, bool printOptions); /// Gets a flag indicating whether the NXDN control channel is running. bool getCCRunning() { return m_ccRunning; } @@ -108,6 +108,11 @@ namespace nxdn /// Permits a TGID on a non-authoritative host. void permittedTG(uint32_t dstId); + /// Releases a granted TG. + void releaseGrantTG(uint32_t dstId); + /// Touchs a granted TG to keep a channel grant alive. + void touchGrantTG(uint32_t dstId); + /// Gets instance of the AffiliationLookup class. lookups::AffiliationLookup affiliations() { return m_affiliations; } @@ -140,7 +145,7 @@ namespace nxdn uint32_t m_timeout; modem::Modem* m_modem; - network::BaseNetwork* m_network; + network::Network* m_network; bool m_duplex; bool m_control; @@ -158,12 +163,14 @@ namespace nxdn lookups::IdenTableLookup* m_idenTable; lookups::RadioIdLookup* m_ridLookup; - lookups::TalkgroupIdLookup* m_tidLookup; + lookups::TalkgroupRulesLookup* m_tidLookup; lookups::AffiliationLookup m_affiliations; + ::lookups::VoiceChData m_controlChData; lookups::IdenTable m_idenEntry; - RingBuffer m_queue; + RingBuffer m_txImmQueue; + RingBuffer m_txQueue; RPT_RF_STATE m_rfState; uint32_t m_rfLastDstId; @@ -199,11 +206,16 @@ namespace nxdn bool m_debug; /// Add data frame to the data ring buffer. - void addFrame(const uint8_t* data, uint32_t length, bool net = false); + void addFrame(const uint8_t* data, bool net = false, bool imm = false); /// Process a data frames from the network. void processNetwork(); + /// Helper to send a REST API request to the CC to release a channel grant at the end of a call. + void notifyCC_ReleaseGrant(uint32_t dstId); + /// Helper to send a REST API request to the CC to "touch" a channel grant to refresh grant timers. + void notifyCC_TouchGrant(uint32_t dstId); + /// Helper to write control channel frame data. bool writeRF_ControlData(); diff --git a/src/nxdn/NXDNDefines.h b/src/nxdn/NXDNDefines.h index 066d45ba..a8b6a89e 100644 --- a/src/nxdn/NXDNDefines.h +++ b/src/nxdn/NXDNDefines.h @@ -33,33 +33,6 @@ #include "Defines.h" -// Message Type Strings -#define NXDN_RTCH_MSG_TYPE_VCALL "VCALL (Voice Call)" -#define NXDN_RTCH_MSG_TYPE_VCALL_REQ "VCALL_REQ (Voice Call Request)" -#define NXDN_RTCH_MSG_TYPE_VCALL_RESP "VCALL_RESP (Voice Call Response)" -#define NXDN_RTCH_MSG_TYPE_VCALL_IV "VCALL_IV (Voice Call Init Vector)" -#define NXDN_RCCH_MSG_TYPE_VCALL_CONN_REQ "VCALL_CONN_REQ (Voice Call Connection Request)" -#define NXDN_RCCH_MSG_TYPE_VCALL_CONN_RESP "VCALL_CONN_RESP (Voice Call Connection Response)" -#define NXDN_RCCH_MSG_TYPE_VCALL_ASSGN "VCALL_ASSGN (Voice Call Assignment)" -#define NXDN_RTCH_MSG_TYPE_TX_REL_EX "TX_REL_EX (Transmission Release Extension)" -#define NXDN_RTCH_MSG_TYPE_TX_REL "TX_REL (Transmission Release)" -#define NXDN_RTCH_MSG_TYPE_DCALL_HDR "DCALL (Data Call Header)" -#define NXDN_RCCH_MSG_TYPE_DCALL_REQ "DCALL_REQ (Data Call Request)" -#define NXDN_RCCH_MSG_TYPE_DCALL_RESP "DCALL_RESP (Data Call Response)" -#define NXDN_RTCH_MSG_TYPE_DCALL_DATA "DCALL (Data Call User Data)" -#define NXDN_RTCH_MSG_TYPE_DCALL_ACK "DCALL_ACL (Data Call Acknowledge)" -#define NXDN_RTCH_MSG_TYPE_HEAD_DLY "HEAD_DLY (Header Delay)" -#define NXDN_MSG_TYPE_IDLE "IDLE (Idle)" -#define NXDN_MSG_TYPE_DISC "DISC (Disconnect)" -#define NXDN_RCCH_MSG_TYPE_DCALL_ASSGN "DCALL_ASSGN (Data Call Assignment)" -#define NXDN_RCCH_MSG_TYPE_REG_REQ "REG_REQ (Registration Request)" -#define NXDN_RCCH_MSG_TYPE_REG_RESP "REG_RESP (Registration Response)" -#define NXDN_RCCH_MSG_TYPE_REG_C_REQ "REG_C_REQ (Registration Clear Request)" -#define NXDN_RCCH_MSG_TYPE_REG_C_RESP "REG_C_RESP (Registration Clear Response)" -#define NXDN_RCCH_MSG_TYPE_REG_COMM "REG_COMM (Registration Command)" -#define NXDN_RCCH_MSG_TYPE_GRP_REG_REQ "GRP_REG_REQ (Group Registration Request)" -#define NXDN_RCCH_MSG_TYPE_GRP_REG_RESP "GRP_REG_RESP (Group Registration Response)" - namespace nxdn { // --------------------------------------------------------------------------- @@ -263,10 +236,14 @@ namespace nxdn // Traffic Channel Message Types const uint8_t RTCH_MESSAGE_TYPE_VCALL = 0x01U; // VCALL - Voice Call +#define NXDN_RTCH_MSG_TYPE_VCALL "VCALL (Voice Call)" +#define NXDN_RTCH_MSG_TYPE_VCALL_RESP "VCALL_RESP (Voice Call Response)" const uint8_t RTCH_MESSAGE_TYPE_VCALL_IV = 0x03U; // VCALL_IV - Voice Call Initialization Vector const uint8_t RTCH_MESSAGE_TYPE_TX_REL_EX = 0x07U; // TX_REL_EX - Transmission Release Extension const uint8_t RTCH_MESSAGE_TYPE_TX_REL = 0x08U; // TX_REL - Transmission Release +#define NXDN_RTCH_MSG_TYPE_TX_REL "TX_REL (Transmission Release)" const uint8_t RTCH_MESSAGE_TYPE_DCALL_HDR = 0x09U; // DCALL - Data Call (Header) +#define NXDN_RTCH_MSG_TYPE_DCALL_HDR "DCALL (Data Call Header)" const uint8_t RTCH_MESSAGE_TYPE_DCALL_DATA = 0x0BU; // DCALL - Data Call (User Data Format) const uint8_t RTCH_MESSAGE_TYPE_DCALL_ACK = 0x0CU; // DCALL_ACK - Data Call Acknowledge const uint8_t RTCH_MESSAGE_TYPE_HEAD_DLY = 0x0FU; // HEAD_DLY - Header Delay diff --git a/src/nxdn/acl/AccessControl.cpp b/src/nxdn/acl/AccessControl.cpp index 3f50ed37..9d55fadf 100644 --- a/src/nxdn/acl/AccessControl.cpp +++ b/src/nxdn/acl/AccessControl.cpp @@ -43,14 +43,14 @@ using namespace nxdn::acl; // --------------------------------------------------------------------------- RadioIdLookup* AccessControl::m_ridLookup; -TalkgroupIdLookup* AccessControl::m_tidLookup; +TalkgroupRulesLookup* AccessControl::m_tidLookup; /// /// Initializes the P25 access control. /// /// Instance of the RadioIdLookup class. -/// Instance of the TalkgroupIdLookup class. -void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup) +/// Instance of the TalkgroupRulesLookup class. +void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; @@ -99,8 +99,11 @@ bool AccessControl::validateTGId(uint32_t id) } // lookup TID and perform test for validity - TalkgroupId tid = m_tidLookup->find(id); - if (!tid.tgEnabled()) + TalkgroupRuleGroupVoice tid = m_tidLookup->find(id); + if (tid.isInvalid()) + return false; + + if (!tid.config().active()) return false; return true; diff --git a/src/nxdn/acl/AccessControl.h b/src/nxdn/acl/AccessControl.h index eba0b8a9..db57664e 100644 --- a/src/nxdn/acl/AccessControl.h +++ b/src/nxdn/acl/AccessControl.h @@ -33,7 +33,7 @@ #include "Defines.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" namespace nxdn { @@ -49,7 +49,7 @@ namespace nxdn class HOST_SW_API AccessControl { public: /// Initializes the P25 access control. - static void init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup); + static void init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLookup); /// Helper to validate a source radio ID. static bool validateSrcId(uint32_t id); @@ -58,9 +58,9 @@ namespace nxdn private: static RadioIdLookup* m_ridLookup; - static TalkgroupIdLookup* m_tidLookup; + static TalkgroupRulesLookup* m_tidLookup; }; - } // namespace ACL + } // namespace acl } // namespace nxdn #endif // __NXDN_ACL__ACCESS_CONTROL_H__ diff --git a/src/nxdn/lc/RCCH.cpp b/src/nxdn/lc/RCCH.cpp index 7e31b109..9dab007c 100644 --- a/src/nxdn/lc/RCCH.cpp +++ b/src/nxdn/lc/RCCH.cpp @@ -96,6 +96,16 @@ RCCH::~RCCH() /* stub */ } +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string RCCH::toString(bool isp) +{ + return std::string("MESSAGE_TYPE_UNKWN (Unknown RCCH)"); +} + /// /// Sets the callsign. /// diff --git a/src/nxdn/lc/RCCH.h b/src/nxdn/lc/RCCH.h index ce9b1ad2..9c623288 100644 --- a/src/nxdn/lc/RCCH.h +++ b/src/nxdn/lc/RCCH.h @@ -58,6 +58,9 @@ namespace nxdn /// Encode layer 3 data. virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U) = 0; + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false); + /// Sets the flag indicating verbose log output. static void setVerbose(bool verbose) { m_verbose = verbose; } diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.cpp index 2d00870b..b903c389 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.cpp @@ -101,3 +101,13 @@ void MESSAGE_TYPE_DCALL_HDR::encode(uint8_t* data, uint32_t length, uint32_t off RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_DCALL_HDR::toString(bool isp) +{ + return std::string("RTCH_MESSAGE_TYPE_DCALL_HDR (Data Call Header)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.h index 2ffd72be..c8895eb6 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.h @@ -37,8 +37,7 @@ namespace nxdn { // --------------------------------------------------------------------------- // Class Declaration - // Implements VCALL_CONN - Voice Call Connection Request (ISP) and - // Voice Call Connection Response (OSP) + // Implements DCALL_HDR - Data Call Header // --------------------------------------------------------------------------- class HOST_SW_API MESSAGE_TYPE_DCALL_HDR : public RCCH { @@ -47,9 +46,12 @@ namespace nxdn MESSAGE_TYPE_DCALL_HDR(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.cpp index a15e5f1c..c4fb430d 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.cpp @@ -84,3 +84,13 @@ void MESSAGE_TYPE_DST_ID_INFO::encode(uint8_t* data, uint32_t length, uint32_t o RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_DST_ID_INFO::toString(bool isp) +{ + return std::string("MESSAGE_TYPE_DST_ID_INFO (Digital Station ID)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.h index 73d52e0b..e17a79e8 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.h @@ -46,9 +46,12 @@ namespace nxdn MESSAGE_TYPE_DST_ID_INFO(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.cpp index 4163935e..7406a606 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.cpp @@ -90,3 +90,14 @@ void MESSAGE_TYPE_GRP_REG::encode(uint8_t* data, uint32_t length, uint32_t offse RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_GRP_REG::toString(bool isp) +{ + if (isp) return std::string("RCCH_MESSAGE_TYPE_GRP_REG (Group Registration Request)"); + else return std::string("RCCH_MESSAGE_TYPE_GRP_REG (Group Registration Response)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.h index 56ac0df1..0c3904d8 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.h @@ -47,9 +47,12 @@ namespace nxdn MESSAGE_TYPE_GRP_REG(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_IDLE.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_IDLE.cpp index 344a6b3c..1ad9c17a 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_IDLE.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_IDLE.cpp @@ -78,3 +78,13 @@ void MESSAGE_TYPE_IDLE::encode(uint8_t* data, uint32_t length, uint32_t offset) RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_IDLE::toString(bool isp) +{ + return std::string("MESSAGE_TYPE_IDLE (Idle)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_IDLE.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_IDLE.h index 868a21d3..6230735e 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_IDLE.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_IDLE.h @@ -46,9 +46,12 @@ namespace nxdn MESSAGE_TYPE_IDLE(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp index c4699eb4..c9e7855c 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp @@ -93,3 +93,14 @@ void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset) RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_REG::toString(bool isp) +{ + if (isp) return std::string("RCCH_MESSAGE_TYPE_REG (Registration Request)"); + else return std::string("RCCH_MESSAGE_TYPE_REG (Registration Response)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG.h index d2914cc5..3f849ffc 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG.h @@ -47,9 +47,12 @@ namespace nxdn MESSAGE_TYPE_REG(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.cpp index b55fe352..2650dbf5 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.cpp @@ -88,3 +88,14 @@ void MESSAGE_TYPE_REG_C::encode(uint8_t* data, uint32_t length, uint32_t offset) RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_REG_C::toString(bool isp) +{ + if (isp) return std::string("RCCH_MESSAGE_TYPE_REG_C (Registration Clear Request)"); + else return std::string("RCCH_MESSAGE_TYPE_REG_C (Registration Clear Response)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.h index 91322abb..f16fb12d 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.h @@ -47,10 +47,13 @@ namespace nxdn MESSAGE_TYPE_REG_C(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); - }; + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; + }; } // namespace rcch } // namespace lc } // namespace nxdn diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.cpp index f20b740d..49801942 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.cpp @@ -83,3 +83,13 @@ void MESSAGE_TYPE_REG_COMM::encode(uint8_t* data, uint32_t length, uint32_t offs RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_REG_COMM::toString(bool isp) +{ + return std::string("RCCH_MESSAGE_TYPE_REG_COMM (Registration Command)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.h index 00f8491e..786d3737 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.h @@ -46,9 +46,12 @@ namespace nxdn MESSAGE_TYPE_REG_COMM(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.cpp index 50a730a6..34e01cad 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.cpp @@ -111,6 +111,16 @@ void MESSAGE_TYPE_SITE_INFO::encode(uint8_t* data, uint32_t length, uint32_t off RCCH::encode(data, rcch, length, offset); } +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_SITE_INFO::toString(bool isp) +{ + return std::string("RCCH_MESSAGE_TYPE_SITE_INFO (Site Information)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.h index d439865b..8c0b9407 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.h @@ -46,9 +46,12 @@ namespace nxdn MESSAGE_TYPE_SITE_INFO(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; public: /** Channel Structure Data */ diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.cpp index 2309adac..81bbe53b 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.cpp @@ -89,3 +89,13 @@ void MESSAGE_TYPE_SRV_INFO::encode(uint8_t* data, uint32_t length, uint32_t offs RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_SRV_INFO::toString(bool isp) +{ + return std::string("MESSAGE_TYPE_SRV_INFO (Service Information)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.h index b6a6b689..45adaea7 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.h @@ -46,9 +46,12 @@ namespace nxdn MESSAGE_TYPE_SRV_INFO(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.cpp index 53595fa0..6426a30d 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.cpp @@ -95,3 +95,13 @@ void MESSAGE_TYPE_VCALL_ASSGN::encode(uint8_t* data, uint32_t length, uint32_t o RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_VCALL_ASSGN::toString(bool isp) +{ + return std::string("RCCH_MESSAGE_TYPE_VCALL_ASSGN (Voice Call Assignment)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.h index b78b806b..fd150667 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.h @@ -46,9 +46,12 @@ namespace nxdn MESSAGE_TYPE_VCALL_ASSGN(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.cpp b/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.cpp index 0ee8ecee..df79bfe2 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.cpp +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.cpp @@ -101,3 +101,14 @@ void MESSAGE_TYPE_VCALL_CONN::encode(uint8_t* data, uint32_t length, uint32_t of RCCH::encode(data, rcch, length, offset); } + +/// +/// Returns a string that represents the current RCCH. +/// +/// +/// +std::string MESSAGE_TYPE_VCALL_CONN::toString(bool isp) +{ + if (isp) return std::string("RCCH_MESSAGE_TYPE_VCALL_CONN (Voice Call Connection Request)"); + else return std::string("RCCH_MESSAGE_TYPE_VCALL_CONN (Voice Call Connection Response)"); +} diff --git a/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.h b/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.h index e6d5a29a..868a34bb 100644 --- a/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.h +++ b/src/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.h @@ -47,9 +47,12 @@ namespace nxdn MESSAGE_TYPE_VCALL_CONN(); /// Decode layer 3 data. - virtual void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); + void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U); /// Encode layer 3 data. - virtual void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U); + + /// Returns a string that represents the current RCCH. + virtual std::string toString(bool isp = false) override; }; } // namespace rcch } // namespace lc diff --git a/src/nxdn/packet/Data.cpp b/src/nxdn/packet/Data.cpp index aad585f3..23b20289 100644 --- a/src/nxdn/packet/Data.cpp +++ b/src/nxdn/packet/Data.cpp @@ -291,7 +291,7 @@ bool Data::processNetwork(uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32 assert(data != nullptr); if (m_nxdn->m_netState == RS_NET_IDLE) { - m_nxdn->m_queue.clear(); + m_nxdn->m_txQueue.clear(); resetRF(); resetNet(); diff --git a/src/nxdn/packet/Trunk.cpp b/src/nxdn/packet/Trunk.cpp index 389d2a29..b4c4dba4 100644 --- a/src/nxdn/packet/Trunk.cpp +++ b/src/nxdn/packet/Trunk.cpp @@ -58,7 +58,7 @@ using namespace nxdn::packet; // Make sure control data is supported. #define IS_SUPPORT_CONTROL_CHECK(_PCKT_STR, _PCKT, _SRCID) \ if (!m_nxdn->m_control) { \ - LogWarning(LOG_RF, "NXDN, " _PCKT_STR " denial, unsupported service, srcId = %u", _SRCID); \ + LogWarning(LOG_RF, "NXDN, %s denial, unsupported service, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_Message_Deny(0U, _SRCID, NXDN_CAUSE_SVC_UNAVAILABLE, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -67,7 +67,7 @@ using namespace nxdn::packet; // Validate the source RID. #define VALID_SRCID(_PCKT_STR, _PCKT, _SRCID, _RSN) \ if (!acl::AccessControl::validateSrcId(_SRCID)) { \ - LogWarning(LOG_RF, "NXDN, " _PCKT_STR " denial, RID rejection, srcId = %u", _SRCID); \ + LogWarning(LOG_RF, "NXDN, %s denial, RID rejection, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -76,7 +76,7 @@ using namespace nxdn::packet; // Validate the target RID. #define VALID_DSTID(_PCKT_STR, _PCKT, _DSTID, _RSN) \ if (!acl::AccessControl::validateSrcId(_DSTID)) { \ - LogWarning(LOG_RF, "NXDN, " _PCKT_STR " denial, RID rejection, dstId = %u", _DSTID); \ + LogWarning(LOG_RF, "NXDN, %s denial, RID rejection, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -85,7 +85,7 @@ using namespace nxdn::packet; // Validate the talkgroup ID. #define VALID_TGID(_PCKT_STR, _PCKT, _DSTID, _SRCID, _RSN) \ if (!acl::AccessControl::validateTGId(_DSTID)) { \ - LogWarning(LOG_RF, "NXDN, " _PCKT_STR " denial, TGID rejection, dstId = %u", _DSTID); \ + LogWarning(LOG_RF, "NXDN, %s denial, TGID rejection, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -94,7 +94,7 @@ using namespace nxdn::packet; // Verify the source RID is registered. #define VERIFY_SRCID_REG(_PCKT_STR, _PCKT, _SRCID, _RSN) \ if (!m_nxdn->m_affiliations.isUnitReg(_SRCID) && m_verifyReg) { \ - LogWarning(LOG_RF, "NXDN, " _PCKT_STR " denial, RID not registered, srcId = %u", _SRCID); \ + LogWarning(LOG_RF, "NXDN, %s denial, RID not registered, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -103,12 +103,36 @@ using namespace nxdn::packet; // Verify the source RID is affiliated. #define VERIFY_SRCID_AFF(_PCKT_STR, _PCKT, _SRCID, _DSTID, _RSN) \ if (!m_nxdn->m_affiliations.isGroupAff(_SRCID, _DSTID) && m_verifyAff) { \ - LogWarning(LOG_RF, "NXDN, " _PCKT_STR " denial, RID not affiliated to TGID, srcId = %u, dstId = %u", _SRCID, _DSTID); \ + LogWarning(LOG_RF, "NXDN, %s denial, RID not affiliated to TGID, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ writeRF_Message_Deny(0U, _SRCID, _RSN, _PCKT); \ m_nxdn->m_rfState = RS_RF_REJECTED; \ return false; \ } +// Macro helper to verbose log a generic message. +#define VERBOSE_LOG_MSG(_PCKT_STR, _SRCID, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_RF, "NXDN, %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ + } + +// Macro helper to verbose log a generic message. +#define VERBOSE_LOG_MSG_DST(_PCKT_STR, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_RF, "NXDN, %s, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ + } + +// Macro helper to verbose log a generic network message. +#define VERBOSE_LOG_MSG_NET(_PCKT_STR, _SRCID, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_NET, "NXDN, %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ + } + +// Macro helper to verbose log a generic network message. +#define DEBUG_LOG_MSG(_PCKT_STR) \ + if (m_debug) { \ + LogMessage(LOG_RF, "NXDN, %s", _PCKT_STR.c_str()); \ + } + // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- @@ -147,7 +171,7 @@ bool Trunk::process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len) m_nxdn->m_rfState = RS_RF_DATA; } - m_nxdn->m_queue.clear(); + m_nxdn->m_txQueue.clear(); // the layer3 data will only be correct if valid is true uint8_t buffer[NXDN_FRAME_LENGTH_BYTES]; @@ -164,21 +188,18 @@ bool Trunk::process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len) case RTCH_MESSAGE_TYPE_VCALL: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK(NXDN_RTCH_MSG_TYPE_VCALL_REQ, RTCH_MESSAGE_TYPE_VCALL, srcId); + IS_SUPPORT_CONTROL_CHECK(rcch->toString(true), RTCH_MESSAGE_TYPE_VCALL, srcId); // validate the source RID - VALID_SRCID(NXDN_RTCH_MSG_TYPE_VCALL_REQ, RTCH_MESSAGE_TYPE_VCALL, srcId, NXDN_CAUSE_VD_REQ_UNIT_NOT_PERM); + VALID_SRCID(rcch->toString(true), RTCH_MESSAGE_TYPE_VCALL, srcId, NXDN_CAUSE_VD_REQ_UNIT_NOT_PERM); // validate the talkgroup ID - VALID_TGID(NXDN_RTCH_MSG_TYPE_VCALL_REQ, RTCH_MESSAGE_TYPE_VCALL, dstId, srcId, NXDN_CAUSE_VD_TGT_UNIT_NOT_PERM); + VALID_TGID(rcch->toString(true), RTCH_MESSAGE_TYPE_VCALL, dstId, srcId, NXDN_CAUSE_VD_TGT_UNIT_NOT_PERM); // verify the source RID is affiliated - VERIFY_SRCID_AFF(NXDN_RTCH_MSG_TYPE_VCALL_REQ, RTCH_MESSAGE_TYPE_VCALL, srcId, dstId, NXDN_CAUSE_VD_REQ_UNIT_NOT_REG); - - if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_REQ ", srcId = %u, dstId = %u", srcId, dstId); - } + VERIFY_SRCID_AFF(rcch->toString(true), RTCH_MESSAGE_TYPE_VCALL, srcId, dstId, NXDN_CAUSE_VD_REQ_UNIT_NOT_REG); + VERBOSE_LOG_MSG(rcch->toString(true), srcId, dstId); uint8_t serviceOptions = (rcch->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag (rcch->getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag (rcch->getPriority() & 0x07U); // Priority @@ -193,10 +214,11 @@ bool Trunk::process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len) case RCCH_MESSAGE_TYPE_REG: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK(NXDN_RCCH_MSG_TYPE_REG_REQ, RCCH_MESSAGE_TYPE_REG, srcId); + IS_SUPPORT_CONTROL_CHECK(rcch->toString(true), RCCH_MESSAGE_TYPE_REG, srcId); if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_REG_REQ ", srcId = %u, locId = %u", srcId, rcch->getLocId()); + LogMessage(LOG_RF, "NXDN, %s, srcId = %u, locId = %u", + rcch->toString(true).c_str(), srcId, rcch->getLocId()); } writeRF_Message_U_Reg_Rsp(srcId, rcch->getLocId()); @@ -205,10 +227,11 @@ bool Trunk::process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len) case RCCH_MESSAGE_TYPE_GRP_REG: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK(NXDN_RCCH_MSG_TYPE_GRP_REG_REQ, RCCH_MESSAGE_TYPE_GRP_REG, srcId); + IS_SUPPORT_CONTROL_CHECK(rcch->toString(true), RCCH_MESSAGE_TYPE_GRP_REG, srcId); if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_GRP_REG_REQ ", srcId = %u, dstId = %u, locId = %u", srcId, dstId, rcch->getLocId()); + LogMessage(LOG_RF, "NXDN, %s, srcId = %u, dstId = %u, locId = %u", + rcch->toString(true).c_str(), srcId, dstId, rcch->getLocId()); } writeRF_Message_Grp_Reg_Rsp(srcId, dstId, rcch->getLocId()); @@ -242,7 +265,7 @@ bool Trunk::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t return false; if (m_nxdn->m_netState == RS_NET_IDLE) { - m_nxdn->m_queue.clear(); + m_nxdn->m_txQueue.clear(); } if (m_nxdn->m_netState == RS_NET_IDLE) { @@ -261,8 +284,8 @@ bool Trunk::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t if (m_nxdn->m_dedicatedControl && !m_nxdn->m_voiceOnControl) { if (!m_nxdn->m_affiliations.isGranted(dstId)) { if (m_verbose) { - LogMessage(LOG_NET, NXDN_RCCH_MSG_TYPE_VCALL_CONN_REQ ", emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", - rcch->getEmergency(), rcch->getEncrypted(), rcch->getPriority(), rcch->getGrpVchNo(), srcId, dstId); + LogMessage(LOG_NET, "NXDN, %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + rcch->toString().c_str(), rcch->getEmergency(), rcch->getEncrypted(), rcch->getPriority(), rcch->getGrpVchNo(), srcId, dstId); } uint8_t serviceOptions = (rcch->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag @@ -367,7 +390,7 @@ void Trunk::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS) // don't add any frames if the queue is full uint8_t len = NXDN_FRAME_LENGTH_BYTES + 2U; - uint32_t space = m_nxdn->m_queue.freeSpace(); + uint32_t space = m_nxdn->m_txQueue.freeSpace(); if (space < (len + 1U)) { return; } @@ -400,7 +423,8 @@ void Trunk::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS) /// /// /// -void Trunk::writeRF_Message(RCCH* rcch, bool noNetwork, bool clearBeforeWrite) +/// +void Trunk::writeRF_Message(RCCH* rcch, bool noNetwork, bool clearBeforeWrite, bool imm) { if (!m_nxdn->m_control) return; @@ -441,12 +465,12 @@ void Trunk::writeRF_Message(RCCH* rcch, bool noNetwork, bool clearBeforeWrite) writeNetwork(data, NXDN_FRAME_LENGTH_BYTES + 2U); if (clearBeforeWrite) { - m_nxdn->m_modem->clearNXDNData(); - m_nxdn->m_queue.clear(); + m_nxdn->m_modem->clearNXDNFrame(); + m_nxdn->m_txQueue.clear(); } if (m_nxdn->m_duplex) { - m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); + m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U, imm); } } @@ -467,11 +491,13 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic bool encryption = ((serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag uint8_t priority = ((serviceOptions & 0xFFU) & 0x07U); // Priority + std::unique_ptr rcch = new_unique(rcch::MESSAGE_TYPE_VCALL_CONN); + // are we skipping checking? if (!skip) { if (m_nxdn->m_rfState != RS_RF_LISTENING && m_nxdn->m_rfState != RS_RF_DATA) { if (!net) { - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_REQ " denied, traffic in progress, dstId = %u", dstId); + LogWarning(LOG_RF, "NXDN, %s denied, traffic in progress, dstId = %u", rcch->toString().c_str(), dstId); writeRF_Message_Deny(0U, srcId, NXDN_CAUSE_VD_QUE_GRP_BUSY, RTCH_MESSAGE_TYPE_VCALL); ::ActivityLog("NXDN", true, "group grant request from %u to TG %u denied", srcId, dstId); @@ -483,7 +509,7 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic if (m_nxdn->m_netState != RS_NET_IDLE && dstId == m_nxdn->m_netLastDstId) { if (!net) { - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_REQ " denied, traffic in progress, dstId = %u", dstId); + LogWarning(LOG_RF, "NXDN, %s denied, traffic in progress, dstId = %u", rcch->toString().c_str(), dstId); writeRF_Message_Deny(0U, srcId, NXDN_CAUSE_VD_QUE_GRP_BUSY, RTCH_MESSAGE_TYPE_VCALL); ::ActivityLog("NXDN", true, "group grant request from %u to TG %u denied", srcId, dstId); @@ -509,7 +535,7 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic if (!m_nxdn->m_affiliations.isRFChAvailable()) { if (grp) { if (!net) { - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_REQ " queued, no channels available, dstId = %u", dstId); + LogWarning(LOG_RF, "NXDN, %s queued, no channels available, dstId = %u", rcch->toString().c_str(), dstId); writeRF_Message_Deny(0U, srcId, NXDN_CAUSE_VD_QUE_CHN_RESOURCE_NOT_AVAIL, RTCH_MESSAGE_TYPE_VCALL); ::ActivityLog("NXDN", true, "group grant request from %u to TG %u queued", srcId, dstId); @@ -520,7 +546,7 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic } else { if (!net) { - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_REQ " queued, no channels available, dstId = %u", dstId); + LogWarning(LOG_RF, "NXDN, %s queued, no channels available, dstId = %u", rcch->toString().c_str(), dstId); writeRF_Message_Deny(0U, srcId, NXDN_CAUSE_VD_QUE_CHN_RESOURCE_NOT_AVAIL, RTCH_MESSAGE_TYPE_VCALL); ::ActivityLog("P25", true, "unit-to-unit grant request from %u to %u queued", srcId, dstId); @@ -543,7 +569,7 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic uint32_t grantedSrcId = m_nxdn->m_affiliations.getGrantedSrcId(dstId); if (srcId != grantedSrcId) { if (!net) { - LogWarning(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_REQ " denied, traffic in progress, dstId = %u", dstId); + LogWarning(LOG_RF, "NXDN, %s denied, traffic in progress, dstId = %u", rcch->toString().c_str(), dstId); writeRF_Message_Deny(0U, srcId, NXDN_CAUSE_VD_QUE_GRP_BUSY, RTCH_MESSAGE_TYPE_VCALL); ::ActivityLog("NXDN", true, "group grant request from %u to TG %u denied", srcId, dstId); @@ -583,7 +609,7 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), HTTP_PUT, PUT_PERMIT_TG, req, m_nxdn->m_debug); if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_RESP ", failed to permit TG for use, chNo = %u", chNo); + ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcch->toString().c_str(), chNo); m_nxdn->m_affiliations.releaseGrant(dstId, false); if (!net) { writeRF_Message_Deny(0U, srcId, NXDN_CAUSE_VD_QUE_GRP_BUSY, RTCH_MESSAGE_TYPE_VCALL); @@ -594,11 +620,10 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic } } else { - ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_RESP ", failed to permit TG for use, chNo = %u", chNo); + ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcch->toString().c_str(), chNo); } } - std::unique_ptr rcch = new_unique(rcch::MESSAGE_TYPE_VCALL_CONN); rcch->setMessageType(RTCH_MESSAGE_TYPE_VCALL); rcch->setGrpVchNo(chNo); rcch->setGroup(grp); @@ -610,12 +635,12 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic rcch->setPriority(priority); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_RESP ", emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", - rcch->getEmergency(), rcch->getEncrypted(), rcch->getPriority(), rcch->getGrpVchNo(), rcch->getSrcId(), rcch->getDstId()); + LogMessage((net) ? LOG_NET : LOG_RF, "NXDN, %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + rcch->toString().c_str(), rcch->getEmergency(), rcch->getEncrypted(), rcch->getPriority(), rcch->getGrpVchNo(), rcch->getSrcId(), rcch->getDstId()); } // transmit group grant - writeRF_Message(rcch.get(), net, true); + writeRF_Message_Imm(rcch.get(), net); return true; } @@ -647,7 +672,7 @@ void Trunk::writeRF_Message_Deny(uint32_t srcId, uint32_t dstId, uint8_t reason, service, srcId, dstId); } - writeRF_Message(rcch.get(), false); + writeRF_Message_Imm(rcch.get(), false); } /// @@ -665,41 +690,39 @@ bool Trunk::writeRF_Message_Grp_Reg_Rsp(uint32_t srcId, uint32_t dstId, uint32_t // validate the location ID if (locId != m_nxdn->m_siteData.locId()) { - LogWarning(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_GRP_REG_REQ " denial, LOCID rejection, locId = $%04X", locId); + LogWarning(LOG_RF, "NXDN, %s denial, LOCID rejection, locId = $%04X", rcch->toString().c_str(), locId); ::ActivityLog("NXDN", true, "group affiliation request from %u denied", srcId); rcch->setCauseResponse(NXDN_CAUSE_MM_REG_FAILED); } // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { - LogWarning(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_GRP_REG_REQ " denial, RID rejection, srcId = %u", srcId); + LogWarning(LOG_RF, "NXDN, %s denial, RID rejection, srcId = %u", rcch->toString().c_str(), srcId); ::ActivityLog("NXDN", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); rcch->setCauseResponse(NXDN_CAUSE_MM_REG_FAILED); } // validate the source RID is registered if (!m_nxdn->m_affiliations.isUnitReg(srcId) && m_verifyReg) { - LogWarning(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_GRP_REG_REQ " denial, RID not registered, srcId = %u", srcId); + LogWarning(LOG_RF, "NXDN, %s denial, RID not registered, srcId = %u", rcch->toString().c_str(), srcId); ::ActivityLog("NXDN", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); rcch->setCauseResponse(NXDN_CAUSE_MM_REG_REFUSED); } // validate the talkgroup ID if (dstId == 0U) { - LogWarning(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_GRP_REG_REQ ", TGID 0, dstId = %u", dstId); + LogWarning(LOG_RF, "NXDN, %s, TGID 0, dstId = %u", rcch->toString().c_str(), dstId); } else { if (!acl::AccessControl::validateTGId(dstId)) { - LogWarning(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_GRP_REG_REQ " denial, TGID rejection, dstId = %u", dstId); + LogWarning(LOG_RF, "NXDN, %s denial, TGID rejection, dstId = %u", rcch->toString().c_str(), dstId); ::ActivityLog("NXDN", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); rcch->setCauseResponse(NXDN_CAUSE_MM_LOC_ACPT_GRP_REFUSE); } } if (rcch->getCauseResponse() == NXDN_CAUSE_MM_REG_ACCEPTED) { - if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_GRP_REG_REQ ", srcId = %u, dstId = %u", srcId, dstId); - } + VERBOSE_LOG_MSG(rcch->toString(), srcId, dstId); ::ActivityLog("NXDN", true, "group affiliation request from %u to %s %u", srcId, "TG ", dstId); ret = true; @@ -708,7 +731,7 @@ bool Trunk::writeRF_Message_Grp_Reg_Rsp(uint32_t srcId, uint32_t dstId, uint32_t m_nxdn->m_affiliations.groupAff(srcId, dstId); } - writeRF_Message(rcch.get(), false); + writeRF_Message_Imm(rcch.get(), false); return ret; } @@ -723,21 +746,22 @@ void Trunk::writeRF_Message_U_Reg_Rsp(uint32_t srcId, uint32_t locId) // validate the location ID if (locId != m_nxdn->m_siteData.locId()) { - LogWarning(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_REG_REQ " denial, LOCID rejection, locId = $%04X", locId); + LogWarning(LOG_RF, "NXDN, %s denial, LOCID rejection, locId = $%04X", rcch->toString().c_str(), locId); ::ActivityLog("NXDN", true, "unit registration request from %u denied", srcId); rcch->setCauseResponse(NXDN_CAUSE_MM_REG_FAILED); } // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { - LogWarning(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_REG_REQ " denial, RID rejection, srcId = %u", srcId); + LogWarning(LOG_RF, "NXDN, %s denial, RID rejection, srcId = %u", rcch->toString().c_str(), srcId); ::ActivityLog("NXDN", true, "unit registration request from %u denied", srcId); rcch->setCauseResponse(NXDN_CAUSE_MM_REG_FAILED); } if (rcch->getCauseResponse() == NXDN_CAUSE_MM_REG_ACCEPTED) { if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RCCH_MSG_TYPE_REG_REQ ", srcId = %u, locId = %u", srcId, locId); + LogMessage(LOG_RF, "NXDN, %s, srcId = %u, locId = %u", + rcch->toString().c_str(), srcId, locId); } ::ActivityLog("NXDN", true, "unit registration request from %u", srcId); @@ -751,7 +775,7 @@ void Trunk::writeRF_Message_U_Reg_Rsp(uint32_t srcId, uint32_t locId) rcch->setSrcId(srcId); rcch->setDstId(srcId); - writeRF_Message(rcch.get(), true); + writeRF_Message_Imm(rcch.get(), true); } /// @@ -759,10 +783,6 @@ void Trunk::writeRF_Message_U_Reg_Rsp(uint32_t srcId, uint32_t locId) /// void Trunk::writeRF_CC_Message_Site_Info() { - if (m_debug) { - LogMessage(LOG_RF, "NXDN, SITE_INFO (Site Information)"); - } - uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES); @@ -780,6 +800,7 @@ void Trunk::writeRF_CC_Message_Site_Info() ::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); std::unique_ptr rcch = new_unique(rcch::MESSAGE_TYPE_SITE_INFO); + DEBUG_LOG_MSG(rcch->toString()); rcch->setBcchCnt(m_bcchCnt); rcch->setRcchGroupingCnt(m_rcchGroupingCnt); rcch->setCcchPagingCnt(m_ccchPagingCnt); @@ -812,10 +833,6 @@ void Trunk::writeRF_CC_Message_Site_Info() /// void Trunk::writeRF_CC_Message_Service_Info() { - if (m_debug) { - LogMessage(LOG_RF, "NXDN, SRV_INFO (Service Information)"); - } - uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES); @@ -833,6 +850,7 @@ void Trunk::writeRF_CC_Message_Service_Info() ::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); std::unique_ptr rcch = new_unique(rcch::MESSAGE_TYPE_SRV_INFO); + DEBUG_LOG_MSG(rcch->toString()); rcch->encode(buffer, NXDN_RCCH_LC_LENGTH_BITS / 2U); //rcch->encode(buffer, NXDN_RCCH_LC_LENGTH_BITS / 2U, NXDN_RCCH_LC_LENGTH_BITS / 2U); diff --git a/src/nxdn/packet/Trunk.h b/src/nxdn/packet/Trunk.h index 4964d2a7..fa0d7463 100644 --- a/src/nxdn/packet/Trunk.h +++ b/src/nxdn/packet/Trunk.h @@ -100,8 +100,10 @@ namespace nxdn /// Helper to write control channel packet data. void writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS); + /// Helper to write a immediate single-block RCCH packet. + void writeRF_Message_Imm(lc::RCCH *rcch, bool noNetwork) { writeRF_Message(rcch, noNetwork, false, true); } /// Helper to write a single-block RCCH packet. - void writeRF_Message(lc::RCCH* rcch, bool noNetwork, bool clearBeforeWrite = false); + void writeRF_Message(lc::RCCH* rcch, bool noNetwork, bool clearBeforeWrite = false, bool imm = false); /// Helper to write a grant packet. bool writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOptions, bool grp, bool net = false, bool skip = false, uint32_t chNo = 0U); diff --git a/src/nxdn/packet/Voice.cpp b/src/nxdn/packet/Voice.cpp index 6756170d..e4f77bdc 100644 --- a/src/nxdn/packet/Voice.cpp +++ b/src/nxdn/packet/Voice.cpp @@ -633,7 +633,7 @@ bool Voice::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t assert(data != nullptr); if (m_nxdn->m_netState == RS_NET_IDLE) { - m_nxdn->m_queue.clear(); + m_nxdn->m_txQueue.clear(); resetRF(); resetNet(); diff --git a/src/p25/Control.cpp b/src/p25/Control.cpp index 6d705cbb..15a05711 100644 --- a/src/p25/Control.cpp +++ b/src/p25/Control.cpp @@ -75,7 +75,7 @@ const uint32_t MAX_PREAMBLE_TDU_CNT = 64U; /// Amount of time to hang on the last talkgroup mode from RF. /// Flag indicating full-duplex operation. /// Instance of the RadioIdLookup class. -/// Instance of the TalkgroupIdLookup class. +/// Instance of the TalkgroupRulesLookup class. /// Instance of the IdenTableLookup class. /// Instance of the RSSIInterpolator class. /// @@ -83,9 +83,9 @@ const uint32_t MAX_PREAMBLE_TDU_CNT = 64U; /// Flag indicating whether TSBK data is dumped to the log. /// Flag indicating whether P25 debug is enabled. /// Flag indicating whether P25 verbose logging is enabled. -Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::BaseNetwork* network, +Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::Network* network, uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::RadioIdLookup* ridLookup, - ::lookups::TalkgroupIdLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, + ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, bool dumpPDUData, bool repeatPDU, bool dumpTSBKData, bool debug, bool verbose) : m_voice(nullptr), m_data(nullptr), @@ -110,8 +110,10 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_ridLookup(ridLookup), m_tidLookup(tidLookup), m_affiliations(this, verbose), + m_controlChData(), m_idenEntry(), - m_queue(queueSize, "P25 Frame"), + m_txImmQueue(queueSize, "P25 Imm Frame"), + m_txQueue(queueSize, "P25 Frame"), m_rfState(RS_RF_LISTENING), m_rfLastDstId(0U), m_netState(RS_NET_IDLE), @@ -202,7 +204,8 @@ void Control::reset() m_data->resetRF(); } - m_queue.clear(); + m_txImmQueue.clear(); + m_txQueue.clear(); } /// @@ -213,6 +216,7 @@ void Control::reset() /// CW callsign of this host. /// Voice Channel Number list. /// Voice Channel data map. +/// Control Channel data. /// /// P25 Network ID. /// P25 System ID. @@ -222,8 +226,9 @@ void Control::reset() /// Channel Number. /// void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector voiceChNo, - const std::unordered_map voiceChData, uint32_t pSuperGroup, uint32_t netId, - uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions) + const std::unordered_map voiceChData, const ::lookups::VoiceChData controlChData, + uint32_t pSuperGroup, uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, + uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; yaml::Node p25Protocol = conf["protocols"]["p25"]; @@ -324,6 +329,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw std::unordered_map chData = std::unordered_map(voiceChData); m_affiliations.setRFChData(chData); + m_controlChData = controlChData; + // set the grant release callback m_affiliations.setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { // callback REST API to clear TG permit for the granted TG on the specified voice channel @@ -430,9 +437,11 @@ bool Control::processFrame(uint8_t* data, uint32_t len) LogMessage(LOG_RF, P25_TDU_STR ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_voice->m_rfFrames, m_voice->m_rfBits, m_voice->m_rfUndecodableLC, m_voice->m_rfErrs, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); - if (m_control) { - m_affiliations.releaseGrant(m_voice->m_rfLC.getDstId(), false); + m_affiliations.releaseGrant(m_voice->m_rfLC.getDstId(), false); + if (!m_control) { + notifyCC_ReleaseGrant(m_voice->m_rfLC.getDstId()); } + m_trunk->writeNet_TSDU_Call_Term(m_voice->m_rfLC.getSrcId(), m_voice->m_rfLC.getDstId()); writeRF_TDU(false); m_voice->m_lastDUID = P25_DUID_TDU; @@ -445,7 +454,7 @@ bool Control::processFrame(uint8_t* data, uint32_t len) m_tailOnIdle = true; m_rfTimeout.stop(); - m_queue.clear(); + m_txQueue.clear(); if (m_network != nullptr) m_network->resetP25(); @@ -463,7 +472,7 @@ bool Control::processFrame(uint8_t* data, uint32_t len) m_data->resetRF(); m_rfTimeout.stop(); - m_queue.clear(); + m_txQueue.clear(); return false; } @@ -595,11 +604,18 @@ bool Control::processFrame(uint8_t* data, uint32_t len) /// Length of frame data retreived. uint32_t Control::peekFrameLength() { - if (m_queue.isEmpty()) + if (m_txQueue.isEmpty() && m_txImmQueue.isEmpty()) return 0U; uint8_t len = 0U; - m_queue.peek(&len, 1U); + + // tx immediate queue takes priority + if (!m_txImmQueue.isEmpty()) { + m_txImmQueue.peek(&len, 1U); + } + else { + m_txQueue.peek(&len, 1U); + } return len; } @@ -613,12 +629,20 @@ uint32_t Control::getFrame(uint8_t* data) { assert(data != nullptr); - if (m_queue.isEmpty()) + if (m_txQueue.isEmpty() && m_txImmQueue.isEmpty()) return 0U; uint8_t len = 0U; - m_queue.getData(&len, 1U); - m_queue.getData(data, len); + + // tx immediate queue takes priority + if (!m_txImmQueue.isEmpty()) { + m_txImmQueue.getData(&len, 1U); + m_txImmQueue.getData(data, len); + } + else { + m_txQueue.getData(&len, 1U); + m_txQueue.getData(data, len); + } return len; } @@ -754,10 +778,7 @@ void Control::clock(uint32_t ms) } m_networkWatchdog.stop(); - - if (m_control) { - m_affiliations.releaseGrant(m_voice->m_netLC.getDstId(), false); - } + m_affiliations.releaseGrant(m_voice->m_netLC.getDstId(), false); if (m_dedicatedControl) { if (m_network != nullptr) @@ -775,7 +796,7 @@ void Control::clock(uint32_t ms) // reset states if we're in a rejected state if (m_rfState == RS_RF_REJECTED) { - m_queue.clear(); + m_txQueue.clear(); m_voice->resetRF(); m_voice->resetNet(); @@ -809,12 +830,50 @@ void Control::permittedTG(uint32_t dstId) } if (m_verbose) { - LogDebug(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId); + LogMessage(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId); } m_permittedDstId = dstId; } +/// +/// Releases a granted TG. +/// +/// +void Control::releaseGrantTG(uint32_t dstId) +{ + if (!m_control) { + return; + } + + if (m_affiliations.isGranted(dstId)) { + if (m_verbose) { + LogMessage(LOG_P25, "REST request, release TG grant, dstId = %u", dstId); + } + + m_affiliations.releaseGrant(dstId, false); + } +} + +/// +/// Touchs a granted TG to keep a channel grant alive. +/// +/// +void Control::touchGrantTG(uint32_t dstId) +{ + if (!m_control) { + return; + } + + if (m_affiliations.isGranted(dstId)) { + if (m_verbose) { + LogMessage(LOG_P25, "REST request, touch TG grant, dstId = %u", dstId); + } + + m_affiliations.touchGrant(dstId); + } +} + /// /// Flag indicating whether the processor or is busy or not. /// @@ -842,10 +901,11 @@ void Control::setDebugVerbose(bool debug, bool verbose) /// /// Add data frame to the data ring buffer. /// -/// -/// -/// -void Control::addFrame(const uint8_t* data, uint32_t length, bool net) +/// Frame data to add to Tx queue. +/// Length of data to add. +/// Flag indicating whether the data came from the network or not +/// Flag indicating whether or not the data is priority and is added to the immediate queue. +void Control::addFrame(const uint8_t* data, uint32_t length, bool net, bool imm) { assert(data != nullptr); @@ -857,12 +917,40 @@ void Control::addFrame(const uint8_t* data, uint32_t length, bool net) return; } - uint32_t space = m_queue.freeSpace(); + if (m_debug) { + Utils::symbols("!!! *Tx P25", data + 2U, length - 2U); + } + + // is this immediate data? + if (imm) { + // resize immediate queue if necessary (this shouldn't really ever happen) + uint32_t space = m_txImmQueue.freeSpace(); + if (space < (length + 1U)) { + if (!net) { + uint32_t queueLen = m_txImmQueue.length(); + m_txImmQueue.resize(queueLen + P25_LDU_FRAME_LENGTH_BYTES); + LogError(LOG_P25, "overflow in the P25 queue while writing imm data; queue free is %u, needed %u; resized was %u is %u", space, length, queueLen, m_txImmQueue.length()); + return; + } + else { + LogError(LOG_P25, "overflow in the P25 queue while writing imm network data; queue free is %u, needed %u", space, length); + return; + } + } + + uint8_t len = length; + m_txImmQueue.addData(&len, 1U); + m_txImmQueue.addData(data, len); + return; + } + + // resize immediate queue if necessary (this shouldn't really ever happen) + uint32_t space = m_txQueue.freeSpace(); if (space < (length + 1U)) { if (!net) { - uint32_t queueLen = m_queue.length(); - m_queue.resize(queueLen + P25_LDU_FRAME_LENGTH_BYTES); - LogError(LOG_P25, "overflow in the P25 queue while writing data; queue free is %u, needed %u; resized was %u is %u", space, length, queueLen, m_queue.length()); + uint32_t queueLen = m_txQueue.length(); + m_txQueue.resize(queueLen + P25_LDU_FRAME_LENGTH_BYTES); + LogError(LOG_P25, "overflow in the P25 queue while writing data; queue free is %u, needed %u; resized was %u is %u", space, length, queueLen, m_txQueue.length()); return; } else { @@ -871,13 +959,9 @@ void Control::addFrame(const uint8_t* data, uint32_t length, bool net) } } - if (m_debug) { - Utils::symbols("!!! *Tx P25", data + 2U, length - 2U); - } - uint8_t len = length; - m_queue.addData(&len, 1U); - m_queue.addData(data, len); + m_txQueue.addData(&len, 1U); + m_txQueue.addData(data, len); } #if ENABLE_DFSI_SUPPORT @@ -1042,57 +1126,181 @@ void Control::processNetwork() if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) return; - lc::LC control; - data::LowSpeedData lsd; - uint8_t duid; - uint8_t frameType; - - uint32_t length = 100U; + uint32_t length = 0U; bool ret = false; - uint8_t* data = m_network->readP25(ret, control, lsd, duid, frameType, length); + UInt8Array buffer = m_network->readP25(ret, length); if (!ret) return; if (length == 0U) return; - if (data == nullptr) { + if (buffer == nullptr) { m_network->resetP25(); return; } + uint8_t lco = buffer[4U]; + + uint32_t srcId = __GET_UINT16(buffer, 5U); + uint32_t dstId = __GET_UINT16(buffer, 8U); + + uint8_t MFId = buffer[15U]; + + uint8_t lsd1 = buffer[20U]; + uint8_t lsd2 = buffer[21U]; + + uint8_t duid = buffer[22U]; + uint8_t frameType = p25::P25_FT_DATA_UNIT; + + if (m_debug) { + LogDebug(LOG_NET, "P25, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", duid, lco, MFId, srcId, dstId, length); + } + + lc::LC control; + data::LowSpeedData lsd; + + // is this a LDU1, is this the first of a call? + if (duid == p25::P25_DUID_LDU1) { + frameType = buffer[180U]; + + if (m_debug) { + LogDebug(LOG_NET, "P25, frameType = $%02X", frameType); + } + + if (frameType == p25::P25_FT_HDU_VALID) { + uint8_t algId = buffer[181U]; + uint32_t kid = (buffer[182U] << 8) | (buffer[183U] << 0); + + // copy MI data + uint8_t mi[p25::P25_MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, p25::P25_MI_LENGTH_BYTES); + + for (uint8_t i = 0; i < p25::P25_MI_LENGTH_BYTES; i++) { + mi[i] = buffer[184U + i]; + } + + if (m_debug) { + LogDebug(LOG_NET, "P25, HDU algId = $%02X, kId = $%02X", algId, kid); + Utils::dump(1U, "P25 HDU Network MI", mi, p25::P25_MI_LENGTH_BYTES); + } + + control.setAlgId(algId); + control.setKId(kid); + control.setMI(mi); + } + } + + control.setLCO(lco); + control.setSrcId(srcId); + control.setDstId(dstId); + control.setMFId(MFId); + + lsd.setLSD1(lsd1); + lsd.setLSD2(lsd2); + + UInt8Array data; + uint8_t frameLength = buffer[23U]; + if (duid == p25::P25_DUID_PDU) { + frameLength = length; + data = std::unique_ptr(new uint8_t[length]); + ::memset(data.get(), 0x00U, length); + ::memcpy(data.get(), buffer.get(), length); + } + else { + if (frameLength <= 24) { + data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + } + else { + data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), buffer.get() + 24U, frameLength); + } + } + m_networkWatchdog.start(); if (m_debug) { - Utils::dump(2U, "!!! *P25 Network Frame", data, length); + Utils::dump(2U, "!!! *P25 Network Frame", data.get(), frameLength); } switch (duid) { case P25_DUID_HDU: case P25_DUID_LDU1: case P25_DUID_LDU2: - ret = m_voice->processNetwork(data, length, control, lsd, duid, frameType); + ret = m_voice->processNetwork(data.get(), frameLength, control, lsd, duid, frameType); break; case P25_DUID_TDU: case P25_DUID_TDULC: - m_voice->processNetwork(data, length, control, lsd, duid, frameType); + m_voice->processNetwork(data.get(), frameLength, control, lsd, duid, frameType); break; case P25_DUID_PDU: if (!m_dedicatedControl) - ret = m_data->processNetwork(data, length, control, lsd, duid); + ret = m_data->processNetwork(data.get(), frameLength, control, lsd, duid); else { if (m_voiceOnControl) { - ret = m_voice->processNetwork(data, length, control, lsd, duid, frameType); + ret = m_voice->processNetwork(data.get(), frameLength, control, lsd, duid, frameType); } } break; case P25_DUID_TSDU: - m_trunk->processNetwork(data, length, control, lsd, duid); + m_trunk->processNetwork(data.get(), frameLength, control, lsd, duid); break; } +} + +/// +/// Helper to send a REST API request to the CC to release a channel grant at the end of a call. +/// +/// +void Control::notifyCC_ReleaseGrant(uint32_t dstId) +{ + // callback REST API to release the granted TG on the specified control channel + if (!m_controlChData.address().empty() && m_controlChData.port() > 0) { + if (m_controlChData.address() == "127.0.0.1") { + // cowardly ignore trying to send release grants to ourselves + return; + } + + json::object req = json::object(); + int state = modem::DVM_STATE::STATE_P25; + req["state"].set(state); + req["dstId"].set(dstId); + + int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), + HTTP_PUT, PUT_RELEASE_TG, req, m_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_P25, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + } + } +} + +/// +/// Helper to send a REST API request to the CC to "touch" a channel grant to refresh grant timers. +/// +/// +void Control::notifyCC_TouchGrant(uint32_t dstId) +{ + // callback REST API to touch the granted TG on the specified control channel + if (!m_controlChData.address().empty() && m_controlChData.port() > 0) { + if (m_controlChData.address() == "127.0.0.1") { + // cowardly ignore trying to send touch grants to ourselves + return; + } - delete data; + json::object req = json::object(); + int state = modem::DVM_STATE::STATE_P25; + req["state"].set(state); + req["dstId"].set(dstId); + + int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), + HTTP_PUT, PUT_TOUCH_TG, req, m_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_P25, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + } + } } /// @@ -1111,7 +1319,7 @@ bool Control::writeRF_ControlData() // don't add any frames if the queue is full uint8_t len = (P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES * 2U) + 2U; - uint32_t space = m_queue.freeSpace(); + uint32_t space = m_txQueue.freeSpace(); if (space < (len + 1U)) { return false; } @@ -1144,7 +1352,7 @@ bool Control::writeRF_ControlEnd() if (!m_control) return false; - m_queue.clear(); + m_txQueue.clear(); m_ccPacketInterval.stop(); if (m_netState == RS_NET_IDLE && m_rfState == RS_RF_LISTENING) { diff --git a/src/p25/Control.h b/src/p25/Control.h index 8b34b801..16c6d4e1 100644 --- a/src/p25/Control.h +++ b/src/p25/Control.h @@ -37,11 +37,11 @@ #include "p25/packet/Data.h" #include "p25/packet/Voice.h" #include "p25/packet/Trunk.h" -#include "network/BaseNetwork.h" +#include "network/Network.h" #include "lookups/RSSIInterpolator.h" #include "lookups/IdenTableLookup.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" #include "p25/lookups/P25AffiliationLookup.h" #include "modem/Modem.h" #include "RingBuffer.h" @@ -73,9 +73,9 @@ namespace p25 class HOST_SW_API Control { public: /// Initializes a new instance of the Control class. - Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::BaseNetwork* network, + Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::Network* network, uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::RadioIdLookup* ridLookup, - ::lookups::TalkgroupIdLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, + ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, bool dumpPDUData, bool repeatPDU, bool dumpTSBKData, bool debug, bool verbose); /// Finalizes a instance of the Control class. ~Control(); @@ -85,8 +85,9 @@ namespace p25 /// Helper to set P25 configuration options. void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector voiceChNo, - const std::unordered_map voiceChData, uint32_t pSuperGroup, uint32_t netId, - uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions); + const std::unordered_map voiceChData, const ::lookups::VoiceChData controlChData, + uint32_t pSuperGroup, uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, + uint32_t channelNo, bool printOptions); /// Gets a flag indicating whether the P25 control channel is running. bool getCCRunning() { return m_ccRunning; } @@ -118,6 +119,11 @@ namespace p25 /// Permits a TGID on a non-authoritative host. void permittedTG(uint32_t dstId); + /// Releases a granted TG. + void releaseGrantTG(uint32_t dstId); + /// Touchs a granted TG to keep a channel grant alive. + void touchGrantTG(uint32_t dstId); + /// Gets instance of the NID class. NID nid() { return m_nid; } /// Gets instance of the Trunk class. @@ -154,7 +160,7 @@ namespace p25 uint32_t m_timeout; modem::Modem* m_modem; - network::BaseNetwork* m_network; + network::Network* m_network; bool m_inhibitIllegal; bool m_legacyGroupGrnt; @@ -169,12 +175,14 @@ namespace p25 ::lookups::IdenTableLookup* m_idenTable; ::lookups::RadioIdLookup* m_ridLookup; - ::lookups::TalkgroupIdLookup* m_tidLookup; + ::lookups::TalkgroupRulesLookup* m_tidLookup; lookups::P25AffiliationLookup m_affiliations; + ::lookups::VoiceChData m_controlChData; ::lookups::IdenTable m_idenEntry; - RingBuffer m_queue; + RingBuffer m_txImmQueue; + RingBuffer m_txQueue; RPT_RF_STATE m_rfState; uint32_t m_rfLastDstId; @@ -216,7 +224,7 @@ namespace p25 bool m_debug; /// Add data frame to the data ring buffer. - void addFrame(const uint8_t* data, uint32_t length, bool net = false); + void addFrame(const uint8_t* data, uint32_t length, bool net = false, bool imm = false); #if ENABLE_DFSI_SUPPORT /// Process a DFSI data frame from the RF interface. @@ -226,6 +234,11 @@ namespace p25 /// Process a data frames from the network. void processNetwork(); + /// Helper to send a REST API request to the CC to release a channel grant at the end of a call. + void notifyCC_ReleaseGrant(uint32_t dstId); + /// Helper to send a REST API request to the CC to "touch" a channel grant to refresh grant timers. + void notifyCC_TouchGrant(uint32_t dstId); + /// Helper to write control channel frame data. bool writeRF_ControlData(); /// Helper to write end of control channel frame data. diff --git a/src/p25/P25Defines.h b/src/p25/P25Defines.h index 2767ef65..3560c5ea 100644 --- a/src/p25/P25Defines.h +++ b/src/p25/P25Defines.h @@ -33,15 +33,6 @@ #include "Defines.h" -// Data Unit ID String(s) -#define P25_HDU_STR "P25_DUID_HDU (Header Data Unit)" -#define P25_TDU_STR "P25_DUID_TDU (Simple Terminator Data Unit)" -#define P25_LDU1_STR "P25_DUID_LDU1 (Logical Link Data Unit 1)" -#define P25_TSDU_STR "P25_DUID_TSDU (Trunking System Data Unit)" -#define P25_LDU2_STR "P25_DUID_LDU2 (Logical Link Data Unit 2)" -#define P25_PDU_STR "P25_DUID_PDU (Packet Data Unit)" -#define P25_TDULC_STR "P25_DUID_TDULC (Terminator Data Unit with Link Control)" - namespace p25 { // --------------------------------------------------------------------------- @@ -354,17 +345,24 @@ namespace p25 const uint8_t TSBK_OSP_MOT_CC_BSI = 0x0BU; // MOT CC BSI - Motorola / Control Channel Base Station Identifier const uint8_t TSBK_OSP_MOT_PSH_CCH = 0x0EU; // MOT PSH CCH - Motorola / Planned Control Channel Shutdown - // TSBK Motorola Outbound Signalling Packet (OSP) Opcode(s) + // TSBK DVM Outbound Signalling Packet (OSP) Opcode(s) const uint8_t TSBK_OSP_DVM_GIT_HASH = 0x3FU; // // Data Unit ID(s) const uint8_t P25_DUID_HDU = 0x00U; // Header Data Unit +#define P25_HDU_STR "P25_DUID_HDU (Header Data Unit)" const uint8_t P25_DUID_TDU = 0x03U; // Simple Terminator Data Unit +#define P25_TDU_STR "P25_DUID_TDU (Simple Terminator Data Unit)" const uint8_t P25_DUID_LDU1 = 0x05U; // Logical Link Data Unit 1 +#define P25_LDU1_STR "P25_DUID_LDU1 (Logical Link Data Unit 1)" const uint8_t P25_DUID_TSDU = 0x07U; // Trunking System Data Unit +#define P25_TSDU_STR "P25_DUID_TSDU (Trunking System Data Unit)" const uint8_t P25_DUID_LDU2 = 0x0AU; // Logical Link Data Unit 2 +#define P25_LDU2_STR "P25_DUID_LDU2 (Logical Link Data Unit 2)" const uint8_t P25_DUID_PDU = 0x0CU; // Packet Data Unit +#define P25_PDU_STR "P25_DUID_PDU (Packet Data Unit)" const uint8_t P25_DUID_TDULC = 0x0FU; // Terminator Data Unit with Link Control +#define P25_TDULC_STR "P25_DUID_TDULC (Terminator Data Unit with Link Control)" } // namespace p25 // --------------------------------------------------------------------------- diff --git a/src/p25/acl/AccessControl.cpp b/src/p25/acl/AccessControl.cpp index 5cdab832..e5346697 100644 --- a/src/p25/acl/AccessControl.cpp +++ b/src/p25/acl/AccessControl.cpp @@ -43,14 +43,14 @@ using namespace p25::acl; // --------------------------------------------------------------------------- RadioIdLookup* AccessControl::m_ridLookup; -TalkgroupIdLookup* AccessControl::m_tidLookup; +TalkgroupRulesLookup* AccessControl::m_tidLookup; /// /// Initializes the P25 access control. /// /// Instance of the RadioIdLookup class. -/// Instance of the TalkgroupIdLookup class. -void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup) +/// Instance of the TalkgroupRulesLookup class. +void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; @@ -99,8 +99,11 @@ bool AccessControl::validateTGId(uint32_t id) } // lookup TID and perform test for validity - TalkgroupId tid = m_tidLookup->find(id); - if (!tid.tgEnabled()) + TalkgroupRuleGroupVoice tid = m_tidLookup->find(id); + if (tid.isInvalid()) + return false; + + if (!tid.config().active()) return false; return true; diff --git a/src/p25/acl/AccessControl.h b/src/p25/acl/AccessControl.h index ccee3cd0..81e62c1c 100644 --- a/src/p25/acl/AccessControl.h +++ b/src/p25/acl/AccessControl.h @@ -33,7 +33,7 @@ #include "Defines.h" #include "lookups/RadioIdLookup.h" -#include "lookups/TalkgroupIdLookup.h" +#include "lookups/TalkgroupRulesLookup.h" namespace p25 { @@ -49,7 +49,7 @@ namespace p25 class HOST_SW_API AccessControl { public: /// Initializes the P25 access control. - static void init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup); + static void init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLookup); /// Helper to validate a source radio ID. static bool validateSrcId(uint32_t id); @@ -58,9 +58,9 @@ namespace p25 private: static RadioIdLookup* m_ridLookup; - static TalkgroupIdLookup* m_tidLookup; + static TalkgroupRulesLookup* m_tidLookup; }; - } // namespace ACL + } // namespace acl } // namespace p25 #endif // __P25_ACL__ACCESS_CONTROL_H__ diff --git a/src/p25/dfsi/packet/DFSITrunk.cpp b/src/p25/dfsi/packet/DFSITrunk.cpp index b78cafff..59c115db 100644 --- a/src/p25/dfsi/packet/DFSITrunk.cpp +++ b/src/p25/dfsi/packet/DFSITrunk.cpp @@ -113,7 +113,7 @@ void DFSITrunk::writeRF_TDULC(lc::TDULC* lc, bool noNetwork) /// /// /// -void DFSITrunk::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWrite, bool force) +void DFSITrunk::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWrite, bool force, bool imm) { if (!m_p25->m_control) return; @@ -157,10 +157,15 @@ void DFSITrunk::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBefor if (!noNetwork) writeNetworkRF(tsbk, data + 2U, true); + // bryanb: hack-o-ramma, for now -- we will force any immediate TSDUs as single-block + if (imm) { + force = true; + } + if (!force) { if (clearBeforeWrite) { - m_p25->m_modem->clearP25Data(); - m_p25->m_queue.clear(); + m_p25->m_modem->clearP25Frame(); + m_p25->m_txQueue.clear(); } } diff --git a/src/p25/dfsi/packet/DFSITrunk.h b/src/p25/dfsi/packet/DFSITrunk.h index b7393233..a722ca43 100644 --- a/src/p25/dfsi/packet/DFSITrunk.h +++ b/src/p25/dfsi/packet/DFSITrunk.h @@ -53,7 +53,7 @@ namespace p25 class HOST_SW_API DFSITrunk : public p25::packet::Trunk { public: /// Process a data frame from the RF interface. - virtual bool process(uint8_t* data, uint32_t len, std::unique_ptr preDecodedTSBK = nullptr); + bool process(uint8_t* data, uint32_t len, std::unique_ptr preDecodedTSBK = nullptr) override; protected: LC m_rfDFSILC; @@ -65,17 +65,17 @@ namespace p25 virtual ~DFSITrunk(); /// Helper to write a P25 TDU w/ link control packet. - virtual void writeRF_TDULC(lc::TDULC* lc, bool noNetwork); + void writeRF_TDULC(lc::TDULC* lc, bool noNetwork) override; /// Helper to write a single-block P25 TSDU packet. - virtual void writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWrite = false, bool force = false); + void writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWrite = false, bool force = false, bool imm = false) override; /// Helper to write a alternate multi-block trunking PDU packet. - virtual void writeRF_TSDU_AMBT(lc::AMBT* ambt, bool clearBeforeWrite = false); + void writeRF_TSDU_AMBT(lc::AMBT* ambt, bool clearBeforeWrite = false) override; /// Helper to write a network P25 TDU w/ link control packet. - //virtual void writeNet_TDULC(lc::TDULC lc); + //void writeNet_TDULC(lc::TDULC lc) override; /// Helper to write a network single-block P25 TSDU packet. - virtual void writeNet_TSDU(lc::TSBK* tsbk); + void writeNet_TSDU(lc::TSBK* tsbk) override; /// Helper to write start DFSI data. void writeRF_DFSI_Start(uint8_t type); diff --git a/src/p25/dfsi/packet/DFSIVoice.cpp b/src/p25/dfsi/packet/DFSIVoice.cpp index 5f247aa2..f71f4783 100644 --- a/src/p25/dfsi/packet/DFSIVoice.cpp +++ b/src/p25/dfsi/packet/DFSIVoice.cpp @@ -95,9 +95,9 @@ bool DFSIVoice::process(uint8_t* data, uint32_t len) if (frameType == P25_DFSI_VHDR2) { if (m_p25->m_rfState == RS_RF_LISTENING) { if (!m_p25->m_dedicatedControl) { - m_p25->m_modem->clearP25Data(); + m_p25->m_modem->clearP25Frame(); } - m_p25->m_queue.clear(); + m_p25->m_txQueue.clear(); resetRF(); resetNet(); } @@ -183,9 +183,9 @@ bool DFSIVoice::process(uint8_t* data, uint32_t len) // if this is a late entry call, clear states if (m_rfLastHDU.getDstId() == 0U) { if (!m_p25->m_dedicatedControl) { - m_p25->m_modem->clearP25Data(); + m_p25->m_modem->clearP25Frame(); } - m_p25->m_queue.clear(); + m_p25->m_txQueue.clear(); resetRF(); resetNet(); } @@ -418,6 +418,7 @@ bool DFSIVoice::process(uint8_t* data, uint32_t len) if (m_p25->m_control) { m_p25->m_affiliations.touchGrant(m_rfLC.getDstId()); + m_p25->notifyCC_TouchGrant(m_rfLC.getDstId()); } // single-channel trunking or voice on control support? @@ -571,8 +572,9 @@ bool DFSIVoice::process(uint8_t* data, uint32_t len) } else if (frameType == P25_DFSI_START_STOP) { if (m_rfDFSILC.getType() == P25_DFSI_TYPE_VOICE && m_rfDFSILC.getStartStop() == P25_DFSI_STOP_FLAG) { - if (m_p25->m_control) { + if (!m_p25->m_control) { m_p25->m_affiliations.releaseGrant(m_rfLC.getDstId(), false); + m_p25->notifyCC_ReleaseGrant(m_rfLC.getDstId()); } uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U]; @@ -632,11 +634,12 @@ bool DFSIVoice::process(uint8_t* data, uint32_t len) /// /// Buffer containing data frame. /// Length of data frame. -/// -/// -/// +/// Link Control Data. +/// Low Speed Data. +/// Data Unit ID. +/// Network Frame Type. /// -bool DFSIVoice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid) +bool DFSIVoice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid, uint8_t& frameType) { uint32_t count = 0U; @@ -745,9 +748,9 @@ bool DFSIVoice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, dat if (m_p25->m_netState == RS_NET_IDLE) { if (!m_p25->m_voiceOnControl) { - m_p25->m_modem->clearP25Data(); + m_p25->m_modem->clearP25Frame(); } - m_p25->m_queue.clear(); + m_p25->m_txQueue.clear(); resetRF(); resetNet(); @@ -771,8 +774,9 @@ bool DFSIVoice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, dat return false; } - if (m_p25->m_control) { + if (!m_p25->m_control) { m_p25->m_affiliations.releaseGrant(m_netLC.getDstId(), false); + m_p25->notifyCC_ReleaseGrant(m_netLC.getDstId()); } if (m_p25->m_netState != RS_NET_IDLE) { @@ -830,10 +834,6 @@ DFSIVoice::~DFSIVoice() /// void DFSIVoice::writeNet_TDU() { - if (m_p25->m_control) { - m_p25->m_affiliations.releaseGrant(m_netLC.getDstId(), false); - } - m_trunk->writeRF_DSFI_Stop(P25_DFSI_TYPE_VOICE); if (m_verbose) { @@ -929,6 +929,7 @@ void DFSIVoice::writeNet_LDU1() if (m_p25->m_control) { m_p25->m_affiliations.touchGrant(m_rfLC.getDstId()); + m_p25->notifyCC_TouchGrant(m_rfLC.getDstId()); } // set network and RF link control states diff --git a/src/p25/dfsi/packet/DFSIVoice.h b/src/p25/dfsi/packet/DFSIVoice.h index fd286620..3fa826ea 100644 --- a/src/p25/dfsi/packet/DFSIVoice.h +++ b/src/p25/dfsi/packet/DFSIVoice.h @@ -58,14 +58,14 @@ namespace p25 class HOST_SW_API DFSIVoice : public p25::packet::Voice { public: /// Resets the data states for the RF interface. - virtual void resetRF(); + void resetRF() override; /// Resets the data states for the network. - virtual void resetNet(); + void resetNet() override; /// Process a data frame from the RF interface. - virtual bool process(uint8_t* data, uint32_t len); + bool process(uint8_t* data, uint32_t len) override; /// Process a data frame from the network. - virtual bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); + bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid, uint8_t& frameType) override; protected: DFSITrunk* m_trunk; @@ -82,11 +82,11 @@ namespace p25 virtual ~DFSIVoice(); /// Helper to write a network P25 TDU packet. - virtual void writeNet_TDU(); + void writeNet_TDU() override; /// Helper to write a network P25 LDU1 packet. - virtual void writeNet_LDU1(); + void writeNet_LDU1() override; /// Helper to write a network P25 LDU1 packet. - virtual void writeNet_LDU2(); + void writeNet_LDU2() override; private: friend class packet::DFSITrunk; diff --git a/src/p25/lc/AMBT.h b/src/p25/lc/AMBT.h index 91194424..b0aaea4c 100644 --- a/src/p25/lc/AMBT.h +++ b/src/p25/lc/AMBT.h @@ -51,9 +51,9 @@ namespace p25 virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData) = 0; /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); protected: /// Internal helper to convert TSBK bytes to a 64-bit long value. diff --git a/src/p25/lc/TDULC.cpp b/src/p25/lc/TDULC.cpp index bff5413a..f976a247 100644 --- a/src/p25/lc/TDULC.cpp +++ b/src/p25/lc/TDULC.cpp @@ -146,9 +146,9 @@ ulong64_t TDULC::toValue(const uint8_t* rs) /// /// /// -std::unique_ptr TDULC::fromValue(const ulong64_t rsValue) +UInt8Array TDULC::fromValue(const ulong64_t rsValue) { - __UNIQUE_BUFFER(rs, uint8_t, P25_TDULC_LENGTH_BYTES); + __UNIQUE_UINT8_ARRAY(rs, P25_TDULC_LENGTH_BYTES); // split ulong64_t (8 byte) value into bytes rs[1U] = (uint8_t)((rsValue >> 56) & 0xFFU); diff --git a/src/p25/lc/TDULC.h b/src/p25/lc/TDULC.h index 7547e264..d130e6b0 100644 --- a/src/p25/lc/TDULC.h +++ b/src/p25/lc/TDULC.h @@ -124,7 +124,7 @@ namespace p25 /// Internal helper to convert RS bytes to a 64-bit long value. static ulong64_t toValue(const uint8_t* rs); /// Internal helper to convert a 64-bit long value to RS bytes. - static std::unique_ptr fromValue(const ulong64_t rsValue); + static UInt8Array fromValue(const ulong64_t rsValue); /// Internal helper to decode terminator data unit w/ link control. bool decode(const uint8_t* data, uint8_t* rs); diff --git a/src/p25/lc/TSBK.cpp b/src/p25/lc/TSBK.cpp index 4ab9b99a..d4c3a67a 100644 --- a/src/p25/lc/TSBK.cpp +++ b/src/p25/lc/TSBK.cpp @@ -126,6 +126,16 @@ TSBK::~TSBK() /* stub */ } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string TSBK::toString(bool isp) +{ + return std::string("TSBK_IOSP_UNKWN (Unknown TSBK)"); +} + /// /// Sets the callsign. /// @@ -179,9 +189,9 @@ ulong64_t TSBK::toValue(const uint8_t* tsbk) /// /// /// -std::unique_ptr TSBK::fromValue(const ulong64_t tsbkValue) +UInt8Array TSBK::fromValue(const ulong64_t tsbkValue) { - __UNIQUE_BUFFER(tsbk, uint8_t, P25_TSBK_LENGTH_BYTES); + __UNIQUE_UINT8_ARRAY(tsbk, P25_TSBK_LENGTH_BYTES); // split ulong64_t (8 byte) value into bytes tsbk[2U] = (uint8_t)((tsbkValue >> 56) & 0xFFU); diff --git a/src/p25/lc/TSBK.h b/src/p25/lc/TSBK.h index a9cb6838..022b9627 100644 --- a/src/p25/lc/TSBK.h +++ b/src/p25/lc/TSBK.h @@ -77,6 +77,9 @@ namespace p25 /// Encode a trunking signalling block. virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false) = 0; + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false); + /// Sets the flag indicating verbose log output. static void setVerbose(bool verbose) { m_verbose = verbose; } /// Sets the flag indicating CRC-errors should be warnings and not errors. @@ -158,7 +161,7 @@ namespace p25 /// Internal helper to convert TSBK bytes to a 64-bit long value. static ulong64_t toValue(const uint8_t* tsbk); /// Internal helper to convert a 64-bit long value to TSBK bytes. - static std::unique_ptr fromValue(const ulong64_t tsbkValue); + static UInt8Array fromValue(const ulong64_t tsbkValue); /// Internal helper to decode a trunking signalling block. bool decode(const uint8_t* data, uint8_t* tsbk, bool rawTSBK = false); diff --git a/src/p25/lc/tdulc/LC_ADJ_STS_BCAST.h b/src/p25/lc/tdulc/LC_ADJ_STS_BCAST.h index b92f5ee7..0caa673f 100644 --- a/src/p25/lc/tdulc/LC_ADJ_STS_BCAST.h +++ b/src/p25/lc/tdulc/LC_ADJ_STS_BCAST.h @@ -46,9 +46,9 @@ namespace p25 LC_ADJ_STS_BCAST(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); public: /// Adjacent site CFVA flags. diff --git a/src/p25/lc/tdulc/LC_CALL_TERM.h b/src/p25/lc/tdulc/LC_CALL_TERM.h index 2ad0ef5a..15f9753e 100644 --- a/src/p25/lc/tdulc/LC_CALL_TERM.h +++ b/src/p25/lc/tdulc/LC_CALL_TERM.h @@ -46,9 +46,9 @@ namespace p25 LC_CALL_TERM(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_CONV_FALLBACK.h b/src/p25/lc/tdulc/LC_CONV_FALLBACK.h index 6b08f9bc..9c011a18 100644 --- a/src/p25/lc/tdulc/LC_CONV_FALLBACK.h +++ b/src/p25/lc/tdulc/LC_CONV_FALLBACK.h @@ -46,9 +46,9 @@ namespace p25 LC_CONV_FALLBACK(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_GROUP.h b/src/p25/lc/tdulc/LC_GROUP.h index cf542e93..cc5769cb 100644 --- a/src/p25/lc/tdulc/LC_GROUP.h +++ b/src/p25/lc/tdulc/LC_GROUP.h @@ -46,9 +46,9 @@ namespace p25 LC_GROUP(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_GROUP_UPDT.h b/src/p25/lc/tdulc/LC_GROUP_UPDT.h index 42a7b0ef..d23b5835 100644 --- a/src/p25/lc/tdulc/LC_GROUP_UPDT.h +++ b/src/p25/lc/tdulc/LC_GROUP_UPDT.h @@ -46,9 +46,9 @@ namespace p25 LC_GROUP_UPDT(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_IDEN_UP.h b/src/p25/lc/tdulc/LC_IDEN_UP.h index 80f4fd26..e6f71df2 100644 --- a/src/p25/lc/tdulc/LC_IDEN_UP.h +++ b/src/p25/lc/tdulc/LC_IDEN_UP.h @@ -46,9 +46,9 @@ namespace p25 LC_IDEN_UP(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_NET_STS_BCAST.h b/src/p25/lc/tdulc/LC_NET_STS_BCAST.h index 55b16989..6bbfdbdb 100644 --- a/src/p25/lc/tdulc/LC_NET_STS_BCAST.h +++ b/src/p25/lc/tdulc/LC_NET_STS_BCAST.h @@ -46,9 +46,9 @@ namespace p25 LC_NET_STS_BCAST(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_PRIVATE.h b/src/p25/lc/tdulc/LC_PRIVATE.h index 59182db8..0de38d96 100644 --- a/src/p25/lc/tdulc/LC_PRIVATE.h +++ b/src/p25/lc/tdulc/LC_PRIVATE.h @@ -46,9 +46,9 @@ namespace p25 LC_PRIVATE(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_RFSS_STS_BCAST.h b/src/p25/lc/tdulc/LC_RFSS_STS_BCAST.h index df563b9a..023522a6 100644 --- a/src/p25/lc/tdulc/LC_RFSS_STS_BCAST.h +++ b/src/p25/lc/tdulc/LC_RFSS_STS_BCAST.h @@ -46,9 +46,9 @@ namespace p25 LC_RFSS_STS_BCAST(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_SYS_SRV_BCAST.h b/src/p25/lc/tdulc/LC_SYS_SRV_BCAST.h index 2f3b3df8..2879df8a 100644 --- a/src/p25/lc/tdulc/LC_SYS_SRV_BCAST.h +++ b/src/p25/lc/tdulc/LC_SYS_SRV_BCAST.h @@ -46,9 +46,9 @@ namespace p25 LC_SYS_SRV_BCAST(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h b/src/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h index 192acc5a..18ab0850 100644 --- a/src/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h +++ b/src/p25/lc/tdulc/LC_TEL_INT_VCH_USER.h @@ -46,9 +46,9 @@ namespace p25 LC_TEL_INT_VCH_USER(); /// Decode a terminator data unit w/ link control. - virtual bool decode(const uint8_t* data); + bool decode(const uint8_t* data); /// Encode a terminator data unit w/ link control. - virtual void encode(uint8_t* data); + void encode(uint8_t* data); }; } // namespace tdulc } // namespace lc diff --git a/src/p25/lc/tsbk/IOSP_ACK_RSP.cpp b/src/p25/lc/tsbk/IOSP_ACK_RSP.cpp index 363ec02d..5627e1f0 100644 --- a/src/p25/lc/tsbk/IOSP_ACK_RSP.cpp +++ b/src/p25/lc/tsbk/IOSP_ACK_RSP.cpp @@ -101,3 +101,16 @@ void IOSP_ACK_RSP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_ACK_RSP::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_ACK_RSP (Acknowledge Response - Unit)"); + else + return std::string("TSBK_IOSP_ACK_RSP (Acknowledge Response - FNE)"); +} diff --git a/src/p25/lc/tsbk/IOSP_ACK_RSP.h b/src/p25/lc/tsbk/IOSP_ACK_RSP.h index 45583c1c..8741d12e 100644 --- a/src/p25/lc/tsbk/IOSP_ACK_RSP.h +++ b/src/p25/lc/tsbk/IOSP_ACK_RSP.h @@ -47,9 +47,12 @@ namespace p25 IOSP_ACK_RSP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/IOSP_CALL_ALRT.cpp b/src/p25/lc/tsbk/IOSP_CALL_ALRT.cpp index 3de109df..0f9a07f8 100644 --- a/src/p25/lc/tsbk/IOSP_CALL_ALRT.cpp +++ b/src/p25/lc/tsbk/IOSP_CALL_ALRT.cpp @@ -90,3 +90,16 @@ void IOSP_CALL_ALRT::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_CALL_ALRT::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_CALL_ALRT (Call Alert Request)"); + else + return std::string("TSBK_IOSP_CALL_ALRT (Call Alert)"); +} diff --git a/src/p25/lc/tsbk/IOSP_CALL_ALRT.h b/src/p25/lc/tsbk/IOSP_CALL_ALRT.h index 93b52c4a..ea4d6dde 100644 --- a/src/p25/lc/tsbk/IOSP_CALL_ALRT.h +++ b/src/p25/lc/tsbk/IOSP_CALL_ALRT.h @@ -47,9 +47,12 @@ namespace p25 IOSP_CALL_ALRT(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/IOSP_EXT_FNCT.cpp b/src/p25/lc/tsbk/IOSP_EXT_FNCT.cpp index cc765db7..6e5be7c4 100644 --- a/src/p25/lc/tsbk/IOSP_EXT_FNCT.cpp +++ b/src/p25/lc/tsbk/IOSP_EXT_FNCT.cpp @@ -94,6 +94,19 @@ void IOSP_EXT_FNCT::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_EXT_FNCT::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_EXT_FNCT (Extended Function Response)"); + else + return std::string("TSBK_IOSP_EXT_FNCT (Extended Function Command)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/IOSP_EXT_FNCT.h b/src/p25/lc/tsbk/IOSP_EXT_FNCT.h index c95c4022..dc8f54d3 100644 --- a/src/p25/lc/tsbk/IOSP_EXT_FNCT.h +++ b/src/p25/lc/tsbk/IOSP_EXT_FNCT.h @@ -47,9 +47,12 @@ namespace p25 IOSP_EXT_FNCT(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Extended function opcode. diff --git a/src/p25/lc/tsbk/IOSP_GRP_AFF.cpp b/src/p25/lc/tsbk/IOSP_GRP_AFF.cpp index e80b8b5b..c534235f 100644 --- a/src/p25/lc/tsbk/IOSP_GRP_AFF.cpp +++ b/src/p25/lc/tsbk/IOSP_GRP_AFF.cpp @@ -96,6 +96,19 @@ void IOSP_GRP_AFF::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_GRP_AFF::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_GRP_AFF (Group Affiliation Request)"); + else + return std::string("TSBK_IOSP_GRP_AFF (Group Affiliation Response)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/IOSP_GRP_AFF.h b/src/p25/lc/tsbk/IOSP_GRP_AFF.h index beeb3173..e93d8924 100644 --- a/src/p25/lc/tsbk/IOSP_GRP_AFF.h +++ b/src/p25/lc/tsbk/IOSP_GRP_AFF.h @@ -47,9 +47,12 @@ namespace p25 IOSP_GRP_AFF(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Announcement group. diff --git a/src/p25/lc/tsbk/IOSP_GRP_VCH.cpp b/src/p25/lc/tsbk/IOSP_GRP_VCH.cpp index 87d86e1a..ce9b1d2f 100644 --- a/src/p25/lc/tsbk/IOSP_GRP_VCH.cpp +++ b/src/p25/lc/tsbk/IOSP_GRP_VCH.cpp @@ -101,3 +101,16 @@ void IOSP_GRP_VCH::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_GRP_VCH::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)"); + else + return std::string("TSBK_IOSP_GRP_VCH (Group Voice Channel Grant)"); +} diff --git a/src/p25/lc/tsbk/IOSP_GRP_VCH.h b/src/p25/lc/tsbk/IOSP_GRP_VCH.h index ac848786..c041bc35 100644 --- a/src/p25/lc/tsbk/IOSP_GRP_VCH.h +++ b/src/p25/lc/tsbk/IOSP_GRP_VCH.h @@ -47,9 +47,12 @@ namespace p25 IOSP_GRP_VCH(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/IOSP_MSG_UPDT.cpp b/src/p25/lc/tsbk/IOSP_MSG_UPDT.cpp index 26e57ddb..3872183b 100644 --- a/src/p25/lc/tsbk/IOSP_MSG_UPDT.cpp +++ b/src/p25/lc/tsbk/IOSP_MSG_UPDT.cpp @@ -94,6 +94,19 @@ void IOSP_MSG_UPDT::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_MSG_UPDT::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_MSG_UPDT (Message Update Request)"); + else + return std::string("TSBK_IOSP_MSG_UPDT (Message Update)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/IOSP_MSG_UPDT.h b/src/p25/lc/tsbk/IOSP_MSG_UPDT.h index 78304ed1..cdc3491b 100644 --- a/src/p25/lc/tsbk/IOSP_MSG_UPDT.h +++ b/src/p25/lc/tsbk/IOSP_MSG_UPDT.h @@ -47,9 +47,12 @@ namespace p25 IOSP_MSG_UPDT(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Status value. diff --git a/src/p25/lc/tsbk/IOSP_RAD_MON.cpp b/src/p25/lc/tsbk/IOSP_RAD_MON.cpp index a507dd82..d5b6b420 100644 --- a/src/p25/lc/tsbk/IOSP_RAD_MON.cpp +++ b/src/p25/lc/tsbk/IOSP_RAD_MON.cpp @@ -95,6 +95,19 @@ void IOSP_RAD_MON::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_RAD_MON::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_RAD_MON (Radio Unit Monitor Request)"); + else + return std::string("TSBK_IOSP_RAD_MON (Radio Unit Monitor Command)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/IOSP_RAD_MON.h b/src/p25/lc/tsbk/IOSP_RAD_MON.h index 111cc260..166dfae6 100644 --- a/src/p25/lc/tsbk/IOSP_RAD_MON.h +++ b/src/p25/lc/tsbk/IOSP_RAD_MON.h @@ -48,9 +48,12 @@ namespace p25 IOSP_RAD_MON(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Radio Unit Monitor. diff --git a/src/p25/lc/tsbk/IOSP_STS_UPDT.cpp b/src/p25/lc/tsbk/IOSP_STS_UPDT.cpp index 864d1f39..1841776e 100644 --- a/src/p25/lc/tsbk/IOSP_STS_UPDT.cpp +++ b/src/p25/lc/tsbk/IOSP_STS_UPDT.cpp @@ -94,6 +94,19 @@ void IOSP_STS_UPDT::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_STS_UPDT::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_STS_UPDT (Status Update Request)"); + else + return std::string("TSBK_IOSP_STS_UPDT (Status Update)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/IOSP_STS_UPDT.h b/src/p25/lc/tsbk/IOSP_STS_UPDT.h index f7772bb4..2b472ba3 100644 --- a/src/p25/lc/tsbk/IOSP_STS_UPDT.h +++ b/src/p25/lc/tsbk/IOSP_STS_UPDT.h @@ -47,9 +47,12 @@ namespace p25 IOSP_STS_UPDT(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Status value. diff --git a/src/p25/lc/tsbk/IOSP_UU_ANS.cpp b/src/p25/lc/tsbk/IOSP_UU_ANS.cpp index c290341d..1e09730d 100644 --- a/src/p25/lc/tsbk/IOSP_UU_ANS.cpp +++ b/src/p25/lc/tsbk/IOSP_UU_ANS.cpp @@ -98,3 +98,16 @@ void IOSP_UU_ANS::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_UU_ANS::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response)"); + else + return std::string("TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Request)"); +} diff --git a/src/p25/lc/tsbk/IOSP_UU_ANS.h b/src/p25/lc/tsbk/IOSP_UU_ANS.h index c5afaac1..a6e313dd 100644 --- a/src/p25/lc/tsbk/IOSP_UU_ANS.h +++ b/src/p25/lc/tsbk/IOSP_UU_ANS.h @@ -50,9 +50,12 @@ namespace p25 IOSP_UU_ANS& operator=(const IOSP_UU_ANS& data); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/IOSP_UU_VCH.cpp b/src/p25/lc/tsbk/IOSP_UU_VCH.cpp index f32247a5..cea30672 100644 --- a/src/p25/lc/tsbk/IOSP_UU_VCH.cpp +++ b/src/p25/lc/tsbk/IOSP_UU_VCH.cpp @@ -101,3 +101,16 @@ void IOSP_UU_VCH::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_UU_VCH::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)"); + else + return std::string("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Grant)"); +} diff --git a/src/p25/lc/tsbk/IOSP_UU_VCH.h b/src/p25/lc/tsbk/IOSP_UU_VCH.h index a6d098ef..6f778f8d 100644 --- a/src/p25/lc/tsbk/IOSP_UU_VCH.h +++ b/src/p25/lc/tsbk/IOSP_UU_VCH.h @@ -47,9 +47,12 @@ namespace p25 IOSP_UU_VCH(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/IOSP_U_REG.cpp b/src/p25/lc/tsbk/IOSP_U_REG.cpp index 31a732fd..3d8e8f22 100644 --- a/src/p25/lc/tsbk/IOSP_U_REG.cpp +++ b/src/p25/lc/tsbk/IOSP_U_REG.cpp @@ -93,3 +93,16 @@ void IOSP_U_REG::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string IOSP_U_REG::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_U_REG (Unit Registration Request)"); + else + return std::string("TSBK_IOSP_U_REG (Unit Registration Response)"); +} diff --git a/src/p25/lc/tsbk/IOSP_U_REG.h b/src/p25/lc/tsbk/IOSP_U_REG.h index 3d7edaa8..051f0c65 100644 --- a/src/p25/lc/tsbk/IOSP_U_REG.h +++ b/src/p25/lc/tsbk/IOSP_U_REG.h @@ -47,9 +47,12 @@ namespace p25 IOSP_U_REG(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/ISP_AUTH_FNE_RST.cpp b/src/p25/lc/tsbk/ISP_AUTH_FNE_RST.cpp index 398146b9..6bb751b4 100644 --- a/src/p25/lc/tsbk/ISP_AUTH_FNE_RST.cpp +++ b/src/p25/lc/tsbk/ISP_AUTH_FNE_RST.cpp @@ -88,6 +88,16 @@ void ISP_AUTH_FNE_RST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_AUTH_FNE_RST::toString(bool isp) +{ + return std::string("TSBK_ISP_AUTH_FNE_RST (Authentication FNE Result)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/ISP_AUTH_FNE_RST.h b/src/p25/lc/tsbk/ISP_AUTH_FNE_RST.h index 3ca48b5b..05ea2c80 100644 --- a/src/p25/lc/tsbk/ISP_AUTH_FNE_RST.h +++ b/src/p25/lc/tsbk/ISP_AUTH_FNE_RST.h @@ -46,9 +46,12 @@ namespace p25 ISP_AUTH_FNE_RST(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Flag indicating authentication was successful. diff --git a/src/p25/lc/tsbk/ISP_AUTH_RESP.cpp b/src/p25/lc/tsbk/ISP_AUTH_RESP.cpp index ce1a7f79..7c362c5a 100644 --- a/src/p25/lc/tsbk/ISP_AUTH_RESP.cpp +++ b/src/p25/lc/tsbk/ISP_AUTH_RESP.cpp @@ -104,6 +104,16 @@ void ISP_AUTH_RESP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_AUTH_RESP::toString(bool isp) +{ + return std::string("TSBK_ISP_AUTH_RESP (Authentication Response)"); +} + /// Gets the authentication result. /// void ISP_AUTH_RESP::getAuthRes(uint8_t* res) const diff --git a/src/p25/lc/tsbk/ISP_AUTH_RESP.h b/src/p25/lc/tsbk/ISP_AUTH_RESP.h index b8685c22..d4a8bd2d 100644 --- a/src/p25/lc/tsbk/ISP_AUTH_RESP.h +++ b/src/p25/lc/tsbk/ISP_AUTH_RESP.h @@ -48,9 +48,12 @@ namespace p25 ~ISP_AUTH_RESP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; /** Authentication data */ /// Gets the authentication result. diff --git a/src/p25/lc/tsbk/ISP_AUTH_SU_DMD.cpp b/src/p25/lc/tsbk/ISP_AUTH_SU_DMD.cpp index e26cc249..13cf99e3 100644 --- a/src/p25/lc/tsbk/ISP_AUTH_SU_DMD.cpp +++ b/src/p25/lc/tsbk/ISP_AUTH_SU_DMD.cpp @@ -83,3 +83,13 @@ void ISP_AUTH_SU_DMD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_AUTH_SU_DMD::toString(bool isp) +{ + return std::string("TSBK_ISP_AUTH_SU_DMD (Authentication SU Demand)"); +} diff --git a/src/p25/lc/tsbk/ISP_AUTH_SU_DMD.h b/src/p25/lc/tsbk/ISP_AUTH_SU_DMD.h index 8ead4ce7..79038469 100644 --- a/src/p25/lc/tsbk/ISP_AUTH_SU_DMD.h +++ b/src/p25/lc/tsbk/ISP_AUTH_SU_DMD.h @@ -46,9 +46,12 @@ namespace p25 ISP_AUTH_SU_DMD(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/ISP_CAN_SRV_REQ.cpp b/src/p25/lc/tsbk/ISP_CAN_SRV_REQ.cpp index c4af1dbc..d42f2f99 100644 --- a/src/p25/lc/tsbk/ISP_CAN_SRV_REQ.cpp +++ b/src/p25/lc/tsbk/ISP_CAN_SRV_REQ.cpp @@ -87,3 +87,13 @@ void ISP_CAN_SRV_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_CAN_SRV_REQ::toString(bool isp) +{ + return std::string("TSBK_ISP_CAN_SRV_REQ (Cancel Service Request)"); +} diff --git a/src/p25/lc/tsbk/ISP_CAN_SRV_REQ.h b/src/p25/lc/tsbk/ISP_CAN_SRV_REQ.h index b54f9814..372f8305 100644 --- a/src/p25/lc/tsbk/ISP_CAN_SRV_REQ.h +++ b/src/p25/lc/tsbk/ISP_CAN_SRV_REQ.h @@ -46,9 +46,12 @@ namespace p25 ISP_CAN_SRV_REQ(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.cpp b/src/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.cpp index f7ff4e55..0efb0652 100644 --- a/src/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.cpp +++ b/src/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.cpp @@ -98,3 +98,13 @@ void ISP_EMERG_ALRM_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_EMERG_ALRM_REQ::toString(bool isp) +{ + return std::string("TSBK_ISP_EMERG_ALRM_REQ (Emergency Alarm Request)"); +} diff --git a/src/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.h b/src/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.h index fab0c0b2..8c0404d4 100644 --- a/src/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.h +++ b/src/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.h @@ -46,9 +46,12 @@ namespace p25 ISP_EMERG_ALRM_REQ(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.cpp b/src/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.cpp index d8545419..b518e594 100644 --- a/src/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.cpp +++ b/src/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.cpp @@ -87,6 +87,16 @@ void ISP_GRP_AFF_Q_RSP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_GRP_AFF_Q_RSP::toString(bool isp) +{ + return std::string("TSBK_ISP_GRP_AFF_Q_RSP (Group Affiliation Query Response)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.h b/src/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.h index 5e6a8a01..9a36f6d6 100644 --- a/src/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.h +++ b/src/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.h @@ -46,9 +46,12 @@ namespace p25 ISP_GRP_AFF_Q_RSP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Announcement group. diff --git a/src/p25/lc/tsbk/ISP_LOC_REG_REQ.cpp b/src/p25/lc/tsbk/ISP_LOC_REG_REQ.cpp index 36cddc87..fa88546d 100644 --- a/src/p25/lc/tsbk/ISP_LOC_REG_REQ.cpp +++ b/src/p25/lc/tsbk/ISP_LOC_REG_REQ.cpp @@ -87,6 +87,16 @@ void ISP_LOC_REG_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_LOC_REG_REQ::toString(bool isp) +{ + return std::string("TSBK_ISP_LOC_REG_REQ (Location Registration Request)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/ISP_LOC_REG_REQ.h b/src/p25/lc/tsbk/ISP_LOC_REG_REQ.h index 08327923..5a6e9c2e 100644 --- a/src/p25/lc/tsbk/ISP_LOC_REG_REQ.h +++ b/src/p25/lc/tsbk/ISP_LOC_REG_REQ.h @@ -46,9 +46,12 @@ namespace p25 ISP_LOC_REG_REQ(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Location registration area. diff --git a/src/p25/lc/tsbk/ISP_SNDCP_CH_REQ.cpp b/src/p25/lc/tsbk/ISP_SNDCP_CH_REQ.cpp index 5729db1e..86a3220a 100644 --- a/src/p25/lc/tsbk/ISP_SNDCP_CH_REQ.cpp +++ b/src/p25/lc/tsbk/ISP_SNDCP_CH_REQ.cpp @@ -88,6 +88,16 @@ void ISP_SNDCP_CH_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_SNDCP_CH_REQ::toString(bool isp) +{ + return std::string("TSBK_ISP_SNDCP_CH_REQ (Authentication FNE Result)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/ISP_SNDCP_CH_REQ.h b/src/p25/lc/tsbk/ISP_SNDCP_CH_REQ.h index 5dac1cf6..d588c566 100644 --- a/src/p25/lc/tsbk/ISP_SNDCP_CH_REQ.h +++ b/src/p25/lc/tsbk/ISP_SNDCP_CH_REQ.h @@ -46,9 +46,12 @@ namespace p25 ISP_SNDCP_CH_REQ(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// SNDCP Data Service Options diff --git a/src/p25/lc/tsbk/ISP_U_DEREG_REQ.cpp b/src/p25/lc/tsbk/ISP_U_DEREG_REQ.cpp index f2bcbefe..d5b7563c 100644 --- a/src/p25/lc/tsbk/ISP_U_DEREG_REQ.cpp +++ b/src/p25/lc/tsbk/ISP_U_DEREG_REQ.cpp @@ -85,3 +85,13 @@ void ISP_U_DEREG_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis) /* stub */ } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string ISP_U_DEREG_REQ::toString(bool isp) +{ + return std::string("TSBK_ISP_U_DEREG_REQ (Unit De-Registration Request)"); +} diff --git a/src/p25/lc/tsbk/ISP_U_DEREG_REQ.h b/src/p25/lc/tsbk/ISP_U_DEREG_REQ.h index 4b5d3a3f..ccf8e533 100644 --- a/src/p25/lc/tsbk/ISP_U_DEREG_REQ.h +++ b/src/p25/lc/tsbk/ISP_U_DEREG_REQ.h @@ -46,9 +46,12 @@ namespace p25 ISP_U_DEREG_REQ(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/MBT_IOSP_ACK_RSP.cpp b/src/p25/lc/tsbk/MBT_IOSP_ACK_RSP.cpp index e1a48a4a..9f41b678 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_ACK_RSP.cpp +++ b/src/p25/lc/tsbk/MBT_IOSP_ACK_RSP.cpp @@ -89,3 +89,16 @@ void MBT_IOSP_ACK_RSP::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserD return; } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_IOSP_ACK_RSP::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_ACK_RSP (Acknowledge Response - Unit)"); + else + return std::string("TSBK_IOSP_ACK_RSP (Acknowledge Response - FNE)"); +} diff --git a/src/p25/lc/tsbk/MBT_IOSP_ACK_RSP.h b/src/p25/lc/tsbk/MBT_IOSP_ACK_RSP.h index f984b124..20832f17 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_ACK_RSP.h +++ b/src/p25/lc/tsbk/MBT_IOSP_ACK_RSP.h @@ -47,9 +47,12 @@ namespace p25 MBT_IOSP_ACK_RSP(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/MBT_IOSP_CALL_ALRT.cpp b/src/p25/lc/tsbk/MBT_IOSP_CALL_ALRT.cpp index eea65435..276521f8 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_CALL_ALRT.cpp +++ b/src/p25/lc/tsbk/MBT_IOSP_CALL_ALRT.cpp @@ -87,3 +87,16 @@ void MBT_IOSP_CALL_ALRT::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUse return; } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_IOSP_CALL_ALRT::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_CALL_ALRT (Call Alert Request)"); + else + return std::string("TSBK_IOSP_CALL_ALRT (Call Alert)"); +} diff --git a/src/p25/lc/tsbk/MBT_IOSP_CALL_ALRT.h b/src/p25/lc/tsbk/MBT_IOSP_CALL_ALRT.h index b2971347..974998e6 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_CALL_ALRT.h +++ b/src/p25/lc/tsbk/MBT_IOSP_CALL_ALRT.h @@ -47,9 +47,12 @@ namespace p25 MBT_IOSP_CALL_ALRT(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/MBT_IOSP_EXT_FNCT.cpp b/src/p25/lc/tsbk/MBT_IOSP_EXT_FNCT.cpp index 10d770ac..b7ebd006 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_EXT_FNCT.cpp +++ b/src/p25/lc/tsbk/MBT_IOSP_EXT_FNCT.cpp @@ -90,6 +90,19 @@ void MBT_IOSP_EXT_FNCT::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUser return; } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_IOSP_EXT_FNCT::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_EXT_FNCT (Extended Function Response)"); + else + return std::string("TSBK_IOSP_EXT_FNCT (Extended Function Command)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/MBT_IOSP_EXT_FNCT.h b/src/p25/lc/tsbk/MBT_IOSP_EXT_FNCT.h index 42aed45c..b76a6bda 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_EXT_FNCT.h +++ b/src/p25/lc/tsbk/MBT_IOSP_EXT_FNCT.h @@ -47,9 +47,12 @@ namespace p25 MBT_IOSP_EXT_FNCT(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Extended function opcode. diff --git a/src/p25/lc/tsbk/MBT_IOSP_GRP_AFF.cpp b/src/p25/lc/tsbk/MBT_IOSP_GRP_AFF.cpp index deda3da8..e32893ba 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_GRP_AFF.cpp +++ b/src/p25/lc/tsbk/MBT_IOSP_GRP_AFF.cpp @@ -87,3 +87,16 @@ void MBT_IOSP_GRP_AFF::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserD return; } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_IOSP_GRP_AFF::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_GRP_AFF (Group Affiliation Request)"); + else + return std::string("TSBK_IOSP_GRP_AFF (Group Affiliation Response)"); +} diff --git a/src/p25/lc/tsbk/MBT_IOSP_GRP_AFF.h b/src/p25/lc/tsbk/MBT_IOSP_GRP_AFF.h index 8970ab94..4fa513e1 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_GRP_AFF.h +++ b/src/p25/lc/tsbk/MBT_IOSP_GRP_AFF.h @@ -47,9 +47,12 @@ namespace p25 MBT_IOSP_GRP_AFF(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/MBT_IOSP_MSG_UPDT.cpp b/src/p25/lc/tsbk/MBT_IOSP_MSG_UPDT.cpp index 69cc425a..5b6396ae 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_MSG_UPDT.cpp +++ b/src/p25/lc/tsbk/MBT_IOSP_MSG_UPDT.cpp @@ -90,6 +90,19 @@ void MBT_IOSP_MSG_UPDT::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUser return; } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_IOSP_MSG_UPDT::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_MSG_UPDT (Message Update Request)"); + else + return std::string("TSBK_IOSP_MSG_UPDT (Message Update)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/MBT_IOSP_MSG_UPDT.h b/src/p25/lc/tsbk/MBT_IOSP_MSG_UPDT.h index ca48ee18..838837ff 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_MSG_UPDT.h +++ b/src/p25/lc/tsbk/MBT_IOSP_MSG_UPDT.h @@ -47,9 +47,12 @@ namespace p25 MBT_IOSP_MSG_UPDT(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Message value. diff --git a/src/p25/lc/tsbk/MBT_IOSP_STS_UPDT.cpp b/src/p25/lc/tsbk/MBT_IOSP_STS_UPDT.cpp index ea430e43..30c4f9d6 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_STS_UPDT.cpp +++ b/src/p25/lc/tsbk/MBT_IOSP_STS_UPDT.cpp @@ -90,6 +90,19 @@ void MBT_IOSP_STS_UPDT::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUser return; } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_IOSP_STS_UPDT::toString(bool isp) +{ + if (isp) + return std::string("TSBK_IOSP_STS_UPDT (Status Update Request)"); + else + return std::string("TSBK_IOSP_STS_UPDT (Status Update)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/MBT_IOSP_STS_UPDT.h b/src/p25/lc/tsbk/MBT_IOSP_STS_UPDT.h index 0f5c00a6..561fac52 100644 --- a/src/p25/lc/tsbk/MBT_IOSP_STS_UPDT.h +++ b/src/p25/lc/tsbk/MBT_IOSP_STS_UPDT.h @@ -47,9 +47,12 @@ namespace p25 MBT_IOSP_STS_UPDT(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Status value. diff --git a/src/p25/lc/tsbk/MBT_ISP_AUTH_RESP_M.cpp b/src/p25/lc/tsbk/MBT_ISP_AUTH_RESP_M.cpp index f1de2c54..443c6141 100644 --- a/src/p25/lc/tsbk/MBT_ISP_AUTH_RESP_M.cpp +++ b/src/p25/lc/tsbk/MBT_ISP_AUTH_RESP_M.cpp @@ -127,6 +127,16 @@ void MBT_ISP_AUTH_RESP_M::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUs return; } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_ISP_AUTH_RESP_M::toString(bool isp) +{ + return std::string("TSBK_ISP_AUTH_RESP_M (Authentication Response Mutual)"); +} + /// Gets the authentication result. /// void MBT_ISP_AUTH_RESP_M::getAuthRes(uint8_t* res) const diff --git a/src/p25/lc/tsbk/MBT_ISP_AUTH_RESP_M.h b/src/p25/lc/tsbk/MBT_ISP_AUTH_RESP_M.h index c3047b68..d0af35e4 100644 --- a/src/p25/lc/tsbk/MBT_ISP_AUTH_RESP_M.h +++ b/src/p25/lc/tsbk/MBT_ISP_AUTH_RESP_M.h @@ -48,9 +48,12 @@ namespace p25 ~MBT_ISP_AUTH_RESP_M(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; /** Authentication data */ /// Gets the authentication result. diff --git a/src/p25/lc/tsbk/MBT_ISP_AUTH_SU_DMD.cpp b/src/p25/lc/tsbk/MBT_ISP_AUTH_SU_DMD.cpp index 705137d8..daafd754 100644 --- a/src/p25/lc/tsbk/MBT_ISP_AUTH_SU_DMD.cpp +++ b/src/p25/lc/tsbk/MBT_ISP_AUTH_SU_DMD.cpp @@ -86,3 +86,13 @@ void MBT_ISP_AUTH_SU_DMD::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUs return; } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_ISP_AUTH_SU_DMD::toString(bool isp) +{ + return std::string("TSBK_ISP_AUTH_SU_DMD (Authentication SU Demand)"); +} diff --git a/src/p25/lc/tsbk/MBT_ISP_AUTH_SU_DMD.h b/src/p25/lc/tsbk/MBT_ISP_AUTH_SU_DMD.h index 53b3dafa..dfdf26a4 100644 --- a/src/p25/lc/tsbk/MBT_ISP_AUTH_SU_DMD.h +++ b/src/p25/lc/tsbk/MBT_ISP_AUTH_SU_DMD.h @@ -46,9 +46,12 @@ namespace p25 MBT_ISP_AUTH_SU_DMD(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/MBT_ISP_CAN_SRV_REQ.cpp b/src/p25/lc/tsbk/MBT_ISP_CAN_SRV_REQ.cpp index 4b6bb7fb..d0ec8407 100644 --- a/src/p25/lc/tsbk/MBT_ISP_CAN_SRV_REQ.cpp +++ b/src/p25/lc/tsbk/MBT_ISP_CAN_SRV_REQ.cpp @@ -91,3 +91,13 @@ void MBT_ISP_CAN_SRV_REQ::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUs return; } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_ISP_CAN_SRV_REQ::toString(bool isp) +{ + return std::string("TSBK_ISP_CAN_SRV_REQ (Cancel Service Request)"); +} diff --git a/src/p25/lc/tsbk/MBT_ISP_CAN_SRV_REQ.h b/src/p25/lc/tsbk/MBT_ISP_CAN_SRV_REQ.h index 07e500d5..6573bc6a 100644 --- a/src/p25/lc/tsbk/MBT_ISP_CAN_SRV_REQ.h +++ b/src/p25/lc/tsbk/MBT_ISP_CAN_SRV_REQ.h @@ -46,9 +46,12 @@ namespace p25 MBT_ISP_CAN_SRV_REQ(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/MBT_OSP_ADJ_STS_BCAST.cpp b/src/p25/lc/tsbk/MBT_OSP_ADJ_STS_BCAST.cpp index c9a14213..288c9417 100644 --- a/src/p25/lc/tsbk/MBT_OSP_ADJ_STS_BCAST.cpp +++ b/src/p25/lc/tsbk/MBT_OSP_ADJ_STS_BCAST.cpp @@ -112,6 +112,16 @@ void MBT_OSP_ADJ_STS_BCAST::encodeMBT(data::DataHeader& dataHeader, uint8_t* pdu AMBT::encode(dataHeader, pduUserData); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_OSP_ADJ_STS_BCAST::toString(bool isp) +{ + return std::string("TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Status Broadcast - Explicit)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/MBT_OSP_ADJ_STS_BCAST.h b/src/p25/lc/tsbk/MBT_OSP_ADJ_STS_BCAST.h index 5f42221f..858147de 100644 --- a/src/p25/lc/tsbk/MBT_OSP_ADJ_STS_BCAST.h +++ b/src/p25/lc/tsbk/MBT_OSP_ADJ_STS_BCAST.h @@ -46,9 +46,12 @@ namespace p25 MBT_OSP_ADJ_STS_BCAST(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /** Adjacent Site Data */ diff --git a/src/p25/lc/tsbk/MBT_OSP_AUTH_DMD.cpp b/src/p25/lc/tsbk/MBT_OSP_AUTH_DMD.cpp index 77d55eb4..0157b707 100644 --- a/src/p25/lc/tsbk/MBT_OSP_AUTH_DMD.cpp +++ b/src/p25/lc/tsbk/MBT_OSP_AUTH_DMD.cpp @@ -124,6 +124,16 @@ void MBT_OSP_AUTH_DMD::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserD AMBT::encode(dataHeader, pduUserData); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_OSP_AUTH_DMD::toString(bool isp) +{ + return std::string("TSBK_OSP_AUTH_DMD (Authentication Demand)"); +} + /// Sets the authentication random seed. /// void MBT_OSP_AUTH_DMD::setAuthRS(const uint8_t* rs) diff --git a/src/p25/lc/tsbk/MBT_OSP_AUTH_DMD.h b/src/p25/lc/tsbk/MBT_OSP_AUTH_DMD.h index 88917ecd..0132eaf7 100644 --- a/src/p25/lc/tsbk/MBT_OSP_AUTH_DMD.h +++ b/src/p25/lc/tsbk/MBT_OSP_AUTH_DMD.h @@ -48,9 +48,12 @@ namespace p25 ~MBT_OSP_AUTH_DMD(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; /// Sets the authentication random seed. void setAuthRS(const uint8_t* rs); diff --git a/src/p25/lc/tsbk/MBT_OSP_NET_STS_BCAST.cpp b/src/p25/lc/tsbk/MBT_OSP_NET_STS_BCAST.cpp index 22cdceac..37c86c2c 100644 --- a/src/p25/lc/tsbk/MBT_OSP_NET_STS_BCAST.cpp +++ b/src/p25/lc/tsbk/MBT_OSP_NET_STS_BCAST.cpp @@ -90,3 +90,13 @@ void MBT_OSP_NET_STS_BCAST::encodeMBT(data::DataHeader& dataHeader, uint8_t* pdu AMBT::encode(dataHeader, pduUserData); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_OSP_NET_STS_BCAST::toString(bool isp) +{ + return std::string("TSBK_OSP_NET_STS_BCAST (Network Status Broadcast - Explicit)"); +} diff --git a/src/p25/lc/tsbk/MBT_OSP_NET_STS_BCAST.h b/src/p25/lc/tsbk/MBT_OSP_NET_STS_BCAST.h index a296ec22..079518a9 100644 --- a/src/p25/lc/tsbk/MBT_OSP_NET_STS_BCAST.h +++ b/src/p25/lc/tsbk/MBT_OSP_NET_STS_BCAST.h @@ -46,9 +46,12 @@ namespace p25 MBT_OSP_NET_STS_BCAST(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/MBT_OSP_RFSS_STS_BCAST.cpp b/src/p25/lc/tsbk/MBT_OSP_RFSS_STS_BCAST.cpp index 0122882d..3c8f07ee 100644 --- a/src/p25/lc/tsbk/MBT_OSP_RFSS_STS_BCAST.cpp +++ b/src/p25/lc/tsbk/MBT_OSP_RFSS_STS_BCAST.cpp @@ -92,3 +92,13 @@ void MBT_OSP_RFSS_STS_BCAST::encodeMBT(data::DataHeader& dataHeader, uint8_t* pd AMBT::encode(dataHeader, pduUserData); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string MBT_OSP_RFSS_STS_BCAST::toString(bool isp) +{ + return std::string("TSBK_OSP_RFSS_STS_BCAST (RFSS Status Broadcast)"); +} diff --git a/src/p25/lc/tsbk/MBT_OSP_RFSS_STS_BCAST.h b/src/p25/lc/tsbk/MBT_OSP_RFSS_STS_BCAST.h index 72880f03..e95fc193 100644 --- a/src/p25/lc/tsbk/MBT_OSP_RFSS_STS_BCAST.h +++ b/src/p25/lc/tsbk/MBT_OSP_RFSS_STS_BCAST.h @@ -46,9 +46,12 @@ namespace p25 MBT_OSP_RFSS_STS_BCAST(); /// Decode a alternate trunking signalling block. - virtual bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); + bool decodeMBT(const data::DataHeader dataHeader, const data::DataBlock* blocks); /// Encode a alternate trunking signalling block. - virtual void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + void encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserData); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_ADJ_STS_BCAST.cpp b/src/p25/lc/tsbk/OSP_ADJ_STS_BCAST.cpp index b3c7a5bf..799db6c1 100644 --- a/src/p25/lc/tsbk/OSP_ADJ_STS_BCAST.cpp +++ b/src/p25/lc/tsbk/OSP_ADJ_STS_BCAST.cpp @@ -108,6 +108,16 @@ void OSP_ADJ_STS_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_ADJ_STS_BCAST::toString(bool isp) +{ + return std::string("TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Status Broadcast)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_ADJ_STS_BCAST.h b/src/p25/lc/tsbk/OSP_ADJ_STS_BCAST.h index 1074f8c2..4cccf37c 100644 --- a/src/p25/lc/tsbk/OSP_ADJ_STS_BCAST.h +++ b/src/p25/lc/tsbk/OSP_ADJ_STS_BCAST.h @@ -46,9 +46,12 @@ namespace p25 OSP_ADJ_STS_BCAST(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /** Adjacent Site Data */ diff --git a/src/p25/lc/tsbk/OSP_AUTH_FNE_RESP.cpp b/src/p25/lc/tsbk/OSP_AUTH_FNE_RESP.cpp index 3d07ac6e..be6173f5 100644 --- a/src/p25/lc/tsbk/OSP_AUTH_FNE_RESP.cpp +++ b/src/p25/lc/tsbk/OSP_AUTH_FNE_RESP.cpp @@ -99,6 +99,16 @@ void OSP_AUTH_FNE_RESP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_AUTH_FNE_RESP::toString(bool isp) +{ + return std::string("TSBK_OSP_AUTH_FNE_RESP (Authentication FNE Response)"); +} + /// Sets the authentication result. /// void OSP_AUTH_FNE_RESP::setAuthRes(const uint8_t* res) diff --git a/src/p25/lc/tsbk/OSP_AUTH_FNE_RESP.h b/src/p25/lc/tsbk/OSP_AUTH_FNE_RESP.h index 8753edae..49075e75 100644 --- a/src/p25/lc/tsbk/OSP_AUTH_FNE_RESP.h +++ b/src/p25/lc/tsbk/OSP_AUTH_FNE_RESP.h @@ -48,9 +48,12 @@ namespace p25 ~OSP_AUTH_FNE_RESP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; /** Authentication data */ /// Sets the authentication result. diff --git a/src/p25/lc/tsbk/OSP_DENY_RSP.cpp b/src/p25/lc/tsbk/OSP_DENY_RSP.cpp index 7b280500..c47fedff 100644 --- a/src/p25/lc/tsbk/OSP_DENY_RSP.cpp +++ b/src/p25/lc/tsbk/OSP_DENY_RSP.cpp @@ -111,3 +111,13 @@ void OSP_DENY_RSP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_DENY_RSP::toString(bool isp) +{ + return std::string("TSBK_OSP_DENY_RSP (Deny Response)"); +} diff --git a/src/p25/lc/tsbk/OSP_DENY_RSP.h b/src/p25/lc/tsbk/OSP_DENY_RSP.h index 6a44bbb0..cf8318db 100644 --- a/src/p25/lc/tsbk/OSP_DENY_RSP.h +++ b/src/p25/lc/tsbk/OSP_DENY_RSP.h @@ -46,9 +46,12 @@ namespace p25 OSP_DENY_RSP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_DVM_GIT_HASH.cpp b/src/p25/lc/tsbk/OSP_DVM_GIT_HASH.cpp index 2fbad9bb..7e94a317 100644 --- a/src/p25/lc/tsbk/OSP_DVM_GIT_HASH.cpp +++ b/src/p25/lc/tsbk/OSP_DVM_GIT_HASH.cpp @@ -87,3 +87,13 @@ void OSP_DVM_GIT_HASH::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_DVM_GIT_HASH::toString(bool isp) +{ + return std::string("TSBK_OSP_DVM_GIT_HASH (DVM Git Hash Identifier)"); +} diff --git a/src/p25/lc/tsbk/OSP_DVM_GIT_HASH.h b/src/p25/lc/tsbk/OSP_DVM_GIT_HASH.h index 57a969ff..433ef5c7 100644 --- a/src/p25/lc/tsbk/OSP_DVM_GIT_HASH.h +++ b/src/p25/lc/tsbk/OSP_DVM_GIT_HASH.h @@ -46,9 +46,12 @@ namespace p25 OSP_DVM_GIT_HASH(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cpp b/src/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cpp index b29b71c4..75a93727 100644 --- a/src/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cpp +++ b/src/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cpp @@ -95,3 +95,13 @@ void OSP_DVM_LC_CALL_TERM::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_DVM_LC_CALL_TERM::toString(bool isp) +{ + return std::string("LC_CALL_TERM (Call Termination or Cancellation)"); +} diff --git a/src/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.h b/src/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.h index 5c3ce70c..fbeb6324 100644 --- a/src/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.h +++ b/src/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.h @@ -46,9 +46,12 @@ namespace p25 OSP_DVM_LC_CALL_TERM(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_GRP_AFF_Q.cpp b/src/p25/lc/tsbk/OSP_GRP_AFF_Q.cpp index 5e81b781..dbf0b6f6 100644 --- a/src/p25/lc/tsbk/OSP_GRP_AFF_Q.cpp +++ b/src/p25/lc/tsbk/OSP_GRP_AFF_Q.cpp @@ -80,3 +80,13 @@ void OSP_GRP_AFF_Q::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_GRP_AFF_Q::toString(bool isp) +{ + return std::string("TSBK_OSP_GRP_AFF_Q (Group Affiliation Query)"); +} diff --git a/src/p25/lc/tsbk/OSP_GRP_AFF_Q.h b/src/p25/lc/tsbk/OSP_GRP_AFF_Q.h index 15be99a4..454ce393 100644 --- a/src/p25/lc/tsbk/OSP_GRP_AFF_Q.h +++ b/src/p25/lc/tsbk/OSP_GRP_AFF_Q.h @@ -46,9 +46,12 @@ namespace p25 OSP_GRP_AFF_Q(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.cpp b/src/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.cpp index ae9509f9..0245acf5 100644 --- a/src/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.cpp +++ b/src/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.cpp @@ -82,3 +82,13 @@ void OSP_GRP_VCH_GRANT_UPD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_GRP_VCH_GRANT_UPD::toString(bool isp) +{ + return std::string("TSBK_OSP_GRP_VCH_GRANT_UPD (Group Voice Channel Grant Update)"); +} diff --git a/src/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.h b/src/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.h index 9cc03211..ef8519e1 100644 --- a/src/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.h +++ b/src/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.h @@ -46,9 +46,12 @@ namespace p25 OSP_GRP_VCH_GRANT_UPD(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_IDEN_UP.cpp b/src/p25/lc/tsbk/OSP_IDEN_UP.cpp index 0d0d07fe..7f143524 100644 --- a/src/p25/lc/tsbk/OSP_IDEN_UP.cpp +++ b/src/p25/lc/tsbk/OSP_IDEN_UP.cpp @@ -108,3 +108,13 @@ void OSP_IDEN_UP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_IDEN_UP::toString(bool isp) +{ + return std::string("TSBK_OSP_IDEN_UP (Channel Identifier Update)"); +} diff --git a/src/p25/lc/tsbk/OSP_IDEN_UP.h b/src/p25/lc/tsbk/OSP_IDEN_UP.h index ddafaeae..0859e9b6 100644 --- a/src/p25/lc/tsbk/OSP_IDEN_UP.h +++ b/src/p25/lc/tsbk/OSP_IDEN_UP.h @@ -46,9 +46,12 @@ namespace p25 OSP_IDEN_UP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_IDEN_UP_VU.cpp b/src/p25/lc/tsbk/OSP_IDEN_UP_VU.cpp index 88a2e0bc..9f4f2569 100644 --- a/src/p25/lc/tsbk/OSP_IDEN_UP_VU.cpp +++ b/src/p25/lc/tsbk/OSP_IDEN_UP_VU.cpp @@ -102,3 +102,13 @@ void OSP_IDEN_UP_VU::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_IDEN_UP_VU::toString(bool isp) +{ + return std::string("TSBK_OSP_IDEN_UP_VU (Channel Identifier Update for VHF/UHF Bands)"); +} diff --git a/src/p25/lc/tsbk/OSP_IDEN_UP_VU.h b/src/p25/lc/tsbk/OSP_IDEN_UP_VU.h index c6532c64..e251b0c1 100644 --- a/src/p25/lc/tsbk/OSP_IDEN_UP_VU.h +++ b/src/p25/lc/tsbk/OSP_IDEN_UP_VU.h @@ -46,9 +46,12 @@ namespace p25 OSP_IDEN_UP_VU(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_LOC_REG_RSP.cpp b/src/p25/lc/tsbk/OSP_LOC_REG_RSP.cpp index 46bd7e35..dd915263 100644 --- a/src/p25/lc/tsbk/OSP_LOC_REG_RSP.cpp +++ b/src/p25/lc/tsbk/OSP_LOC_REG_RSP.cpp @@ -83,3 +83,13 @@ void OSP_LOC_REG_RSP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_LOC_REG_RSP::toString(bool isp) +{ + return std::string("TSBK_OSP_LOC_REG_RSP (Location Registration Response)"); +} diff --git a/src/p25/lc/tsbk/OSP_LOC_REG_RSP.h b/src/p25/lc/tsbk/OSP_LOC_REG_RSP.h index 5064c369..04cf4d05 100644 --- a/src/p25/lc/tsbk/OSP_LOC_REG_RSP.h +++ b/src/p25/lc/tsbk/OSP_LOC_REG_RSP.h @@ -46,9 +46,12 @@ namespace p25 OSP_LOC_REG_RSP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp b/src/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp index a1223a06..30668e2e 100644 --- a/src/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp +++ b/src/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp @@ -85,3 +85,13 @@ void OSP_MOT_CC_BSI::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_MOT_CC_BSI::toString(bool isp) +{ + return std::string("TSBK_OSP_MOT_CC_BSI (Motorola / Control Channel Base Station Identifier)"); +} diff --git a/src/p25/lc/tsbk/OSP_MOT_CC_BSI.h b/src/p25/lc/tsbk/OSP_MOT_CC_BSI.h index 98d5996f..c0629c30 100644 --- a/src/p25/lc/tsbk/OSP_MOT_CC_BSI.h +++ b/src/p25/lc/tsbk/OSP_MOT_CC_BSI.h @@ -46,9 +46,12 @@ namespace p25 OSP_MOT_CC_BSI(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_MOT_GRG_ADD.cpp b/src/p25/lc/tsbk/OSP_MOT_GRG_ADD.cpp index 12d1ee14..4973f3ed 100644 --- a/src/p25/lc/tsbk/OSP_MOT_GRG_ADD.cpp +++ b/src/p25/lc/tsbk/OSP_MOT_GRG_ADD.cpp @@ -99,6 +99,16 @@ void OSP_MOT_GRG_ADD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_MOT_GRG_ADD::toString(bool isp) +{ + return std::string("TSBK_OSP_MOT_GRG_ADD (Motorola / Group Regroup Add)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_MOT_GRG_ADD.h b/src/p25/lc/tsbk/OSP_MOT_GRG_ADD.h index 97d46f14..3b3e1301 100644 --- a/src/p25/lc/tsbk/OSP_MOT_GRG_ADD.h +++ b/src/p25/lc/tsbk/OSP_MOT_GRG_ADD.h @@ -46,9 +46,12 @@ namespace p25 OSP_MOT_GRG_ADD(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Patch super group ID. diff --git a/src/p25/lc/tsbk/OSP_MOT_GRG_DEL.cpp b/src/p25/lc/tsbk/OSP_MOT_GRG_DEL.cpp index 48399144..7681110c 100644 --- a/src/p25/lc/tsbk/OSP_MOT_GRG_DEL.cpp +++ b/src/p25/lc/tsbk/OSP_MOT_GRG_DEL.cpp @@ -104,6 +104,16 @@ void OSP_MOT_GRG_DEL::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_MOT_GRG_DEL::toString(bool isp) +{ + return std::string("TSBK_OSP_MOT_GRG_DEL (Motorola / Group Regroup Delete)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_MOT_GRG_DEL.h b/src/p25/lc/tsbk/OSP_MOT_GRG_DEL.h index 6a58d039..82b7c19c 100644 --- a/src/p25/lc/tsbk/OSP_MOT_GRG_DEL.h +++ b/src/p25/lc/tsbk/OSP_MOT_GRG_DEL.h @@ -46,9 +46,12 @@ namespace p25 OSP_MOT_GRG_DEL(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Patch super group ID. diff --git a/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.cpp b/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.cpp index 9282a67f..b29fba0f 100644 --- a/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.cpp +++ b/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.cpp @@ -92,6 +92,16 @@ void OSP_MOT_GRG_VCH_GRANT::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_MOT_GRG_VCH_GRANT::toString(bool isp) +{ + return std::string("TSBK_OSP_MOT_GRG_VCH_GRANT (Group Regroup Voice Channel Grant)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.h b/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.h index fd389469..130515af 100644 --- a/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.h +++ b/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.h @@ -46,9 +46,12 @@ namespace p25 OSP_MOT_GRG_VCH_GRANT(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Patch super group ID. diff --git a/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.cpp b/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.cpp index e409d4ab..2a3e6bb0 100644 --- a/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.cpp +++ b/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.cpp @@ -87,6 +87,16 @@ void OSP_MOT_GRG_VCH_UPD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_MOT_GRG_VCH_UPD::toString(bool isp) +{ + return std::string("TSBK_OSP_MOT_GRG_VCH_UPD (Group Regroup Voice Channel Grant Update)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.h b/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.h index f20eef09..cfe044c6 100644 --- a/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.h +++ b/src/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.h @@ -46,9 +46,12 @@ namespace p25 OSP_MOT_GRG_VCH_UPD(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// 1st patch group ID. diff --git a/src/p25/lc/tsbk/OSP_MOT_PSH_CCH.cpp b/src/p25/lc/tsbk/OSP_MOT_PSH_CCH.cpp index 6313a384..159d1982 100644 --- a/src/p25/lc/tsbk/OSP_MOT_PSH_CCH.cpp +++ b/src/p25/lc/tsbk/OSP_MOT_PSH_CCH.cpp @@ -78,3 +78,13 @@ void OSP_MOT_PSH_CCH::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_MOT_PSH_CCH::toString(bool isp) +{ + return std::string("TSBK_OSP_MOT_PSH_CCH (Motorola / Planned Control Channel Shutdown)"); +} diff --git a/src/p25/lc/tsbk/OSP_MOT_PSH_CCH.h b/src/p25/lc/tsbk/OSP_MOT_PSH_CCH.h index d0cdffd8..3310625c 100644 --- a/src/p25/lc/tsbk/OSP_MOT_PSH_CCH.h +++ b/src/p25/lc/tsbk/OSP_MOT_PSH_CCH.h @@ -46,9 +46,12 @@ namespace p25 OSP_MOT_PSH_CCH(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_NET_STS_BCAST.cpp b/src/p25/lc/tsbk/OSP_NET_STS_BCAST.cpp index d5e39c9e..ed84b17f 100644 --- a/src/p25/lc/tsbk/OSP_NET_STS_BCAST.cpp +++ b/src/p25/lc/tsbk/OSP_NET_STS_BCAST.cpp @@ -84,3 +84,13 @@ void OSP_NET_STS_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_NET_STS_BCAST::toString(bool isp) +{ + return std::string("TSBK_OSP_NET_STS_BCAST (Network Status Broadcast)"); +} diff --git a/src/p25/lc/tsbk/OSP_NET_STS_BCAST.h b/src/p25/lc/tsbk/OSP_NET_STS_BCAST.h index f40794d0..f01211ec 100644 --- a/src/p25/lc/tsbk/OSP_NET_STS_BCAST.h +++ b/src/p25/lc/tsbk/OSP_NET_STS_BCAST.h @@ -46,9 +46,12 @@ namespace p25 OSP_NET_STS_BCAST(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_QUE_RSP.cpp b/src/p25/lc/tsbk/OSP_QUE_RSP.cpp index 573de838..b6d427b5 100644 --- a/src/p25/lc/tsbk/OSP_QUE_RSP.cpp +++ b/src/p25/lc/tsbk/OSP_QUE_RSP.cpp @@ -111,3 +111,13 @@ void OSP_QUE_RSP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_QUE_RSP::toString(bool isp) +{ + return std::string("TSBK_OSP_QUE_RSP (Queued Response)"); +} diff --git a/src/p25/lc/tsbk/OSP_QUE_RSP.h b/src/p25/lc/tsbk/OSP_QUE_RSP.h index 7e1b3424..79e80619 100644 --- a/src/p25/lc/tsbk/OSP_QUE_RSP.h +++ b/src/p25/lc/tsbk/OSP_QUE_RSP.h @@ -46,9 +46,12 @@ namespace p25 OSP_QUE_RSP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_RFSS_STS_BCAST.cpp b/src/p25/lc/tsbk/OSP_RFSS_STS_BCAST.cpp index ee589903..eb12c452 100644 --- a/src/p25/lc/tsbk/OSP_RFSS_STS_BCAST.cpp +++ b/src/p25/lc/tsbk/OSP_RFSS_STS_BCAST.cpp @@ -87,3 +87,13 @@ void OSP_RFSS_STS_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_RFSS_STS_BCAST::toString(bool isp) +{ + return std::string("TSBK_OSP_RFSS_STS_BCAST (RFSS Status Broadcast)"); +} diff --git a/src/p25/lc/tsbk/OSP_RFSS_STS_BCAST.h b/src/p25/lc/tsbk/OSP_RFSS_STS_BCAST.h index 03c75335..03a98d7a 100644 --- a/src/p25/lc/tsbk/OSP_RFSS_STS_BCAST.h +++ b/src/p25/lc/tsbk/OSP_RFSS_STS_BCAST.h @@ -46,9 +46,12 @@ namespace p25 OSP_RFSS_STS_BCAST(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_SCCB.cpp b/src/p25/lc/tsbk/OSP_SCCB.cpp index 60935e15..cf30050d 100644 --- a/src/p25/lc/tsbk/OSP_SCCB.cpp +++ b/src/p25/lc/tsbk/OSP_SCCB.cpp @@ -97,6 +97,16 @@ void OSP_SCCB::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_SCCB::toString(bool isp) +{ + return std::string("TSBK_OSP_SCCB (Secondary Control Channel Broadcast)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_SCCB.h b/src/p25/lc/tsbk/OSP_SCCB.h index 440a61f6..e81a1267 100644 --- a/src/p25/lc/tsbk/OSP_SCCB.h +++ b/src/p25/lc/tsbk/OSP_SCCB.h @@ -46,9 +46,12 @@ namespace p25 OSP_SCCB(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// SCCB channel ID 1. diff --git a/src/p25/lc/tsbk/OSP_SCCB_EXP.cpp b/src/p25/lc/tsbk/OSP_SCCB_EXP.cpp index 695f927b..917579d1 100644 --- a/src/p25/lc/tsbk/OSP_SCCB_EXP.cpp +++ b/src/p25/lc/tsbk/OSP_SCCB_EXP.cpp @@ -95,6 +95,16 @@ void OSP_SCCB_EXP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_SCCB_EXP::toString(bool isp) +{ + return std::string("TSBK_OSP_SCCB_EXP (Secondary Control Channel Broadcast - Explicit)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_SCCB_EXP.h b/src/p25/lc/tsbk/OSP_SCCB_EXP.h index 1a07aaca..151f3c44 100644 --- a/src/p25/lc/tsbk/OSP_SCCB_EXP.h +++ b/src/p25/lc/tsbk/OSP_SCCB_EXP.h @@ -46,9 +46,12 @@ namespace p25 OSP_SCCB_EXP(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// SCCB channel ID 1. diff --git a/src/p25/lc/tsbk/OSP_SNDCP_CH_ANN.cpp b/src/p25/lc/tsbk/OSP_SNDCP_CH_ANN.cpp index e62e72a0..e2a6d76f 100644 --- a/src/p25/lc/tsbk/OSP_SNDCP_CH_ANN.cpp +++ b/src/p25/lc/tsbk/OSP_SNDCP_CH_ANN.cpp @@ -101,6 +101,16 @@ void OSP_SNDCP_CH_ANN::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_SNDCP_CH_ANN::toString(bool isp) +{ + return std::string("TSBK_OSP_SNDCP_CH_ANN (SNDCP Data Channel Announcement)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_SNDCP_CH_ANN.h b/src/p25/lc/tsbk/OSP_SNDCP_CH_ANN.h index 4cb039b0..81792a65 100644 --- a/src/p25/lc/tsbk/OSP_SNDCP_CH_ANN.h +++ b/src/p25/lc/tsbk/OSP_SNDCP_CH_ANN.h @@ -46,9 +46,12 @@ namespace p25 OSP_SNDCP_CH_ANN(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; private: bool m_sndcpAutoAccess; diff --git a/src/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp b/src/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp index 8fc3aa2e..28e70d97 100644 --- a/src/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp +++ b/src/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp @@ -97,6 +97,16 @@ void OSP_SNDCP_CH_GNT::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_SNDCP_CH_GNT::toString(bool isp) +{ + return std::string("TSBK_OSP_SNDCP_CH_GNT (SNDCP Data Channel Grant)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_SNDCP_CH_GNT.h b/src/p25/lc/tsbk/OSP_SNDCP_CH_GNT.h index 6a102e96..2f115530 100644 --- a/src/p25/lc/tsbk/OSP_SNDCP_CH_GNT.h +++ b/src/p25/lc/tsbk/OSP_SNDCP_CH_GNT.h @@ -46,9 +46,12 @@ namespace p25 OSP_SNDCP_CH_GNT(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// SNDCP Data Service Options diff --git a/src/p25/lc/tsbk/OSP_SYNC_BCAST.cpp b/src/p25/lc/tsbk/OSP_SYNC_BCAST.cpp index 5f823a80..d5af3016 100644 --- a/src/p25/lc/tsbk/OSP_SYNC_BCAST.cpp +++ b/src/p25/lc/tsbk/OSP_SYNC_BCAST.cpp @@ -128,6 +128,16 @@ void OSP_SYNC_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_SYNC_BCAST::toString(bool isp) +{ + return std::string("TSBK_OSP_SYNC_BCAST (Synchronization Broadcast)"); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/lc/tsbk/OSP_SYNC_BCAST.h b/src/p25/lc/tsbk/OSP_SYNC_BCAST.h index 63ea125a..70807eb4 100644 --- a/src/p25/lc/tsbk/OSP_SYNC_BCAST.h +++ b/src/p25/lc/tsbk/OSP_SYNC_BCAST.h @@ -46,9 +46,12 @@ namespace p25 OSP_SYNC_BCAST(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; public: /// Microslot count. diff --git a/src/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp b/src/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp index c6c8158e..95df43df 100644 --- a/src/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp +++ b/src/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp @@ -82,3 +82,13 @@ void OSP_SYS_SRV_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_SYS_SRV_BCAST::toString(bool isp) +{ + return std::string("TSBK_OSP_SYS_SRV_BCAST (System Service Broadcast)"); +} diff --git a/src/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h b/src/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h index 9a533489..728df9a9 100644 --- a/src/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h +++ b/src/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h @@ -46,9 +46,12 @@ namespace p25 OSP_SYS_SRV_BCAST(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_TIME_DATE_ANN.cpp b/src/p25/lc/tsbk/OSP_TIME_DATE_ANN.cpp index 992c4e06..3a68ddb9 100644 --- a/src/p25/lc/tsbk/OSP_TIME_DATE_ANN.cpp +++ b/src/p25/lc/tsbk/OSP_TIME_DATE_ANN.cpp @@ -127,3 +127,13 @@ void OSP_TIME_DATE_ANN::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_TIME_DATE_ANN::toString(bool isp) +{ + return std::string("TSBK_OSP_TIME_DATE_ANN (Time and Date Announcement)"); +} diff --git a/src/p25/lc/tsbk/OSP_TIME_DATE_ANN.h b/src/p25/lc/tsbk/OSP_TIME_DATE_ANN.h index 91e88535..04240899 100644 --- a/src/p25/lc/tsbk/OSP_TIME_DATE_ANN.h +++ b/src/p25/lc/tsbk/OSP_TIME_DATE_ANN.h @@ -46,9 +46,12 @@ namespace p25 OSP_TIME_DATE_ANN(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_TSBK_RAW.h b/src/p25/lc/tsbk/OSP_TSBK_RAW.h index 1432d66d..0d50501b 100644 --- a/src/p25/lc/tsbk/OSP_TSBK_RAW.h +++ b/src/p25/lc/tsbk/OSP_TSBK_RAW.h @@ -48,9 +48,9 @@ namespace p25 ~OSP_TSBK_RAW(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); /// Sets the TSBK to encode. void setTSBK(const uint8_t* tsbk); diff --git a/src/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp b/src/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp index e1833735..aa0ae652 100644 --- a/src/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp +++ b/src/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp @@ -92,3 +92,13 @@ void OSP_U_DEREG_ACK::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_U_DEREG_ACK::toString(bool isp) +{ + return std::string("TSBK_OSP_U_DEREG_ACK (Unit De-Registration Acknowledge)"); +} diff --git a/src/p25/lc/tsbk/OSP_U_DEREG_ACK.h b/src/p25/lc/tsbk/OSP_U_DEREG_ACK.h index 64448741..1f7fe7b2 100644 --- a/src/p25/lc/tsbk/OSP_U_DEREG_ACK.h +++ b/src/p25/lc/tsbk/OSP_U_DEREG_ACK.h @@ -46,9 +46,12 @@ namespace p25 OSP_U_DEREG_ACK(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lc/tsbk/OSP_U_REG_CMD.cpp b/src/p25/lc/tsbk/OSP_U_REG_CMD.cpp index a2a8d339..4452d4b3 100644 --- a/src/p25/lc/tsbk/OSP_U_REG_CMD.cpp +++ b/src/p25/lc/tsbk/OSP_U_REG_CMD.cpp @@ -80,3 +80,13 @@ void OSP_U_REG_CMD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } + +/// +/// Returns a string that represents the current TSBK. +/// +/// +/// +std::string OSP_U_REG_CMD::toString(bool isp) +{ + return std::string("TSBK_OSP_U_REG_CMD (Unit Registration Command)"); +} diff --git a/src/p25/lc/tsbk/OSP_U_REG_CMD.h b/src/p25/lc/tsbk/OSP_U_REG_CMD.h index ac38b7d4..af116dc9 100644 --- a/src/p25/lc/tsbk/OSP_U_REG_CMD.h +++ b/src/p25/lc/tsbk/OSP_U_REG_CMD.h @@ -46,9 +46,12 @@ namespace p25 OSP_U_REG_CMD(); /// Decode a trunking signalling block. - virtual bool decode(const uint8_t* data, bool rawTSBK = false); + bool decode(const uint8_t* data, bool rawTSBK = false); /// Encode a trunking signalling block. - virtual void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + void encode(uint8_t* data, bool rawTSBK = false, bool noTrellis = false); + + /// Returns a string that represents the current TSBK. + virtual std::string toString(bool isp = false) override; }; } // namespace tsbk } // namespace lc diff --git a/src/p25/lookups/P25AffiliationLookup.h b/src/p25/lookups/P25AffiliationLookup.h index fccbe775..2dfd526e 100644 --- a/src/p25/lookups/P25AffiliationLookup.h +++ b/src/p25/lookups/P25AffiliationLookup.h @@ -53,9 +53,9 @@ namespace p25 virtual ~P25AffiliationLookup(); /// Helper to release the channel grant for the destination ID. - virtual bool releaseGrant(uint32_t dstId, bool releaseAll); + bool releaseGrant(uint32_t dstId, bool releaseAll) override; /// Helper to release group affiliations. - virtual std::vector clearGroupAff(uint32_t dstId, bool releaseAll); + std::vector clearGroupAff(uint32_t dstId, bool releaseAll) override; protected: Control* m_p25; diff --git a/src/p25/packet/Data.cpp b/src/p25/packet/Data.cpp index 84c893fa..6b84bdc0 100644 --- a/src/p25/packet/Data.cpp +++ b/src/p25/packet/Data.cpp @@ -542,8 +542,8 @@ void Data::writeRF_PDU_User(data::DataHeader dataHeader, const uint8_t* pduUserD } if (clearBeforeWrite) { - m_p25->m_modem->clearP25Data(); - m_p25->m_queue.clear(); + m_p25->m_modem->clearP25Frame(); + m_p25->m_txQueue.clear(); } writeRF_PDU(data, bitLength); diff --git a/src/p25/packet/Trunk.cpp b/src/p25/packet/Trunk.cpp index 33285f46..c00ed0b3 100644 --- a/src/p25/packet/Trunk.cpp +++ b/src/p25/packet/Trunk.cpp @@ -56,7 +56,7 @@ using namespace p25::packet; // Make sure control data is supported. #define IS_SUPPORT_CONTROL_CHECK(_PCKT_STR, _PCKT, _SRCID) \ if (!m_p25->m_control) { \ - LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, unsupported service, srcId = %u", _SRCID); \ + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, unsupported service, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_TSDU_Deny(P25_WUID_FNE, _SRCID, P25_DENY_RSN_SYS_UNSUPPORTED_SVC, _PCKT); \ m_p25->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -65,7 +65,7 @@ using namespace p25::packet; // Validate the source RID. #define VALID_SRCID(_PCKT_STR, _PCKT, _SRCID) \ if (!acl::AccessControl::validateSrcId(_SRCID)) { \ - LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, RID rejection, srcId = %u", _SRCID); \ + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID rejection, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_TSDU_Deny(P25_WUID_FNE, _SRCID, P25_DENY_RSN_REQ_UNIT_NOT_VALID, _PCKT); \ denialInhibit(_SRCID); \ m_p25->m_rfState = RS_RF_REJECTED; \ @@ -75,7 +75,7 @@ using namespace p25::packet; // Validate the target RID. #define VALID_DSTID(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ if (!acl::AccessControl::validateSrcId(_DSTID)) { \ - LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, RID rejection, dstId = %u", _DSTID); \ + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID rejection, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ writeRF_TSDU_Deny(_SRCID, _DSTID, P25_DENY_RSN_TGT_UNIT_NOT_VALID, _PCKT); \ m_p25->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -84,7 +84,7 @@ using namespace p25::packet; // Validate the talkgroup ID. #define VALID_TGID(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ if (!acl::AccessControl::validateTGId(_DSTID)) { \ - LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, TGID rejection, dstId = %u", _DSTID); \ + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, TGID rejection, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ writeRF_TSDU_Deny(_SRCID, _DSTID, P25_DENY_RSN_TGT_GROUP_NOT_VALID, _PCKT); \ m_p25->m_rfState = RS_RF_REJECTED; \ return false; \ @@ -93,7 +93,7 @@ using namespace p25::packet; // Verify the source RID is registered. #define VERIFY_SRCID_REG(_PCKT_STR, _PCKT, _SRCID) \ if (!m_p25->m_affiliations.isUnitReg(_SRCID) && m_verifyReg) { \ - LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, RID not registered, srcId = %u", _SRCID); \ + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID not registered, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ writeRF_TSDU_Deny(P25_WUID_FNE, _SRCID, P25_DENY_RSN_REQ_UNIT_NOT_AUTH, _PCKT); \ writeRF_TSDU_U_Reg_Cmd(_SRCID); \ m_p25->m_rfState = RS_RF_REJECTED; \ @@ -103,7 +103,7 @@ using namespace p25::packet; // Verify the source RID is affiliated. #define VERIFY_SRCID_AFF(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ if (!m_p25->m_affiliations.isGroupAff(_SRCID, _DSTID) && m_verifyAff) { \ - LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, RID not affiliated to TGID, srcId = %u, dstId = %u", _SRCID, _DSTID); \ + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID not affiliated to TGID, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ writeRF_TSDU_Deny(_SRCID, _DSTID, P25_DENY_RSN_REQ_UNIT_NOT_AUTH, _PCKT); \ writeRF_TSDU_U_Reg_Cmd(_SRCID); \ m_p25->m_rfState = RS_RF_REJECTED; \ @@ -113,17 +113,41 @@ using namespace p25::packet; // Validate the source RID (network). #define VALID_SRCID_NET(_PCKT_STR, _SRCID) \ if (!acl::AccessControl::validateSrcId(_SRCID)) { \ - LogWarning(LOG_NET, P25_TSDU_STR ", " _PCKT_STR " denial, RID rejection, srcId = %u", _SRCID); \ + LogWarning(LOG_NET, P25_TSDU_STR ", %s denial, RID rejection, srcId = %u", _PCKT_STR.c_str(), _SRCID); \ return false; \ } // Validate the target RID (network). #define VALID_DSTID_NET(_PCKT_STR, _DSTID) \ if (!acl::AccessControl::validateSrcId(_DSTID)) { \ - LogWarning(LOG_NET, P25_TSDU_STR ", " _PCKT_STR " denial RID rejection, dstId = %u", _DSTID); \ + LogWarning(LOG_NET, P25_TSDU_STR ", %s denial RID rejection, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ return false; \ } +// Macro helper to verbose log a generic TSBK. +#define VERBOSE_LOG_TSBK(_PCKT_STR, _SRCID, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ + } + +// Macro helper to verbose log a generic TSBK. +#define VERBOSE_LOG_TSBK_DST(_PCKT_STR, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_RF, P25_TSDU_STR ", %s, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ + } + +// Macro helper to verbose log a generic network TSBK. +#define VERBOSE_LOG_TSBK_NET(_PCKT_STR, _SRCID, _DSTID) \ + if (m_verbose) { \ + LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ + } + +// Macro helper to verbose log a generic network TSBK. +#define DEBUG_LOG_TSBK(_PCKT_STR) \ + if (m_debug) { \ + LogMessage(LOG_RF, P25_TSDU_STR ", %s", _PCKT_STR.c_str()); \ + } + #define RF_TO_WRITE_NET(OSP) \ if (m_network != nullptr) { \ uint8_t _buf[P25_TSDU_FRAME_LENGTH_BYTES]; \ @@ -182,7 +206,7 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe m_p25->m_rfState = RS_RF_DATA; } - m_p25->m_queue.clear(); + m_p25->m_txQueue.clear(); if (preDecodedTSBK == nullptr) { tsbk = TSBKFactory::createTSBK(data + 2U); @@ -205,21 +229,18 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_GRP_VCH: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)", TSBK_IOSP_GRP_VCH, srcId); + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_IOSP_GRP_VCH, srcId); // validate the source RID - VALID_SRCID("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)", TSBK_IOSP_GRP_VCH, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_GRP_VCH, srcId); // validate the talkgroup ID - VALID_TGID("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)", TSBK_IOSP_GRP_VCH, srcId, dstId); + VALID_TGID(tsbk->toString(true), TSBK_IOSP_GRP_VCH, srcId, dstId); // verify the source RID is affiliated - VERIFY_SRCID_AFF("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)", TSBK_IOSP_GRP_VCH, srcId, dstId); - - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Request), srcId = %u, dstId = %u", srcId, dstId); - } + VERIFY_SRCID_AFF(tsbk->toString(true), TSBK_IOSP_GRP_VCH, srcId, dstId); + VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId); if (m_p25->m_authoritative) { uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag (tsbk->getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag @@ -234,21 +255,18 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_UU_VCH: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)", TSBK_IOSP_UU_VCH, srcId); + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_IOSP_UU_VCH, srcId); // validate the source RID - VALID_SRCID("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)", TSBK_IOSP_UU_VCH, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_UU_VCH, srcId); // validate the target RID - VALID_DSTID("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)", TSBK_IOSP_UU_VCH, srcId, dstId); + VALID_DSTID(tsbk->toString(true), TSBK_IOSP_UU_VCH, srcId, dstId); // verify the source RID is registered - VERIFY_SRCID_REG("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)", TSBK_IOSP_UU_VCH, srcId); - - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), srcId = %u, dstId = %u", srcId, dstId); - } + VERIFY_SRCID_REG(tsbk->toString(true), TSBK_IOSP_UU_VCH, srcId); + VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId); if (m_unitToUnitAvailCheck) { writeRF_TSDU_UU_Ans_Req(srcId, dstId); } @@ -268,18 +286,18 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_UU_ANS: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response)", TSBK_IOSP_UU_ANS, srcId); + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_IOSP_UU_ANS, srcId); // validate the source RID - VALID_SRCID("TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response)", TSBK_IOSP_UU_ANS, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_UU_ANS, srcId); // validate the target RID - VALID_DSTID("TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response)", TSBK_IOSP_UU_ANS, srcId, dstId); + VALID_DSTID(tsbk->toString(true), TSBK_IOSP_UU_ANS, srcId, dstId); IOSP_UU_ANS* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response), response = $%02X, srcId = %u, dstId = %u", - iosp->getResponse(), srcId, dstId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, response = $%02X, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), iosp->getResponse(), srcId, dstId); } if (iosp->getResponse() == P25_ANS_RSP_PROCEED) { @@ -308,10 +326,10 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_TELE_INT_ANS: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_TELE_INT_ANS (Telephone Interconnect Answer Response)", TSBK_IOSP_TELE_INT_ANS, srcId); + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_IOSP_TELE_INT_ANS, srcId); // validate the source RID - VALID_SRCID("TSBK_IOSP_TELE_INT_ANS (Telephone Interconnect Answer Response)", TSBK_IOSP_TELE_INT_ANS, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_TELE_INT_ANS, srcId); writeRF_TSDU_Deny(P25_WUID_FNE, srcId, P25_DENY_RSN_SYS_UNSUPPORTED_SVC, TSBK_IOSP_TELE_INT_ANS); } @@ -319,15 +337,15 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_ISP_SNDCP_CH_REQ: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_ISP_SNDCP_CH_REQ (SNDCP Channel Request)", TSBK_ISP_SNDCP_CH_REQ, srcId); + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_ISP_SNDCP_CH_REQ, srcId); // validate the source RID - VALID_SRCID("TSBK_ISP_SNDCP_CH_REQ (SNDCP Channel Request)", TSBK_ISP_SNDCP_CH_REQ, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_ISP_SNDCP_CH_REQ, srcId); ISP_SNDCP_CH_REQ* isp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_SNDCP_CH_REQ (SNDCP Channel Request), dataServiceOptions = $%02X, dataAccessControl = %u, srcId = %u", - isp->getDataServiceOptions(), isp->getDataAccessControl(), srcId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, dataServiceOptions = $%02X, dataAccessControl = %u, srcId = %u", + tsbk->toString(true).c_str(), isp->getDataServiceOptions(), isp->getDataAccessControl(), srcId); } if (m_sndcpChGrant) { @@ -341,11 +359,12 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_STS_UPDT: { // validate the source RID - VALID_SRCID("TSBK_IOSP_STS_UPDT (Status Update)", TSBK_IOSP_STS_UPDT, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_STS_UPDT, srcId); IOSP_STS_UPDT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_STS_UPDT (Status Update), status = $%02X, srcId = %u", iosp->getStatus(), srcId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, status = $%02X, srcId = %u", + tsbk->toString(true).c_str(), iosp->getStatus(), srcId); } RF_TO_WRITE_NET(iosp); @@ -360,12 +379,12 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_MSG_UPDT: { // validate the source RID - VALID_SRCID("TSBK_IOSP_MSG_UPDT (Message Update)", TSBK_IOSP_MSG_UPDT, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_MSG_UPDT, srcId); IOSP_MSG_UPDT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_MSG_UPDT (Message Update), message = $%02X, srcId = %u, dstId = %u", - iosp->getMessage(), srcId, dstId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, message = $%02X, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), iosp->getMessage(), srcId, dstId); } RF_TO_WRITE_NET(iosp); @@ -380,14 +399,15 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_RAD_MON: { // validate the source RID - VALID_SRCID("TSBK_IOSP_RAD_MON (Radio Monitor)", TSBK_IOSP_RAD_MON, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_RAD_MON, srcId); // validate the target RID - VALID_DSTID("TSBK_IOSP_RAD_MON (Radio Monitor)", TSBK_IOSP_RAD_MON, srcId, dstId); + VALID_DSTID(tsbk->toString(true), TSBK_IOSP_RAD_MON, srcId, dstId); IOSP_RAD_MON* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_RAD_MON_REQ (Radio Monitor), srcId = %u, dstId = %u", srcId, dstId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", + tsbk->toString(true).c_str(), srcId, dstId, iosp->getTxMult()); } ::ActivityLog("P25" , true , "radio monitor request from %u to %u" , srcId , dstId); @@ -398,15 +418,12 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_CALL_ALRT: { // validate the source RID - VALID_SRCID("TSBK_IOSP_CALL_ALRT (Call Alert)", TSBK_IOSP_CALL_ALRT, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_CALL_ALRT, srcId); // validate the target RID - VALID_DSTID("TSBK_IOSP_CALL_ALRT (Call Alert)", TSBK_IOSP_CALL_ALRT, srcId, dstId); - - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_CALL_ALRT (Call Alert), srcId = %u, dstId = %u", srcId, dstId); - } + VALID_DSTID(tsbk->toString(true), TSBK_IOSP_CALL_ALRT, srcId, dstId); + VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId); ::ActivityLog("P25", true, "call alert request from %u to %u", srcId, dstId); writeRF_TSDU_Call_Alrt(srcId, dstId); @@ -415,15 +432,15 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_ACK_RSP: { // validate the source RID - VALID_SRCID("TSBK_IOSP_ACK_RSP (Acknowledge Response)", TSBK_IOSP_ACK_RSP, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_ACK_RSP, srcId); // validate the target RID - VALID_DSTID("TSBK_IOSP_ACK_RSP (Acknowledge Response)", TSBK_IOSP_ACK_RSP, srcId, dstId); + VALID_DSTID(tsbk->toString(true), TSBK_IOSP_ACK_RSP, srcId, dstId); IOSP_ACK_RSP* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_ACK_RSP (Acknowledge Response), AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", - iosp->getAIV(), iosp->getService(), srcId, dstId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), iosp->getAIV(), iosp->getService(), srcId, dstId); } ::ActivityLog("P25", true, "ack response from %u to %u", srcId, dstId); @@ -443,8 +460,8 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe { ISP_CAN_SRV_REQ* isp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_CAN_SRV_REQ (Cancel Service Request), AIV = %u, serviceType = $%02X, reason = $%02X, srcId = %u, dstId = %u", - isp->getAIV(), isp->getService(), isp->getResponse(), srcId, dstId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, reason = $%02X, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), isp->getAIV(), isp->getService(), isp->getResponse(), srcId, dstId); } ::ActivityLog("P25", true, "cancel service request from %u", srcId); @@ -456,8 +473,8 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe { IOSP_EXT_FNCT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", - iosp->getExtendedFunction(), dstId, srcId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, op = $%02X, arg = %u, tgt = %u", + tsbk->toString(true).c_str(), iosp->getExtendedFunction(), dstId, srcId); } // generate activity log entry @@ -478,10 +495,7 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe { ISP_EMERG_ALRM_REQ* isp = static_cast(tsbk.get()); if (isp->getEmergency()) { - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_EMERG_ALRM_REQ (Emergency Alarm Request), srcId = %u, dstId = %u", - srcId, dstId); - } + VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId); ::ActivityLog("P25", true, "emergency alarm request request from %u", srcId); @@ -494,12 +508,9 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_GRP_AFF: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_GRP_AFF (Group Affiliation Request)", TSBK_IOSP_GRP_AFF, srcId); - - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Request), srcId = %u, dstId = %u", srcId, dstId); - } + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_IOSP_GRP_AFF, srcId); + VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId); if (m_p25->m_ackTSBKRequests) { writeRF_TSDU_ACK_FNE(srcId, TSBK_IOSP_GRP_AFF, true, true); } @@ -510,12 +521,12 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_ISP_GRP_AFF_Q_RSP: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_GRP_AFF (Group Affiliation Query Response)", TSBK_ISP_GRP_AFF_Q_RSP, srcId); + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_ISP_GRP_AFF_Q_RSP, srcId); ISP_GRP_AFF_Q_RSP* isp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Query Response), srcId = %u, dstId = %u, anncId = %u", srcId, dstId, - isp->getAnnounceGroup()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, anncId = %u", + tsbk->toString(true).c_str(), srcId, dstId, isp->getAnnounceGroup()); } ::ActivityLog("P25", true, "group affiliation query response from %u to %s %u", srcId, "TG ", dstId); @@ -524,14 +535,14 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_ISP_U_DEREG_REQ: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_ISP_U_DEREG_REQ (Unit Deregistration Request)", TSBK_ISP_U_DEREG_REQ, srcId); + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_ISP_U_DEREG_REQ, srcId); // validate the source RID - VALID_SRCID("TSBK_ISP_U_DEREG_REQ (Unit Deregistration Request)", TSBK_ISP_U_DEREG_REQ, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_ISP_U_DEREG_REQ, srcId); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_U_DEREG_REQ (Unit Deregistration Request) srcId = %u, sysId = $%03X, netId = $%05X", - srcId, tsbk->getSysId(), tsbk->getNetId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X, netId = $%05X", + tsbk->toString(true).c_str(), srcId, tsbk->getSysId(), tsbk->getNetId()); } if (m_p25->m_ackTSBKRequests) { @@ -544,11 +555,11 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_IOSP_U_REG: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_ISP_U_REG_REQ (Unit Registration Request)", TSBK_IOSP_U_REG, srcId); + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_IOSP_U_REG, srcId); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_U_REG_REQ (Unit Registration Request), srcId = %u, sysId = $%03X, netId = $%05X", - srcId, tsbk->getSysId(), tsbk->getNetId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X, netId = $%05X", + tsbk->toString(true).c_str(), srcId, tsbk->getSysId(), tsbk->getNetId()); } if (m_p25->m_ackTSBKRequests) { @@ -561,12 +572,9 @@ bool Trunk::process(uint8_t* data, uint32_t len, std::unique_ptr preDe case TSBK_ISP_LOC_REG_REQ: { // make sure control data is supported - IS_SUPPORT_CONTROL_CHECK("TSBK_ISP_LOC_REG_REQ (Location Registration Request)", TSBK_ISP_LOC_REG_REQ, srcId); - - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_LOC_REG_REQ (Location Registration Request), srcId = %u, dstId = %u", srcId, dstId); - } + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_ISP_LOC_REG_REQ, srcId); + VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId); writeRF_TSDU_Loc_Reg_Rsp(srcId, dstId, tsbk->getGroup()); } break; @@ -631,7 +639,7 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L } if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Status Broadcast), sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", + LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass()); } @@ -653,7 +661,7 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L } if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_OSP_SCCB_EXP (Secondary Control Channel Broadcast), sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", + LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass()); } @@ -678,11 +686,12 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L uint32_t chNo = tsbk->getGrpVchNo(); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", LC_CALL_TERM (Call Termination), chNo = %u, srcId = %u, dstId = %u", chNo, srcId, dstId); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, chNo = %u, srcId = %u, dstId = %u", + tsbk->toString().c_str(), chNo, srcId, dstId); } // is the specified channel granted? - if (m_p25->m_affiliations.isChBusy(chNo) && m_p25->m_affiliations.isGranted(dstId)) { + if (/*m_p25->m_affiliations.isChBusy(chNo) &&*/ m_p25->m_affiliations.isGranted(dstId)) { m_p25->m_affiliations.releaseGrant(dstId, false); } } @@ -704,11 +713,11 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L switch (tsbk->getLCO()) { case TSBK_IOSP_GRP_VCH: { - if (m_p25->m_dedicatedControl && !m_p25->m_voiceOnControl) { + if (m_p25->m_dedicatedControl) { if (!m_p25->m_affiliations.isGranted(dstId)) { if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Grant), emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", - tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), srcId, dstId); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), srcId, dstId); } uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag @@ -722,11 +731,11 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L return true; // don't allow this to write to the air case TSBK_IOSP_UU_VCH: { - if (m_p25->m_dedicatedControl && !m_p25->m_voiceOnControl) { + if (m_p25->m_dedicatedControl) { if (!m_p25->m_affiliations.isGranted(dstId)) { if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Grant), emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", - tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), srcId, dstId); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), srcId, dstId); } uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag @@ -743,26 +752,24 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L IOSP_UU_ANS* iosp = static_cast(tsbk.get()); if (iosp->getResponse() > 0U) { if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response), response = $%02X, srcId = %u, dstId = %u", - iosp->getResponse(), srcId, dstId); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, response = $%02X, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), iosp->getResponse(), srcId, dstId); } } else { - if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Request), srcId = %u, dstId = %u", srcId, dstId); - } + VERBOSE_LOG_TSBK_NET(tsbk->toString(), srcId, dstId); } } break; case TSBK_IOSP_STS_UPDT: { // validate the source RID - VALID_SRCID_NET("TSBK_IOSP_STS_UPDT (Status Update)", srcId); + VALID_SRCID_NET(tsbk->toString(), srcId); IOSP_STS_UPDT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_STS_UPDT (Status Update), status = $%02X, srcId = %u", - iosp->getStatus(), srcId); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, status = $%02X, srcId = %u", + tsbk->toString(true).c_str(), iosp->getStatus(), srcId); } ::ActivityLog("P25", false, "status update from %u", srcId); @@ -771,12 +778,12 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L case TSBK_IOSP_MSG_UPDT: { // validate the source RID - VALID_SRCID_NET("TSBK_IOSP_MSG_UPDT (Message Update)", srcId); + VALID_SRCID_NET(tsbk->toString(), srcId); IOSP_MSG_UPDT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_MSG_UPDT (Message Update), message = $%02X, srcId = %u, dstId = %u", - iosp->getMessage(), srcId, dstId); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, message = $%02X, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), iosp->getMessage(), srcId, dstId); } ::ActivityLog("P25", false, "message update from %u", srcId); @@ -785,15 +792,13 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L case TSBK_IOSP_RAD_MON: { // validate the source RID - VALID_SRCID("TSBK_ISP_RAD_MON_REQ (Radio Monitor)", TSBK_IOSP_RAD_MON, srcId); + VALID_SRCID(tsbk->toString(true), TSBK_IOSP_RAD_MON, srcId); // validate the target RID - VALID_DSTID("TSBK_ISP_RAD_MON_REQ (Radio monitor)", TSBK_IOSP_RAD_MON, srcId, dstId); + VALID_DSTID(tsbk->toString(true), TSBK_IOSP_RAD_MON, srcId, dstId); IOSP_RAD_MON* iosp = static_cast(tsbk.get()); - if (m_verbose) { - LogMessage(LOG_RF , P25_TSDU_STR ", TSBK_ISP_RAD_MON_REQ (Radio Monitor), srcId = %u, dstId = %u" , srcId , dstId); - } + VERBOSE_LOG_TSBK_NET(tsbk->toString(true), srcId, dstId); ::ActivityLog("P25" , true , "radio monitor request from %u to %u" , srcId , dstId); @@ -803,10 +808,10 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L case TSBK_IOSP_CALL_ALRT: { // validate the source RID - VALID_SRCID_NET("TSBK_IOSP_CALL_ALRT (Call Alert)", srcId); + VALID_SRCID_NET(tsbk->toString(true), srcId); // validate the target RID - VALID_DSTID_NET("TSBK_IOSP_CALL_ALRT (Call Alert)", dstId); + VALID_DSTID_NET(tsbk->toString(true), dstId); // validate source RID if (!acl::AccessControl::validateSrcId(srcId)) { @@ -814,25 +819,22 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L return false; } - if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_CALL_ALRT (Call Alert), srcId = %u, dstId = %u", srcId, dstId); - } - + VERBOSE_LOG_TSBK_NET(tsbk->toString(true), srcId, dstId); ::ActivityLog("P25", false, "call alert request from %u to %u", srcId, dstId); } break; case TSBK_IOSP_ACK_RSP: { // validate the source RID - VALID_SRCID_NET("TSBK_IOSP_ACK_RSP (Acknowledge Response)", srcId); + VALID_SRCID_NET(tsbk->toString(true), srcId); // validate the target RID - VALID_DSTID_NET("TSBK_IOSP_ACK_RSP (Acknowledge Response)", dstId); + VALID_DSTID_NET(tsbk->toString(true), dstId); IOSP_ACK_RSP* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_ACK_RSP (Acknowledge Response), AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", - iosp->getAIV(), iosp->getService(), dstId, srcId); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), iosp->getAIV(), iosp->getService(), dstId, srcId); } ::ActivityLog("P25", false, "ack response from %u to %u", srcId, dstId); @@ -841,12 +843,12 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L case TSBK_IOSP_EXT_FNCT: { // validate the target RID - VALID_DSTID_NET("TSBK_IOSP_EXT_FNCT (Extended Function)", dstId); + VALID_DSTID_NET(tsbk->toString(true), dstId); IOSP_EXT_FNCT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_EXT_FNCT (Extended Function), serviceType = $%02X, arg = %u, tgt = %u", - iosp->getService(), srcId, dstId); + LogMessage(LOG_NET, P25_TSDU_STR ", %s, serviceType = $%02X, arg = %u, tgt = %u", + tsbk->toString(true).c_str(), iosp->getService(), srcId, dstId); } } break; @@ -859,10 +861,7 @@ bool Trunk::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L // ignore a network deny command return true; // don't allow this to write to the air } else { - if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_ISP_EMERG_ALRM_REQ (Emergency Alarm Request), srcId = %u, dstId = %u", - srcId, dstId); - } + VERBOSE_LOG_TSBK_NET(tsbk->toString(true), srcId, dstId); return true; // don't allow this to write to the air } } @@ -924,11 +923,6 @@ void Trunk::writeAdjSSNetwork() } if (m_network != nullptr) { - if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Status Broadcast), network announce, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", - m_p25->m_siteData.sysId(), m_p25->m_siteData.rfssId(), m_p25->m_siteData.siteId(), m_p25->m_siteData.channelId(), m_p25->m_siteData.channelNo(), m_p25->m_siteData.serviceClass()); - } - uint8_t cfva = P25_CFVA_VALID; if (m_p25->m_control && m_p25->m_voiceOnControl) { cfva |= P25_CFVA_CONV; @@ -945,6 +939,11 @@ void Trunk::writeAdjSSNetwork() osp->setAdjSiteChnNo(m_p25->m_siteData.channelNo()); osp->setAdjSiteSvcClass(m_p25->m_siteData.serviceClass()); + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", %s, network announce, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", osp->toString().c_str(), + m_p25->m_siteData.sysId(), m_p25->m_siteData.rfssId(), m_p25->m_siteData.siteId(), m_p25->m_siteData.channelId(), m_p25->m_siteData.channelNo(), m_p25->m_siteData.serviceClass()); + } + RF_TO_WRITE_NET(osp.get()); } } @@ -972,7 +971,7 @@ void Trunk::clock(uint32_t ms) if (updateCnt == 0U) { SiteData siteData = m_adjSiteTable[siteId]; - LogWarning(LOG_NET, P25_TSDU_STR ", TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Status Broadcast), no data [FAILED], sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", + LogWarning(LOG_NET, "P25, Adjacent Site Status Expired, no data [FAILED], sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", siteData.sysId(), siteData.rfssId(), siteData.siteId(), siteData.channelId(), siteData.channelNo(), siteData.serviceClass()); } @@ -989,7 +988,7 @@ void Trunk::clock(uint32_t ms) if (updateCnt == 0U) { SiteData siteData = m_sccbTable[rfssId]; - LogWarning(LOG_NET, P25_TSDU_STR ", TSBK_OSP_SCCB (Secondary Control Channel Broadcast), no data [FAILED], sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", + LogWarning(LOG_NET, "P25, Secondary Control Channel Expired, no data [FAILED], sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", siteData.sysId(), siteData.rfssId(), siteData.siteId(), siteData.channelId(), siteData.channelNo(), siteData.serviceClass()); } @@ -1009,16 +1008,13 @@ void Trunk::clock(uint32_t ms) /// void Trunk::writeRF_TSDU_Call_Alrt(uint32_t srcId, uint32_t dstId) { - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_CALL_ALRT (Call Alert), srcId = %u, dstId = %u", srcId, dstId); - } - - ::ActivityLog("P25", true, "call alert request from %u to %u", srcId, dstId); - std::unique_ptr iosp = new_unique(IOSP_CALL_ALRT); iosp->setSrcId(srcId); iosp->setDstId(dstId); + VERBOSE_LOG_TSBK(iosp->toString(), srcId, dstId); + ::ActivityLog("P25", true, "call alert request from %u to %u", srcId, dstId); + writeRF_TSDU_SBF(iosp.get(), false); } @@ -1030,17 +1026,17 @@ void Trunk::writeRF_TSDU_Call_Alrt(uint32_t srcId, uint32_t dstId) /// void Trunk::writeRF_TSDU_Radio_Mon(uint32_t srcId, uint32_t dstId, uint8_t txMult) { - if (m_verbose) { - LogMessage(LOG_RF , P25_TSDU_STR ", TSBK_OSP_RAD_MON_CMD (Radio monitor), srcId = %u, dstId = %u, txMult = %u" , srcId, dstId, txMult); - } - - ::ActivityLog("P25" , true , "Radio Unit Monitor request from %u to %u" , srcId , dstId); - std::unique_ptr iosp = new_unique(IOSP_RAD_MON); iosp->setSrcId(srcId); iosp->setDstId(dstId); iosp->setTxMult(txMult); + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", iosp->toString().c_str(), srcId, dstId, txMult); + } + + ::ActivityLog("P25", true, "Radio Unit Monitor request from %u to %u", srcId, dstId); + writeRF_TSDU_SBF(iosp.get(), false); } @@ -1058,8 +1054,8 @@ void Trunk::writeRF_TSDU_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId) iosp->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", - iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, op = $%02X, arg = %u, tgt = %u", + iosp->toString().c_str(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); } // generate activity log entry @@ -1082,16 +1078,13 @@ void Trunk::writeRF_TSDU_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId) /// void Trunk::writeRF_TSDU_Grp_Aff_Q(uint32_t dstId) { - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_GRP_AFF_Q (Group Affiliation Query), dstId = %u", dstId); - } - - ::ActivityLog("P25", true, "group affiliation query command from %u to %u", P25_WUID_FNE, dstId); - std::unique_ptr osp = new_unique(OSP_GRP_AFF_Q); osp->setSrcId(P25_WUID_FNE); osp->setDstId(dstId); + VERBOSE_LOG_TSBK_DST(osp->toString(), dstId); + ::ActivityLog("P25", true, "group affiliation query command from %u to %u", P25_WUID_FNE, dstId); + writeRF_TSDU_SBF(osp.get(), true); } @@ -1101,16 +1094,13 @@ void Trunk::writeRF_TSDU_Grp_Aff_Q(uint32_t dstId) /// void Trunk::writeRF_TSDU_U_Reg_Cmd(uint32_t dstId) { - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_U_REG_CMD (Unit Registration Command), dstId = %u", dstId); - } - - ::ActivityLog("P25", true, "unit registration command from %u to %u", P25_WUID_FNE, dstId); - std::unique_ptr osp = new_unique(OSP_U_REG_CMD); osp->setSrcId(P25_WUID_FNE); osp->setDstId(dstId); + VERBOSE_LOG_TSBK_DST(osp->toString(), dstId); + ::ActivityLog("P25", true, "unit registration command from %u to %u", P25_WUID_FNE, dstId); + writeRF_TSDU_SBF(osp.get(), true); } @@ -1125,11 +1115,7 @@ void Trunk::writeRF_TSDU_Emerg_Alrm(uint32_t srcId, uint32_t dstId) isp->setSrcId(srcId); isp->setDstId(dstId); - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_EMERG_ALRM_REQ (Emergency Alarm Request), srcId = %u, dstId = %u", - srcId, dstId); - } - + VERBOSE_LOG_TSBK(isp->toString(), srcId, dstId); writeRF_TSDU_SBF(isp.get(), true); } @@ -1596,7 +1582,8 @@ void Trunk::writeRF_TDULC_ChanRelease(bool grp, uint32_t srcId, uint32_t dstId) /// /// /// -void Trunk::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWrite, bool force) +/// +void Trunk::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWrite, bool force, bool imm) { if (!m_p25->m_control) return; @@ -1633,6 +1620,11 @@ void Trunk::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWri if (!noNetwork) writeNetworkRF(tsbk, data + 2U, true); + // bryanb: hack-o-ramma, for now -- we will force any immediate TSDUs as single-block + if (imm) { + force = true; + } + if (!force) { if (m_p25->m_dedicatedControl && m_ctrlTSDUMBF) { writeRF_TSDU_MBF(tsbk, clearBeforeWrite); @@ -1645,8 +1637,8 @@ void Trunk::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWri } if (clearBeforeWrite) { - m_p25->m_modem->clearP25Data(); - m_p25->m_queue.clear(); + m_p25->m_modem->clearP25Frame(); + m_p25->m_txQueue.clear(); } } @@ -1654,11 +1646,10 @@ void Trunk::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWri data[0U] = modem::TAG_DATA; data[1U] = 0x00U; - m_p25->addFrame(data, P25_TSDU_FRAME_LENGTH_BYTES + 2U); + m_p25->addFrame(data, P25_TSDU_FRAME_LENGTH_BYTES + 2U, false, imm); } } - /// /// Helper to write a network single-block P25 TSDU packet. /// @@ -1789,8 +1780,8 @@ void Trunk::writeRF_TSDU_MBF(lc::TSBK* tsbk, bool clearBeforeWrite) data[1U] = 0x00U; if (clearBeforeWrite) { - m_p25->m_modem->clearP25Data(); - m_p25->m_queue.clear(); + m_p25->m_modem->clearP25Frame(); + m_p25->m_txQueue.clear(); } m_p25->addFrame(data, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES + 2U); @@ -1869,11 +1860,8 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) if (m_mbfGrpGrntCnt >= m_p25->m_affiliations.grantSize()) m_mbfGrpGrntCnt = 0U; - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_GRP_VCH_GRANT_UPD (Group Voice Channel Grant Update)"); - } - std::unique_ptr osp = new_unique(OSP_GRP_VCH_GRANT_UPD); + DEBUG_LOG_TSBK(osp->toString()); bool noData = false; uint8_t i = 0U; @@ -1917,10 +1905,6 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) break; case TSBK_OSP_IDEN_UP: { - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_IDEN_UP (Identity Update)"); - } - std::vector<::lookups::IdenTable> entries = m_p25->m_idenTable->list(); if (m_mbfIdenCnt >= entries.size()) m_mbfIdenCnt = 0U; @@ -1939,6 +1923,7 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) // handle 700/800/900 identities if (entry.baseFrequency() >= 762000000U) { std::unique_ptr osp = new_unique(OSP_IDEN_UP); + DEBUG_LOG_TSBK(osp->toString()); osp->siteIdenEntry(entry); // transmit channel ident broadcast @@ -1946,6 +1931,7 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) } else { std::unique_ptr osp = new_unique(OSP_IDEN_UP_VU); + DEBUG_LOG_TSBK(osp->toString()); osp->siteIdenEntry(entry); // transmit channel ident broadcast @@ -1959,20 +1945,14 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) } break; case TSBK_OSP_NET_STS_BCAST: - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_NET_STS_BCAST (Network Status Broadcast)"); - } - // transmit net status burst tsbk = new_unique(OSP_NET_STS_BCAST); + DEBUG_LOG_TSBK(tsbk->toString()); break; case TSBK_OSP_RFSS_STS_BCAST: - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_RFSS_STS_BCAST (RFSS Status Broadcast)"); - } - // transmit rfss status burst tsbk = new_unique(OSP_RFSS_STS_BCAST); + DEBUG_LOG_TSBK(tsbk->toString()); break; case TSBK_OSP_ADJ_STS_BCAST: // write ADJSS @@ -1980,11 +1960,8 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) if (m_mbfAdjSSCnt >= m_adjSiteTable.size()) m_mbfAdjSSCnt = 0U; - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Broadcast)"); - } - std::unique_ptr osp = new_unique(OSP_ADJ_STS_BCAST); + DEBUG_LOG_TSBK(osp->toString()); uint8_t i = 0U; for (auto entry : m_adjSiteTable) { @@ -2029,11 +2006,8 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) if (m_mbfSCCBCnt >= m_sccbTable.size()) m_mbfSCCBCnt = 0U; - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_SCCB_EXP (Secondary Control Channel Broadcast)"); - } - std::unique_ptr osp = new_unique(OSP_SCCB_EXP); + DEBUG_LOG_TSBK(osp->toString()); uint8_t i = 0U; for (auto entry : m_sccbTable) { @@ -2061,21 +2035,15 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) } break; case TSBK_OSP_SNDCP_CH_ANN: - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_SNDCP_CH_ANN (SNDCP Channel Announcement)"); - } - // transmit SNDCP announcement tsbk = new_unique(OSP_SNDCP_CH_ANN); + DEBUG_LOG_TSBK(tsbk->toString()); break; case TSBK_OSP_SYNC_BCAST: { - if (m_debug) { - LogMessage(LOG_RF , P25_TSDU_STR ", TSBK_OSP_SYNC_BCAST (Synchronization Broadcast)"); - } - // transmit sync broadcast std::unique_ptr osp = new_unique(OSP_SYNC_BCAST); + DEBUG_LOG_TSBK(osp->toString()); osp->setMicroslotCount(m_microslotCount); tsbk = std::move(osp); } @@ -2083,44 +2051,31 @@ void Trunk::queueRF_TSBK_Ctrl(uint8_t lco) case TSBK_OSP_TIME_DATE_ANN: { if (m_ctrlTimeDateAnn) { - if (m_debug) { - LogMessage(LOG_RF , P25_TSDU_STR ", TSBK_OSP_TIME_DATE_ANN (Time and Date Announcement)"); - } - // transmit time/date announcement - std::unique_ptr osp = new_unique(OSP_TIME_DATE_ANN); - tsbk = std::move(osp); + tsbk = new_unique(OSP_TIME_DATE_ANN); + DEBUG_LOG_TSBK(tsbk->toString()); } } break; /** Motorola CC data */ case TSBK_OSP_MOT_PSH_CCH: - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_MOT_PSH_CCH (Motorola Planned Shutdown)"); - } - // transmit motorola PSH CCH burst tsbk = new_unique(OSP_MOT_PSH_CCH); + DEBUG_LOG_TSBK(tsbk->toString()); break; case TSBK_OSP_MOT_CC_BSI: - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_MOT_CC_BSI (Motorola Control Channel BSI)"); - } - // transmit motorola CC BSI burst tsbk = new_unique(OSP_MOT_CC_BSI); + DEBUG_LOG_TSBK(tsbk->toString()); break; /** DVM CC data */ case TSBK_OSP_DVM_GIT_HASH: - if (m_debug) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_DVM_GIT_HASH (DVM Git Hash)"); - } - // transmit git hash burst tsbk = new_unique(OSP_DVM_GIT_HASH); + DEBUG_LOG_TSBK(tsbk->toString()); break; } @@ -2294,12 +2249,12 @@ bool Trunk::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOp iosp->setPriority(priority); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Grant), emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", - iosp->getEmergency(), iosp->getEncrypted(), iosp->getPriority(), iosp->getGrpVchNo(), iosp->getSrcId(), iosp->getDstId()); + LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + iosp->toString().c_str(), iosp->getEmergency(), iosp->getEncrypted(), iosp->getPriority(), iosp->getGrpVchNo(), iosp->getSrcId(), iosp->getDstId()); } // transmit group grant - writeRF_TSDU_SBF(iosp.get(), net); + writeRF_TSDU_SBF_Imm(iosp.get(), net); } else { if (!net) { @@ -2344,12 +2299,12 @@ bool Trunk::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOp iosp->setPriority(priority); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Grant), emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", - iosp->getEmergency(), iosp->getEncrypted(), iosp->getPriority(), iosp->getGrpVchNo(), iosp->getSrcId(), iosp->getDstId()); + LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + iosp->toString().c_str(), iosp->getEmergency(), iosp->getEncrypted(), iosp->getPriority(), iosp->getGrpVchNo(), iosp->getSrcId(), iosp->getDstId()); } // transmit private grant - writeRF_TSDU_SBF(iosp.get(), net); + writeRF_TSDU_SBF_Imm(iosp.get(), net); } } @@ -2424,8 +2379,8 @@ bool Trunk::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId, bool skip, } if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBK_OSP_SNDCP_CH_GNT (SNDCP Data Channel Grant), chNo = %u, dstId = %u", - osp->getDataChnNo(), osp->getSrcId()); + LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, chNo = %u, dstId = %u", + osp->toString().c_str(), osp->getDataChnNo(), osp->getSrcId()); } // transmit SNDCP grant @@ -2445,11 +2400,8 @@ void Trunk::writeRF_TSDU_UU_Ans_Req(uint32_t srcId, uint32_t dstId) iosp->setSrcId(srcId); iosp->setDstId(dstId); - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Request), srcId = %u, dstId = %u", srcId, dstId); - } - - writeRF_TSDU_SBF(iosp.get(), false); + VERBOSE_LOG_TSBK(iosp->toString(), srcId, dstId); + writeRF_TSDU_SBF_Imm(iosp.get(), false); } /// @@ -2470,11 +2422,11 @@ void Trunk::writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bool extended } if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_ACK_RSP (Acknowledge Response), AIV = %u, EX = %u, serviceType = $%02X, srcId = %u", - iosp->getAIV(), iosp->getEX(), iosp->getService(), srcId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, EX = %u, serviceType = $%02X, srcId = %u", + iosp->toString().c_str(), iosp->getAIV(), iosp->getEX(), iosp->getService(), srcId); } - writeRF_TSDU_SBF(iosp.get(), noNetwork); + writeRF_TSDU_SBF_Imm(iosp.get(), noNetwork); } /// @@ -2494,11 +2446,11 @@ void Trunk::writeRF_TSDU_Deny(uint32_t srcId, uint32_t dstId, uint8_t reason, ui osp->setResponse(reason); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_DENY_RSP (Deny Response), AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); } - writeRF_TSDU_SBF(osp.get(), false); + writeRF_TSDU_SBF_Imm(osp.get(), false); } /// @@ -2519,25 +2471,25 @@ bool Trunk::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstId) // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response) denial, RID rejection, srcId = %u", srcId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID rejection, srcId = %u", iosp->toString().c_str(), srcId); ::ActivityLog("P25", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); iosp->setResponse(P25_RSP_REFUSED); } // validate the source RID is registered if (!m_p25->m_affiliations.isUnitReg(srcId) && m_verifyReg) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response) denial, RID not registered, srcId = %u", srcId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID not registered, srcId = %u", iosp->toString().c_str(), srcId); ::ActivityLog("P25", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); iosp->setResponse(P25_RSP_REFUSED); } // validate the talkgroup ID if (dstId == 0U) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response), TGID 0, dstId = %u", dstId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s, TGID 0, dstId = %u", iosp->toString().c_str(), dstId); } else { if (!acl::AccessControl::validateTGId(dstId)) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response) denial, TGID rejection, dstId = %u", dstId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, TGID rejection, dstId = %u", iosp->toString().c_str(), dstId); ::ActivityLog("P25", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); iosp->setResponse(P25_RSP_DENY); } @@ -2545,8 +2497,8 @@ bool Trunk::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstId) if (iosp->getResponse() == P25_RSP_ACCEPT) { if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response), anncId = %u, srcId = %u, dstId = %u", - m_patchSuperGroup, srcId, dstId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, anncId = %u, srcId = %u, dstId = %u", + iosp->toString().c_str(), m_patchSuperGroup, srcId, dstId); } ::ActivityLog("P25", true, "group affiliation request from %u to %s %u", srcId, "TG ", dstId); @@ -2556,7 +2508,7 @@ bool Trunk::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstId) m_p25->m_affiliations.groupAff(srcId, dstId); } - writeRF_TSDU_SBF(iosp.get(), false); + writeRF_TSDU_SBF_Imm(iosp.get(), false); return ret; } @@ -2575,21 +2527,21 @@ void Trunk::writeRF_TSDU_U_Reg_Rsp(uint32_t srcId, uint32_t sysId) // validate the system ID if (sysId != m_p25->m_siteData.sysId()) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_U_REG (Unit Registration Response) denial, SYSID rejection, sysId = $%03X", sysId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, SYSID rejection, sysId = $%03X", iosp->toString().c_str(), sysId); ::ActivityLog("P25", true, "unit registration request from %u denied", srcId); iosp->setResponse(P25_RSP_DENY); } // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_U_REG (Unit Registration Response) denial, RID rejection, srcId = %u", srcId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID rejection, srcId = %u", iosp->toString().c_str(), srcId); ::ActivityLog("P25", true, "unit registration request from %u denied", srcId); iosp->setResponse(P25_RSP_REFUSED); } if (iosp->getResponse() == P25_RSP_ACCEPT) { if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_U_REG (Unit Registration Response), srcId = %u, sysId = $%03X", srcId, sysId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X", iosp->toString().c_str(), srcId, sysId); } ::ActivityLog("P25", true, "unit registration request from %u", srcId); @@ -2600,7 +2552,7 @@ void Trunk::writeRF_TSDU_U_Reg_Rsp(uint32_t srcId, uint32_t sysId) } } - writeRF_TSDU_SBF(iosp.get(), true); + writeRF_TSDU_SBF_Imm(iosp.get(), true); // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { @@ -2620,18 +2572,18 @@ void Trunk::writeRF_TSDU_U_Dereg_Ack(uint32_t srcId) dereged = m_p25->m_affiliations.unitDereg(srcId); if (dereged) { - if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_U_DEREG_REQ (Unit Deregistration Ack) srcId = %u", srcId); - } - - ::ActivityLog("P25", true, "unit deregistration request from %u", srcId); - std::unique_ptr osp = new_unique(OSP_U_DEREG_ACK); osp->setMFId(m_lastMFID); osp->setSrcId(P25_WUID_FNE); osp->setDstId(srcId); - writeRF_TSDU_SBF(osp.get(), false); + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u", osp->toString().c_str(), srcId); + } + + ::ActivityLog("P25", true, "unit deregistration request from %u", srcId); + + writeRF_TSDU_SBF_Imm(osp.get(), false); } } @@ -2652,11 +2604,11 @@ void Trunk::writeRF_TSDU_Queue(uint32_t srcId, uint32_t dstId, uint8_t reason, u osp->setResponse(reason); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_QUE_RSP (Queue Response), AIV = %u, reason = $%02X, srcId = %u, dstId = %u", - osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); } - writeRF_TSDU_SBF(osp.get(), false); + writeRF_TSDU_SBF_Imm(osp.get(), false); } /// @@ -2677,14 +2629,14 @@ bool Trunk::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, bool grp) // validate the source RID if (!acl::AccessControl::validateSrcId(srcId)) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_OSP_LOC_REG_RSP (Location Registration Response) denial, RID rejection, srcId = %u", srcId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID rejection, srcId = %u", osp->toString().c_str(), srcId); ::ActivityLog("P25", true, "location registration request from %u denied", srcId); osp->setResponse(P25_RSP_REFUSED); } // validate the source RID is registered if (!m_p25->m_affiliations.isUnitReg(srcId)) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_OSP_LOC_REG_RSP (Location Registration Response) denial, RID not registered, srcId = %u", srcId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, RID not registered, srcId = %u", osp->toString().c_str(), srcId); ::ActivityLog("P25", true, "location registration request from %u denied", srcId); writeRF_TSDU_U_Reg_Cmd(srcId); return false; @@ -2693,11 +2645,11 @@ bool Trunk::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, bool grp) // validate the talkgroup ID if (grp) { if (dstId == 0U) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_OSP_LOC_REG_RSP (Location Registration Response), TGID 0, dstId = %u", dstId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s, TGID 0, dstId = %u", osp->toString().c_str(), dstId); } else { if (!acl::AccessControl::validateTGId(dstId)) { - LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_OSP_LOC_REG_RSP (Location Registration Response) denial, TGID rejection, dstId = %u", dstId); + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, TGID rejection, dstId = %u", osp->toString().c_str(), dstId); ::ActivityLog("P25", true, "location registration request from %u to %s %u denied", srcId, "TG ", dstId); osp->setResponse(P25_RSP_DENY); } @@ -2706,14 +2658,14 @@ bool Trunk::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, bool grp) if (osp->getResponse() == P25_RSP_ACCEPT) { if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_LOC_REG_RSP (Location Registration Response), srcId = %u, dstId = %u", srcId, dstId); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", osp->toString().c_str(), srcId, dstId); } ::ActivityLog("P25", true, "location registration request from %u", srcId); ret = true; } - writeRF_TSDU_SBF(osp.get(), false); + writeRF_TSDU_SBF_Imm(osp.get(), false); return ret; } @@ -2724,6 +2676,11 @@ bool Trunk::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, bool grp) /// bool Trunk::writeNet_TSDU_Call_Term(uint32_t srcId, uint32_t dstId) { + // is the specified channel granted? + if (m_p25->m_affiliations.isGranted(dstId)) { + m_p25->m_affiliations.releaseGrant(dstId, false); + } + std::unique_ptr osp = new_unique(OSP_DVM_LC_CALL_TERM); osp->setGrpVchId(m_p25->m_siteData.channelId()); osp->setGrpVchNo(m_p25->m_siteData.channelNo()); diff --git a/src/p25/packet/Trunk.h b/src/p25/packet/Trunk.h index 1d8a0068..43aad598 100644 --- a/src/p25/packet/Trunk.h +++ b/src/p25/packet/Trunk.h @@ -171,14 +171,16 @@ namespace p25 void writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS); /// Helper to write a P25 TDU w/ link control packet. - void writeRF_TDULC(lc::TDULC* lc, bool noNetwork); + virtual void writeRF_TDULC(lc::TDULC* lc, bool noNetwork); /// Helper to write a network P25 TDU w/ link control packet. virtual void writeNet_TDULC(lc::TDULC* lc); /// Helper to write a P25 TDU w/ link control channel release packet. void writeRF_TDULC_ChanRelease(bool grp, uint32_t srcId, uint32_t dstId); + /// Helper to write a immediate single-block P25 TSDU packet. + virtual void writeRF_TSDU_SBF_Imm(lc::TSBK *tsbk, bool noNetwork) { writeRF_TSDU_SBF(tsbk, noNetwork, false, false, true); } /// Helper to write a single-block P25 TSDU packet. - virtual void writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWrite = false, bool force = false); + virtual void writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool clearBeforeWrite = false, bool force = false, bool imm = false); /// Helper to write a network single-block P25 TSDU packet. virtual void writeNet_TSDU(lc::TSBK* tsbk); /// Helper to write a multi-block (3-block) P25 TSDU packet. diff --git a/src/p25/packet/Voice.cpp b/src/p25/packet/Voice.cpp index 9b0ad479..8ba4902d 100644 --- a/src/p25/packet/Voice.cpp +++ b/src/p25/packet/Voice.cpp @@ -132,9 +132,9 @@ bool Voice::process(uint8_t* data, uint32_t len) if (m_p25->m_rfState == RS_RF_LISTENING) { if (!m_p25->m_dedicatedControl) { - m_p25->m_modem->clearP25Data(); + m_p25->m_modem->clearP25Frame(); } - m_p25->m_queue.clear(); + m_p25->m_txQueue.clear(); resetRF(); resetNet(); } @@ -213,9 +213,9 @@ bool Voice::process(uint8_t* data, uint32_t len) // if this is a late entry call, clear states if (m_rfLastHDU.getDstId() == 0U) { if (!m_p25->m_dedicatedControl) { - m_p25->m_modem->clearP25Data(); + m_p25->m_modem->clearP25Frame(); } - m_p25->m_queue.clear(); + m_p25->m_txQueue.clear(); resetRF(); resetNet(); } @@ -493,6 +493,7 @@ bool Voice::process(uint8_t* data, uint32_t len) if (m_p25->m_control) { m_p25->m_affiliations.touchGrant(m_rfLC.getDstId()); + m_p25->notifyCC_TouchGrant(m_rfLC.getDstId()); } // single-channel trunking or voice on control support? @@ -573,7 +574,6 @@ bool Voice::process(uint8_t* data, uint32_t len) } } else if (duid == P25_DUID_LDU2) { - // prevent two LDUs of the same type from being sent consecutively if (m_lastDUID == P25_DUID_LDU2) { return false; @@ -680,8 +680,9 @@ bool Voice::process(uint8_t* data, uint32_t len) } } else if (duid == P25_DUID_TDU || duid == P25_DUID_TDULC) { - if (m_p25->m_control) { + if (!m_p25->m_control) { m_p25->m_affiliations.releaseGrant(m_rfLC.getDstId(), false); + m_p25->notifyCC_ReleaseGrant(m_rfLC.getDstId()); } if (duid == P25_DUID_TDU) { @@ -721,6 +722,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } else { m_p25->m_tailOnIdle = true; + m_p25->m_trunk->writeNet_TSDU_Call_Term(m_rfLC.getSrcId(), m_rfLC.getDstId()); } } @@ -739,10 +741,10 @@ bool Voice::process(uint8_t* data, uint32_t len) /// /// Buffer containing data frame. /// Length of data frame. -/// -/// -/// -/// +/// Link Control Data. +/// Low Speed Data. +/// Data Unit ID. +/// Network Frame Type. /// bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid, uint8_t& frameType) { @@ -803,6 +805,7 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L if (m_p25->m_control) { lc::LC control = lc::LC(*m_dfsiLC.control()); m_p25->m_affiliations.touchGrant(control.getDstId()); + m_p25->notifyCC_TouchGrant(control.getDstId()); } if (m_p25->m_dedicatedControl && !m_p25->m_voiceOnControl) { @@ -867,6 +870,7 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L if (m_p25->m_control) { lc::LC control = lc::LC(*m_dfsiLC.control()); m_p25->m_affiliations.touchGrant(control.getDstId()); + m_p25->notifyCC_TouchGrant(control.getDstId()); } if (m_p25->m_dedicatedControl && !m_p25->m_voiceOnControl) { @@ -875,9 +879,9 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L if (m_p25->m_netState == RS_NET_IDLE) { if (!m_p25->m_voiceOnControl) { - m_p25->m_modem->clearP25Data(); + m_p25->m_modem->clearP25Frame(); } - m_p25->m_queue.clear(); + m_p25->m_txQueue.clear(); resetRF(); resetNet(); @@ -901,8 +905,9 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L return false; } - if (m_p25->m_control) { + if (!m_p25->m_control) { m_p25->m_affiliations.releaseGrant(m_netLC.getDstId(), false); + m_p25->notifyCC_ReleaseGrant(m_netLC.getDstId()); } if (m_p25->m_netState != RS_NET_IDLE) { @@ -1046,10 +1051,6 @@ void Voice::writeRF_EndOfVoice() /// void Voice::writeNet_TDU() { - if (m_p25->m_control) { - m_p25->m_affiliations.releaseGrant(m_netLC.getDstId(), false); - } - uint8_t buffer[P25_TDU_FRAME_LENGTH_BYTES + 2U]; ::memset(buffer, 0x00U, P25_TDU_FRAME_LENGTH_BYTES + 2U); diff --git a/src/remote/RESTClient.cpp b/src/remote/RESTClient.cpp index 4b6b9858..d489719d 100644 --- a/src/remote/RESTClient.cpp +++ b/src/remote/RESTClient.cpp @@ -56,6 +56,9 @@ using namespace network::rest::http; #define ERRNO_API_CALL_TIMEOUT 96 #define ERRNO_INTERNAL_ERROR 100 +#define ERRNO_NO_ADDRESS 404 +#define ERRNO_NO_PASSWORD 403 + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -139,9 +142,6 @@ RESTClient::~RESTClient() /// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE. int RESTClient::send(const std::string method, const std::string endpoint, json::object payload) { - assert(!m_address.empty()); - assert(m_port > 0U); - return send(m_address, m_port, m_password, method, endpoint, payload, m_debug); } @@ -159,9 +159,15 @@ int RESTClient::send(const std::string method, const std::string endpoint, json: int RESTClient::send(const std::string& address, uint32_t port, const std::string& password, const std::string method, const std::string endpoint, json::object payload, bool debug) { - assert(!address.empty()); - assert(port > 0U); - assert(password.empty()); + if (address.empty()) { + return ERRNO_NO_ADDRESS; + } + if (port <= 0U) { + return ERRNO_NO_ADDRESS; + } + if (password.empty()) { + return ERRNO_NO_PASSWORD; + } int ret = EXIT_SUCCESS; m_debug = debug; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..1ed8303b --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,51 @@ +#/** +#* Digital Voice Modem - Host Software +#* GPLv2 Open Source. Use is subject to license terms. +#* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +#* +#* @package DVM / Host Software +#* +#*/ +#/* +#* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +#* Copyright (C) 2022 by Natalie Moore +#* +#* This program is free software; you can redistribute it and/or modify +#* it under the terms of the GNU General Public License as published by +#* the Free Software Foundation; either version 2 of the License, or +#* (at your option) any later version. +#* +#* This program is distributed in the hope that it will be useful, +#* but WITHOUT ANY WARRANTY; without even the implied warranty of +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#* GNU General Public License for more details. +#* +#* You should have received a copy of the GNU General Public License +#* along with this program; if not, write to the Free Software +#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +#*/ +# +## dvmtest project +# +project(dvmtest) +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") +Include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.0.1 # or a later release +) + +FetchContent_MakeAvailable(Catch2) +find_package(Threads REQUIRED) + +# add ASIO +target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR}) +target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE") +target_link_libraries(asio::asio INTERFACE Threads::Threads) + +add_executable(dvmtests ${dvmhost_SRC} ${dvmtests_SRC}) +target_compile_definitions(dvmtests PUBLIC -DCATCH2_TEST_COMPILATION) +target_link_libraries(dvmtests PRIVATE Catch2::Catch2WithMain asio::asio Threads::Threads util) +target_include_directories(dvmtests PRIVATE src)