merge 3.5-dev into master, this marks 3.5 alpha/beta release; (#31)

* implement inital code to support embedded FNE;

* continued implementation of embedded FNE;

* implement initial core functionality for handling FNE side of DMRD, P25D and NXDD;

* update README.md;

* refactor and rename files; clarify that the embedded FNE is not a "routing" FNE, but rather, a "conference bridge" FNE;

* more cleanups making the conference bridge FNE implementation clearer;

* implement proper code for handling peer inclusion/exclusion; implement proper code for validating a call stream;

* add missing comments;

* make the GCC compiler happy when compiling on 32-bit ARM; make old RPI_ARM compiler happy;

* process parrot flag for talkgroup configuration;

* deprecate and remove old tg_acl.dat file infavor of the YAML-based talkgroup rules file for the entire host; deprecate and remove custom Mutex class in favor of std::mutex; expand helper routines for the TalkgroupRulesLookup class (backwards compatibility essentially with TalkgroupIdLookup); fix issue in network core that could cause deactivated TGs to try using talkgroup lookups when talkgroup lookups may be unavailable;

* support code for future use;

* fixup CMakeLists to fix build warnings for ARM 32-bit platform;

* update detection for sendmsg and sendmmsg (handles some platforms that don't define sendmmsg); implement container classes for RTP framing;

* update CMakeLists to handle new files appropriately;

* initial implementation of FrameQueue to generate FNE RTP frames;

* fix incorrect return;

* implement a typedef for unique_ptr uint8_t arrays; fix issue where DMR, P25 and NXDN control classes were accepting BaseNetwork instead of Network; refactor how FrameQueue handles returning messages read from the network; refactor BaseNetwork, Network and FNENetwork to use FrameQueue instead of raw network writes;

* fix issue with handling network state; fix issue handling global network enable flag; fix buffer pointer cleanup;

* add option to CMake to selectively utilize the legacy (non-RTP) network protocol (embedded FNE does not support this and will be disabled for USE_LEGACY_NETWORK; cleanup some error messages when trying to initialize and read the configuration file; fix some minor issues in parsing the new talkgroup rules YAML file; complete conversion of BaseNetwork from raw socket to using the new FrameQueue class; refactor the multi-buffer UDPSocket write function to properly send multiple messages in batch down to the kernel driver (on Linux using sendmmsg); add flag to UDPSocket to indicate whether the socket is "open" or not; correct some RTP formatting issues with the extension header (the payload length is in 32-bit units);

* remove remaining native WIN32/WIN64 type support code (it was unmaintained anyway); correct issues with network socket reading; cleanup some error messages;q

* finally resolve lingering issue with frame queue having random socket issues;

* refactor how BufferVector and sendmmsg UDPSocket write operates; refactor FrameQueue to properly queue messages;

* properly queue packets to send in bursts to peers; add missing ACKs; add missing MSTPONG;

* fix up formatting; remove unnecessary debug statements;

* minor cleanups;

* remove unused function;

* add fields for function and sub-function;

* describe network functions and subfunctions;

* reorganize some code; add checking for packet sequence;

* roll next sequence properly;

* utilize typedef for std::unique_ptr<uint8_t[]>;

* fix usage of __UNIQUE_BUFFER to __UNIQUE_UINT8_ARRAY;

* clean up macro definitions;

* implement sequence counting for the FNE conference bridge;

* remove USE_LEGACY_NETWORK support;

* use macro expansions for this instead of raw buffer lists;

* implement actual transmission of new network func/sub-func behavior;

* deprecate use of packet tags for determining operation, instead use RTP FNE header function and subfunction bytes;

* fix issue with send talkgroups not setting flag to send talkgroups to peers; fix issue when compiling list of activated or deactivated TGs to send to peers; correct strangeness with the RTP sequence counting; fix talkgroup rule lookup on peer when activating or deactivating TGs;

* update copyright dates;

* complain if the RTP and FNE headers don't agree on stream ID;

* for when you forget to update a comment so things actually make sense when you look at it later...;

* remove extraneous check, this is really not necessary since FrameQueue does the size validation;

* clarify the description of the "embedded FNE";

* merge changes from https://github.com/CVSoft/dvmhost to place spec specific limits on channel spacing;

* refactor how network traffic is handled, we don't handle protocol specific stuff within BaseNetwork anymore and offload that to the specific protocol controller instead; implement support to receive peer traffic and repeat (still need to do master to peer);

* convert fatal error about Rx frequency being below base frequency to a warning instead;

* fix incorrect redefine of slotNo;

* correct ret flag for readDMR, readP25 and readNXDN not being set to true by default (stupid me); correct FNE-mode payload offsets for diagnostic packets;

* implement option to selectively enable/disable FNE mode verbose logging information; enhance FNE display of network diagnostic logs; refactor dmr::Control::processNetwork(); refactor RTP packet sequence counting;

* fix incorrect variable used for out-of-sequence log message;

* fix issues with login RTP stream; specially handle certain control opcodes with a different payload type; continued enhancements to sequence counting;

* whoops readd accidentally removed block;

* backout previous lastPeerId code; properly ensure RTP packet sequence is repeated for traffic calls;

* transmit DVM LC_CALL_TERM TSDU at the end of call or if a call drops (hopefully);

* don't reset the packet sequence for a RPTPING;

* rename some class variables for clarity and prep for some future changes;

* add support for priority/immediate Tx queues, these queues are intended to be used sparingly (and mostly for control data) to transmit priority messages [this is an experimental change and may break the build!];

* fix issue where connecting to a non-existent REST API endpoint would crash with an unhandled ASIO exception;

* increase timeout delay for the modem by 4 seconds (the original 4 second delay would be too short for some longer operations);

* use the immediate queue to prioritize control data in some situations [this is experimental and may break things!];

* reformat headers;

* implement toString() functions on TSBKs, CSBKs and RCCHs for future use to replace fixed strings; cleanup DMRDefines slightly;

* fix up and simplify RTP timestamp generation;

* reduce reliance on fixed strings, instead rely on toString() functions from TSBK, CSBK, and RCCH to ensure string consistency for logging purpsoes;

* refactor how and when releaseGrant is called in some situations; ensure channel grant messages traverse the network;

* fix reset of error count between network super-frames;

* add development helper file for VS Code to configure debug launch parameters;

* update CMakeLists to be more condusive to debugging;

* cleanup AUTHORS.md;

* implement parrot for the conference bridge FNE; implement appropriate call detection logic for conference bridge FNE and attempt to prevent call collisons for the same destination;

* add support for altering softpot levels from calibration mode;

* bump copyright information;

* reorganize CMakeLists;

* remove finalcut from the libraries list (this isn't supposed to be included yet!);

* implement initial rework of setup/calibration mode into a TUI; add ENABLE_TUI_SUPPORT CMake option to disable *all* project TUI support; add ENABLE_TUI_SETUP CMake option to disable setup TUI support for dvmhost;

* properly update isConnected flag;

* fix issue with incorrectly placed control;

* part 1 of a fix for #30, the change here would be to allow the VCs to know about the CC to be able to report back to it to release or other wise update grants;

* part 2 of the fix for #30, this implements the actual logic for a VC to notify the CC of a grant status change (touch, release) [NOTE: this could have undesirable consequences and this commit is experimental!];

* update README.md;

* fix terrible copy and paste job resulting in a poor logic check (SMH); print to log the control channel address and port at startup; use the appropriate REST API for touch (whoops);

* remove transmit overlay on shell; add F12 hotkey to trigger transmit; implement support to use F2 and F12 from various configuration/calibration modals; correct some mode strangeness when saving;

* implement support to adjust FIFO buffer sizes from the host;

* add engineering menu to adjust FIFO buffer sizes from setup;

* ensure set button state is set properly for these windows;

* fix stupid --tags non-sense in the git hash reporting;

* ensure certain error conditions for setup mode are displayed when exiting at initial startup;

* more fixes to attempt to satisfy #30 (why is this being so difficult!); correct situation where RESTClient may throw an assert instead of handling an error condition nicely;

* for #30 allow release tg grant and touch tg grant to work regardless of dedicated CC setting;

* file cleanups;

* C++11/14 cleanups, class overrides and pure-virtual implementors /worked/ but were not really correct;

* stringify modem command and reason messages (so log errors are easier on the eyes);

* late update during a branch merge, to fix some latent issues that were missed;
pull/32/head
Bryan Biedenkapp 3 years ago committed by GitHub
parent 56e4e7cf37
commit 2b6b0c574d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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)

@ -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\"

@ -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 <configuration file>] [--remote [-a <address>] [-p <port>]]
usage: ./dvmhost [-vhf] [--setup] [--fne] [-c <configuration file>] [--remote [-a <address>] [-p <port>]]
-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 <file> 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`.

@ -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

@ -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

@ -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

@ -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),<newline>
16777215,1,0,
65535,1,0,

@ -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
}
},
]
}

@ -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 <https://github.com/jelimoore>
#*
#* 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)

@ -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)
/// </summary>
/// <param name="ms"></param>
/// <returns></returns>
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);
}
/// <summary>
@ -168,45 +165,7 @@ uint64_t msToJiffies(uint64_t ms)
/// </summary>
/// <param name="ms"></param>
/// <returns></returns>
uint64_t jiffiesToMs(uint64_t jiffies)
uint64_t system_clock::jiffiesToMs(uint64_t jiffies)
{
return (uint64_t)(((double)jiffies / 65536) * 1000);
}
#ifdef _WIN32
/// <summary>
///
/// </summary>
/// <param name="tp"></param>
/// <param name="tzp"></param>
/// <returns></returns>
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

@ -23,12 +23,7 @@
#include "Defines.h"
#ifdef _WIN32
#include <winsock2.h>
#else
#include <sys/time.h>
#endif
#include <chrono>
namespace system_clock
@ -67,11 +62,6 @@ namespace system_clock
uint64_t msToJiffies(uint64_t ms);
/// <summary></summary>
uint64_t jiffiesToMs(uint64_t jiffies);
#ifdef _WIN32
/// <summary></summary>
int gettimeofday(struct timeval *tp, struct timezone *tzp);
#endif
} // namespace system_clock
#endif // __CLOCK_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<type[]> name = std::unique_ptr<type[]>(new type[length]); \
::memset(name.get(), 0x00U, length);
typedef std::unique_ptr<uint8_t[]> UInt8Array;
/// <summary>Creates a named uint8_t array buffer.</summary>
#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; }
/// <summary>Creates a read-only get property, does not use "get"/"set".</summary>
#define __PROTECTED_READONLY_PROPERTY_PLAIN(type, variableName, propName) \
protected: type m_##variableName; \
public: __forceinline type propName(void) const { return m_##variableName; }
/// <summary>Creates a read-only get property, does not use "get".</summary>
#define __READONLY_PROPERTY_PLAIN(type, variableName, propName) \
private: type m_##variableName; \

@ -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 <cstdarg>
#include <vector>
#if !defined(_WIN32) && !defined(_WIN64)
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <pwd.h>
#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)
/// <summary>
/// Internal signal handler.
/// </summary>
/// <param name="signum"></param>
static void sigHandler(int signum)
{
g_killed = true;
@ -113,6 +117,11 @@ static void sigHandler(int signum)
}
#endif
/// <summary>
/// Helper to print a fatal error message and exit.
/// </summary>
/// <remarks>This is a variable argument function.</remarks>
/// <param name="msg">Message.</param>
void fatal(const char* msg, ...)
{
char buffer[400U];
@ -129,6 +138,11 @@ void fatal(const char* msg, ...)
exit(EXIT_FAILURE);
}
/// <summary>
/// Helper to pring usage the command line arguments. (And optionally an error.)
/// </summary>
/// <param name="message">Error message.</param>
/// <param name="arg">Error message arguments.</param>
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 <configuration file>] [--remote [-a <address>] [-p <port>]]\n\n"
::fprintf(stdout,
"usage: %s [-vhf]"
"[--setup]"
"[--fne]"
"[-c <configuration file>]"
"[--remote [-a <address>] [-p <port>]]"
"\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 <file> 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);
}
/// <summary>
/// Helper to validate the command line arguments.
/// </summary>
/// <param name="argc">Argument count.</param>
/// <param name="argv">Array of argument strings.</param>
/// <returns>Count of remaining unprocessed arguments.</returns>
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");

@ -31,12 +31,7 @@
#include "Log.h"
#include "network/Network.h"
#if defined(_WIN32) || defined(_WIN64)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <sys/time.h>
#endif
#if defined(CATCH2_TEST_COMPILATION)
#include <catch2/catch_test_macros.hpp>
@ -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;
}
/// <summary>
/// Internal helper to set an output stream to direct logging to.
/// </summary>
/// <param name="stream"></param>
void __InternalOutputStream(std::ostream& stream)
{
m_outStream.rdbuf(stream.rdbuf());
}
/// <summary>
/// Sets the instance of the Network class to transfer the activity log with.
/// </summary>
@ -205,6 +201,7 @@ void ActivityLogFinalise()
/// <summary>
/// Writes a new entry to the activity log.
/// </summary>
/// <remarks>This is a variable argument function.</remarks>
/// <param name="mode">Digital mode (usually P25 or DMR).</param>
/// <param name="sourceRf">Flag indicating that the entry was generated from an RF event.</param>
/// <param name="msg">Formatted string to write to activity log.</param>
@ -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()
/// <summary>
/// Writes a new entry to the diagnostics log.
/// </summary>
/// <remarks>This is a variable argument function.</remarks>
/// <param name="level">Log level.</param>
/// <param name="module">Module name the log entry was genearted from.</param>
/// <param name="msg">Formatted string to write to activity log.</param>
/// <param name="fmt">Formatted string to write to the log.</param>
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);
}

@ -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
// ---------------------------------------------------------------------------
/// <summary>Internal helper to set an output stream to direct logging to.</summary>
extern HOST_SW_API void __InternalOutputStream(std::ostream& stream);
/// <summary>Sets the instance of the Network class to transfer the activity log with.</summary>
extern HOST_SW_API void LogSetNetwork(void* network);

@ -29,75 +29,13 @@
*/
#include "StopWatch.h"
#if !defined(_WIN32) || !defined(_WIN64)
#include <cstdio>
#include <ctime>
#endif
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
#if defined(_WIN32) || defined(_WIN64)
/// <summary>
/// Initializes a new instance of the StopWatch class.
/// </summary>
StopWatch::StopWatch() :
m_frequencyS(),
m_frequencyMS(),
m_start()
{
::QueryPerformanceFrequency(&m_frequencyS);
m_frequencyMS.QuadPart = m_frequencyS.QuadPart / 1000ULL;
}
/// <summary>
/// Finalizes a instance of the StopWatch class.
/// </summary>
StopWatch::~StopWatch()
{
/* stub */
}
/// <summary>
/// Gets the current running time.
/// </summary>
/// <returns></returns>
ulong64_t StopWatch::time() const
{
LARGE_INTEGER now;
::QueryPerformanceCounter(&now);
return (ulong64_t)(now.QuadPart / m_frequencyMS.QuadPart);
}
/// <summary>
/// Starts the stopwatch.
/// </summary>
/// <returns></returns>
ulong64_t StopWatch::start()
{
::QueryPerformanceCounter(&m_start);
return (ulong64_t)(m_start.QuadPart / m_frequencyS.QuadPart);
}
/// <summary>
/// Gets the elpased time since the stopwatch started.
/// </summary>
/// <returns></returns>
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
/// <summary>
/// Initializes a new instance of the StopWatch class.
/// </summary>
@ -154,4 +92,3 @@ uint32_t StopWatch::elapsed()
return nowMS - m_startMS;
}
#endif

@ -32,12 +32,7 @@
#include "Defines.h"
#if defined(_WIN32) || defined(_WIN64)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <sys/time.h>
#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__

@ -29,62 +29,12 @@
*/
#include "Thread.h"
#if !defined(_WIN32) && !defined(_WIN64)
#include <unistd.h>
#endif
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
#if defined(_WIN32) || defined(_WIN64)
/// <summary>
/// Initializes a new instance of the Thread class.
/// </summary>
Thread::Thread() :
m_handle()
{
/* stub */
}
/// <summary>
/// Finalizes a instance of the Thread class.
/// </summary>
Thread::~Thread()
{
/* stub */
}
/// <summary>
/// Starts the thread execution.
/// </summary>
/// <returns>True, if thread started, otherwise false.</returns>
bool Thread::run()
{
m_handle = ::CreateThread(NULL, 0, &helper, this, 0, NULL);
return m_handle != nullptr;
}
/// <summary>
///
/// </summary>
void Thread::wait()
{
::WaitForSingleObject(m_handle, INFINITE);
::CloseHandle(m_handle);
}
/// <summary>
///
/// </summary>
/// <param name="ms"></param>
void Thread::sleep(uint32_t ms)
{
::Sleep(ms);
}
#else
/// <summary>
/// Initializes a new instance of the Thread class.
/// </summary>
@ -127,27 +77,11 @@ void Thread::sleep(uint32_t ms)
{
::usleep(ms * 1000);
}
#endif
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
#if defined(_WIN32) || defined(_WIN64)
/// <summary>
///
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
DWORD Thread::helper(LPVOID arg)
{
Thread* p = (Thread*)arg;
p->entry();
return 0UL;
}
#else
/// <summary>
///
/// </summary>
@ -161,4 +95,3 @@ void* Thread::helper(void* arg)
return nullptr;
}
#endif

@ -32,12 +32,7 @@
#include "Defines.h"
#if defined(_WIN32) || defined(_WIN64)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <pthread.h>
#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)
/// <summary></summary>
static DWORD __stdcall helper(LPVOID arg);
#else
/// <summary></summary>
static void* helper(void* arg);
#endif
};
#endif // __THREAD_H__

@ -55,7 +55,7 @@ using namespace dmr;
/// <param name="network">Instance of the BaseNetwork class.</param>
/// <param name="duplex">Flag indicating full-duplex operation.</param>
/// <param name="ridLookup">Instance of the RadioIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupRulesLookup class.</param>
/// <param name="idenTable">Instance of the IdenTableLookup class.</param>
/// <param name="rssiMapper">Instance of the RSSIInterpolator class.</param>
/// <param name="jitter"></param>
@ -65,8 +65,8 @@ using namespace dmr;
/// <param name="debug">Flag indicating whether DMR debug is enabled.</param>
/// <param name="verbose">Flag indicating whether DMR verbose logging is enabled.</param>
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()
/// <param name="supervisor">Flag indicating whether the DMR has supervisory functions.</param>
/// <param name="voiceChNo">Voice Channel Number list.</param>
/// <param name="voiceChData">Voice Channel data map.</param>
/// <param name="controlChData">Control Channel data.</param>
/// <param name="netId">DMR Network ID.</param>
/// <param name="siteId">DMR Site ID.</param>
/// <param name="channelId">Channel ID.</param>
/// <param name="channelNo">Channel Number.</param>
/// <param name="printOptions"></param>
void Control::setOptions(yaml::Node& conf, bool supervisor, const std::vector<uint32_t> voiceChNo, const std::unordered_map<uint32_t, ::lookups::VoiceChData> 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<ui
dedicatedTSCC = false;
}
Slot::setSiteData(voiceChNo, voiceChData, netId, siteId, channelId, channelNo, dedicatedTSCC);
Slot::setSiteData(voiceChNo, voiceChData, controlChData, netId, siteId, channelId, channelNo, dedicatedTSCC);
Slot::setAlohaConfig(nRandWait, backOff);
bool disableGrantSourceIdCheck = control["disableGrantSourceIdCheck"].as<bool>(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)
}
}
/// <summary>
/// Releases a granted TG.
/// </summary>
/// <param name="dstId"></param>
/// <paran name="slot"></param>
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;
}
}
/// <summary>
/// Touchs a granted TG to keep a channel grant alive.
/// </summary>
/// <param name="dstId"></param>
/// <paran name="slot"></param>
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;
}
}
/// <summary>
/// Gets instance of the AffiliationLookup class.
/// </summary>
@ -597,3 +623,96 @@ void Control::setCSBKVerbose(bool verbose)
m_dumpCSBKData = verbose;
lc::CSBK::setVerbose(verbose);
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Process a data frames from the network.
/// </summary>
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;
}
}

@ -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:
/// <summary>Initializes a new instance of the Control class.</summary>
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);
/// <summary>Finalizes a instance of the Control class.</summary>
~Control();
/// <summary>Helper to set DMR configuration options.</summary>
void setOptions(yaml::Node& conf, bool supervisor, const std::vector<uint32_t> voiceChNo, const std::unordered_map<uint32_t, ::lookups::VoiceChData> 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);
/// <summary>Gets a flag indicating whether the DMR control channel is running.</summary>
bool getCCRunning() { return m_ccRunning; }
@ -96,6 +96,11 @@ namespace dmr
/// <summary>Permits a TGID on a non-authoritative host.</summary>
void permittedTG(uint32_t dstId, uint8_t slot);
/// <summary>Releases a granted TG.</summary>
void releaseGrantTG(uint32_t dstId, uint8_t slot);
/// <summary>Touchs a granted TG to keep a channel grant alive.</summary>
void touchGrantTG(uint32_t dstId, uint8_t slot);
/// <summary>Gets instance of the DMRAffiliationLookup class.</summary>
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;
/// <summary>Process a data frames from the network.</summary>
void processNetwork();
};
} // namespace dmr

@ -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;

@ -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;
}
/// <summary>
/// Releases a granted TG.
/// </summary>
/// <param name="dstId"></param>
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);
}
}
/// <summary>
/// Touchs a granted TG to keep a channel grant alive.
/// </summary>
/// <param name="dstId"></param>
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);
}
}
/// <summary>
/// Helper to change the debug and verbose state.
/// </summary>
@ -640,13 +692,13 @@ void Slot::setSilenceThreshold(uint32_t threshold)
/// <param name="network">Instance of the BaseNetwork class.</param>
/// <param name="duplex">Flag indicating full-duplex operation.</param>
/// <param name="ridLookup">Instance of the RadioIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupRulesLookup class.</param>
/// <param name="idenTable">Instance of the IdenTableLookup class.</param>
/// <param name="rssi">Instance of the RSSIInterpolator class.</param>
/// <param name="jitter"></param>
/// <param name="verbose"></param>
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
/// </summary>
/// <param name="voiceChNo">Voice Channel Number list.</param>
/// <param name="voiceChData">Voice Channel data map.</param>
/// <param name="controlChData">Control Channel data.</param>
/// <param name="netId">DMR Network ID.</param>
/// <param name="siteId">DMR Site ID.</param>
/// <param name="channelId">Channel ID.</param>
/// <param name="channelNo">Channel Number.</param>
/// <param name="requireReg"></param>
void Slot::setSiteData(const std::vector<uint32_t> voiceChNo, const std::unordered_map<uint32_t, ::lookups::VoiceChData> 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<uint32_t> voiceChNo, const std::unorder
std::unordered_map<uint32_t, ::lookups::VoiceChData> chData = std::unordered_map<uint32_t, ::lookups::VoiceChData>(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)
/// <summary>
/// Add data frame to the data ring buffer.
/// </summary>
/// <param name="data"></param>
/// <param name="net"></param>
void Slot::addFrame(const uint8_t *data, bool net)
/// <param name="data">Frame data to add to Tx queue.</param>
/// <param name="net">Flag indicating whether the data came from the network or not</param>
/// <param name="imm">Flag indicating whether or not the data is priority and is added to the immediate queue.</param>
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);
}
/// <summary>
/// Helper to send a REST API request to the CC to release a channel grant at the end of a call.
/// </summary>
/// <param name="dstId"></param>
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<int>(state);
req["dstId"].set<uint32_t>(dstId);
uint8_t slot = m_slotNo;
req["slot"].set<uint8_t>(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);
/// <summary>
/// Helper to send a REST API request to the CC to "touch" a channel grant to refresh grant timers.
/// </summary>
/// <param name="dstId"></param>
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<int>(state);
req["dstId"].set<uint32_t>(dstId);
uint8_t slot = m_slotNo;
req["slot"].set<uint8_t>(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);
}
}
}
/// <summary>
@ -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)

@ -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
/// <summary>Permits a TGID on a non-authoritative host.</summary>
void permittedTG(uint32_t dstId);
/// <summary>Releases a granted TG.</summary>
void releaseGrantTG(uint32_t dstId);
/// <summary>Touchs a granted TG to keep a channel grant alive.</summary>
void touchGrantTG(uint32_t dstId);
/// <summary>Gets instance of the ControlSignaling class.</summary>
packet::ControlSignaling* control() { return m_control; }
@ -116,11 +121,11 @@ namespace dmr
/// <summary>Helper to initialize the slot processor.</summary>
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);
/// <summary>Sets local configured site data.</summary>
static void setSiteData(const std::vector<uint32_t> voiceChNo, const std::unordered_map<uint32_t, ::lookups::VoiceChData> 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);
/// <summary>Sets TSCC Aloha configuration.</summary>
static void setAlohaConfig(uint8_t nRandWait, uint8_t backOff);
@ -135,7 +140,8 @@ namespace dmr
uint32_t m_slotNo;
RingBuffer<uint8_t> m_queue;
RingBuffer<uint8_t> m_txImmQueue;
RingBuffer<uint8_t> 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;
/// <summary>Add data frame to the data ring buffer.</summary>
void addFrame(const uint8_t* data, bool net = false);
void addFrame(const uint8_t* data, bool net = false, bool imm = false);
/// <summary>Helper to send a REST API request to the CC to release a channel grant at the end of a call.</summary>
void notifyCC_ReleaseGrant(uint32_t dstId);
/// <summary>Helper to send a REST API request to the CC to "touch" a channel grant to refresh grant timers.</summary>
void notifyCC_TouchGrant(uint32_t dstId);
/// <summary>Write data frame to the network.</summary>
void writeNetwork(const uint8_t* data, uint8_t dataType, uint8_t errors = 0U);

@ -43,14 +43,14 @@ using namespace dmr::acl;
// ---------------------------------------------------------------------------
RadioIdLookup* AccessControl::m_ridLookup;
TalkgroupIdLookup* AccessControl::m_tidLookup;
TalkgroupRulesLookup* AccessControl::m_tidLookup;
/// <summary>
/// Initializes the DMR access control.
/// </summary>
/// <param name="ridLookup">Instance of the RadioIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupIdLookup class.</param>
void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup)
/// <param name="tidLookup">Instance of the TalkgroupRulesLookup class.</param>
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;

@ -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:
/// <summary>Initializes the DMR access control.</summary>
static void init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup);
static void init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLookup);
/// <summary>Helper to validate a source radio ID.</summary>
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

@ -101,6 +101,15 @@ CSBK::~CSBK()
/* stub */
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK::toString()
{
return std::string("CSBKO_UNKWN (Unknown CSBK)");
}
/// <summary>
/// Regenerate a DMR CSBK without decoding.
/// </summary>
@ -175,9 +184,9 @@ ulong64_t CSBK::toValue(const uint8_t* csbk)
/// </summary>
/// <param name="csbkValue"></param>
/// <returns></returns>
std::unique_ptr<uint8_t[]> 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);

@ -59,6 +59,9 @@ namespace dmr
/// <summary>Encodes a DMR CSBK.</summary>
virtual void encode(uint8_t* data) = 0;
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString();
/// <summary>Regenerate a DMR CSBK without decoding.</summary>
/// <remarks>This is because the DMR archeticture allows fall-thru of unsupported CSBKs.</remarks>
static bool regenerate(uint8_t* data);
@ -144,7 +147,7 @@ namespace dmr
/// <summary>Internal helper to convert CSBK bytes to a 64-bit long value.</summary>
static ulong64_t toValue(const uint8_t* Csbk);
/// <summary>Internal helper to convert a 64-bit long value to CSBK bytes.</summary>
static std::unique_ptr<uint8_t[]> fromValue(const ulong64_t csbkValue);
static UInt8Array fromValue(const ulong64_t csbkValue);
/// <summary>Internal helper to decode a control signalling block.</summary>
bool decode(const uint8_t* data, uint8_t* csbk);

@ -97,3 +97,12 @@ void CSBK_ACK_RSP::encode(uint8_t* data)
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_ACK_RSP::toString()
{
return std::string("CSBKO_ACK_RSP (Acknowledge Response)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_ACK_RSP();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -92,6 +92,15 @@ void CSBK_ALOHA::encode(uint8_t* data)
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_ALOHA::toString()
{
return std::string("CSBKO_ALOHA (Aloha PDU)");
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------

@ -46,9 +46,12 @@ namespace dmr
CSBK_ALOHA();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
public:
/// <summary>Aloha Site Time Slot Synchronization.</summary>

@ -154,6 +154,20 @@ void CSBK_BROADCAST::encode(uint8_t* data)
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
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
// ---------------------------------------------------------------------------

@ -46,9 +46,12 @@ namespace dmr
CSBK_BROADCAST();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
public:
/// <summary>Broadcast Announcment Type.</summary>

@ -83,6 +83,15 @@ void CSBK_BSDWNACT::encode(uint8_t* data)
/* stub */
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_BSDWNACT::toString()
{
return std::string("CSBKO_BSDWNACT (BS Outbound Activation)");
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------

@ -46,9 +46,12 @@ namespace dmr
CSBK_BSDWNACT();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
public:
/// <summary>Base Station ID.</summary>

@ -90,3 +90,12 @@ void CSBK_CALL_ALRT::encode(uint8_t* data)
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_CALL_ALRT::toString()
{
return std::string("CSBKO_RAND (Call Alert)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_CALL_ALRT();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -84,3 +84,12 @@ void CSBK_DVM_GIT_HASH::encode(uint8_t* data)
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_DVM_GIT_HASH::toString()
{
return std::string("CSBKO_DVM_GIT_HASH (DVM Git Hash Identifier)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_DVM_GIT_HASH();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -96,6 +96,15 @@ void CSBK_EXT_FNCT::encode(uint8_t* data)
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_EXT_FNCT::toString()
{
return std::string("CSBKO_EXT_FNCT (Extended Function)");
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------

@ -46,9 +46,12 @@ namespace dmr
CSBK_EXT_FNCT();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
public:
/// <summary>Extended function opcode.</summary>

@ -96,6 +96,15 @@ void CSBK_NACK_RSP::encode(uint8_t* data)
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_NACK_RSP::toString()
{
return std::string("CSBKO_NACK_RSP (Negative Acknowledgement Response)");
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------

@ -46,9 +46,12 @@ namespace dmr
CSBK_NACK_RSP();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
public:
/// <summary>Service Kind.</summary>

@ -82,3 +82,12 @@ void CSBK_PD_GRANT::encode(uint8_t* data)
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_PD_GRANT::toString()
{
return std::string("CSBKO_PD_GRANT (Private Data Channel Grant)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_PD_GRANT();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -84,3 +84,12 @@ void CSBK_PRECCSBK::encode(uint8_t* data)
/* stub */
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_PRECCSBK::toString()
{
return std::string("CSBKO_PRECCSBK (Preamble CSBK)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_PRECCSBK();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -82,3 +82,12 @@ void CSBK_PV_GRANT::encode(uint8_t* data)
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_PV_GRANT::toString()
{
return std::string("CSBKO_PV_GRANT (Private Voice Channel Grant)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_PV_GRANT();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -81,3 +81,12 @@ void CSBK_P_CLEAR::encode(uint8_t* data)
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_P_CLEAR::toString()
{
return std::string("CSBKO_P_CLEAR (Payload Channel Clear)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_P_CLEAR();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -82,3 +82,12 @@ void CSBK_P_GRANT::encode(uint8_t* data)
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_P_GRANT::toString()
{
return std::string("CSBK_P_GRANT (Payload Grant)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_P_GRANT();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -99,6 +99,30 @@ void CSBK_RAND::encode(uint8_t* data)
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
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
// ---------------------------------------------------------------------------

@ -46,9 +46,12 @@ namespace dmr
CSBK_RAND();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
public:
/// <summary>Service Options.</summary>

@ -48,9 +48,9 @@ namespace dmr
~CSBK_RAW();
/// <summary>Decode a trunking signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a trunking signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Sets the CSBK to encode.</summary>
void setCSBK(const uint8_t* csbk);

@ -82,3 +82,12 @@ void CSBK_TD_GRANT::encode(uint8_t* data)
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_TD_GRANT::toString()
{
return std::string("CSBKO_TD_GRANT (Talkgroup Data Channel Grant)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_TD_GRANT();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -84,6 +84,15 @@ void CSBK_TV_GRANT::encode(uint8_t* data)
CSBK::encode(data, csbk.get());
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_TV_GRANT::toString()
{
return std::string("CSBKO_TV_GRANT (Talkgroup Voice Channel Grant)");
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------

@ -46,9 +46,12 @@ namespace dmr
CSBK_TV_GRANT();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
public:
/// <summary>Flag indicating whether the grant is a late entry.</summary>

@ -81,3 +81,12 @@ void CSBK_UU_ANS_RSP::encode(uint8_t* data)
/* stub */
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_UU_ANS_RSP::toString()
{
return std::string("CSBKO_UU_ANS_RSP (Unit-to-Unit Answer Response)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_UU_ANS_RSP();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -81,3 +81,12 @@ void CSBK_UU_V_REQ::encode(uint8_t* data)
/* stub */
}
/// <summary>
/// Returns a string that represents the current CSBK.
/// </summary>
/// <returns></returns>
std::string CSBK_UU_V_REQ::toString()
{
return std::string("CSBKO_UU_V_REQ (Unit-to-Unit Voice Channel Request)");
}

@ -46,9 +46,12 @@ namespace dmr
CSBK_UU_V_REQ();
/// <summary>Decode a control signalling block.</summary>
virtual bool decode(const uint8_t* data);
bool decode(const uint8_t* data);
/// <summary>Encode a control signalling block.</summary>
virtual void encode(uint8_t* data);
void encode(uint8_t* data);
/// <summary>Returns a string that represents the current CSBK.</summary>
virtual std::string toString() override;
};
} // namespace csbk
} // namespace lc

@ -49,13 +49,13 @@ namespace dmr
virtual ~DMRAffiliationLookup();
/// <summary>Helper to grant a channel.</summary>
virtual bool grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTimeout);
bool grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTimeout) override;
/// <summary>Helper to grant a channel and slot.</summary>
bool grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t slot, uint32_t grantTimeout);
/// <summary>Helper to release the channel grant for the destination ID.</summary>
virtual bool releaseGrant(uint32_t dstId, bool releaseAll);
bool releaseGrant(uint32_t dstId, bool releaseAll) override;
/// <summary>Helper to determine if the channel number is busy.</summary>
virtual bool isChBusy(uint32_t chNo) const;
bool isChBusy(uint32_t chNo) const override;
/// <summary>Helper to get the slot granted for the given destination ID.</summary>
uint8_t getGrantedSlot(uint32_t dstId) const;

@ -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_EXT_FNCT*>(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_EXT_FNCT*>(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)
/// <param name="dstId">Destination radio ID.</param>
void ControlSignaling::writeRF_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId)
{
std::unique_ptr<CSBK_EXT_FNCT> 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_EXT_FNCT> 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
/// <param name="dstId">Destination radio ID.</param>
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_CALL_ALRT> 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()
/// </summary>
/// <param name="csbk"></param>
/// <param name="clearBeforeWrite"></param>
void ControlSignaling::writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite)
/// <param name="imm"></param>
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);
}
/// <summary>
@ -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());
}
/// <summary>
@ -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());
}
/// <summary>
@ -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());
}
/// <summary>
@ -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
/// </summary>
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_ALOHA> 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()
/// <param name="annWd"></param>
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_BROADCAST> 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)
/// </summary>
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_BROADCAST> 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()
/// </summary>
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_DVM_GIT_HASH> csbk = new_unique(CSBK_DVM_GIT_HASH);
DEBUG_LOG_CSBK(csbk->toString());
writeRF_CSBK(csbk.get());
}

@ -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
/// <summary>Finalizes a instance of the ControlSignaling class.</summary>
~ControlSignaling();
/// <summary>Helper to write a immediate CSBK packet.</summary>
void writeRF_CSBK_Imm(lc::CSBK *csbk) { writeRF_CSBK(csbk, false, true); }
/// <summary>Helper to write a CSBK packet.</summary>
void writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite = false);
void writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite = false, bool imm = false);
/// <summary>Helper to write a ACK RSP packet.</summary>
void writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint8_t responseInfo);

@ -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"

@ -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;

@ -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 <vector>

@ -712,6 +712,29 @@ uint16_t CRC::addCRC16(uint8_t* in, uint32_t bitLength)
return crc;
}
/// <summary>
///
/// </summary>
/// <param name="in">Input byte array.</param>
/// <param name="bitLength">Length of byte array in bits.</param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="in">Input byte array.</param>
/// <param name="bitLength">Length of byte array in bits.</param>
/// <returns></returns>
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;
}

@ -88,6 +88,9 @@ namespace edac
/// <summary>Encode 16-bit CRC CCITT-162 w/ initial generator of 1.</summary>
static uint16_t addCRC16(uint8_t* in, uint32_t bitLength);
/// <summary></summary>
static uint16_t createCRC16(const uint8_t* in, uint32_t bitLength);
private:
/// <summary></summary>
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);
/// <summary></summary>
static uint16_t createCRC15(const uint8_t* in, uint32_t bitLength);
/// <summary></summary>
static uint16_t createCRC16(const uint8_t* in, uint32_t bitLength);
};
} // namespace edac

@ -31,64 +31,15 @@
#include <cstdio>
#if defined(_WIN32) || defined(_WIN64)
#include <conio.h>
#else
#include <cstring>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
#endif
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
#if defined(_WIN32) || defined(_WIN64)
/// <summary>
/// Initializes a new instance of the Console class.
/// </summary>
Console::Console()
{
/* stub */
}
/// <summary>
/// Finalizes a instance of the Console class.
/// </summary>
Console::~Console()
{
/* stub */
}
/// <summary>
/// Opens the terminal console.
/// </summary>
bool Console::open()
{
return true;
}
/// <summary>
/// Retrieves a character input on the keyboard.
/// </summary>
/// <returns></returns>
int Console::getChar()
{
if (::_kbhit() == 0)
return -1;
return ::_getch();
}
/// <summary>
/// Closes the terminal console.
/// </summary>
void Console::close()
{
/* stub */
}
#else
/// <summary>
/// Initializes a new instance of the Console class.
/// </summary>
@ -173,7 +124,6 @@ void Console::close()
if (n != 0)
::fprintf(stderr, "tcsetattr: returned %d\r\n", n);
}
#endif
/// <summary>
/// Retrieves an array of characters input on the keyboard.

@ -32,9 +32,7 @@
#include "Defines.h"
#if !defined(_WIN32) && !defined(_WIN64)
#include <termios.h>
#endif
// ---------------------------------------------------------------------------
// Class Declaration
@ -62,9 +60,7 @@ public:
void close();
private:
#if !defined(_WIN32) && !defined(_WIN64)
termios m_termios;
#endif
};
#endif // __CONSOLE_H__

@ -58,13 +58,11 @@ using namespace lookups;
#include <functional>
#include <vector>
#if !defined(_WIN32) && !defined(_WIN64)
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <pwd.h>
#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 */
}
/// <summary>
@ -153,7 +152,7 @@ Host::Host(const std::string& confFile) :
/// </summary>
Host::~Host()
{
UDPSocket::shutdown();
/* stub */
}
/// <summary>
@ -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<bool>(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<uint32_t>(0U);
bool tidAcl = systemConf["talkgroup_id"]["acl"].as<bool>(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<dmr::Control>(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<dmr::Control>(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<p25::Control>(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<p25::Control>(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<nxdn::Control>(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<nxdn::Control>(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<uint32_t>(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<std::string>("127.0.0.1");
uint16_t restApiPort = (uint16_t)controlCh["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = controlCh["restPassword"].as<std::string>();
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<std::string>("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<uint32_t>(127U);
uint8_t rssiFine = (uint8_t)softpotParams["rssiFine"].as<uint32_t>(127U);
uint16_t dmrFifoLength = (uint16_t)modemConf["dmrFifoLength"].as<uint32_t>(DMR_TX_BUFFER_LEN);
uint16_t p25FifoLength = (uint16_t)modemConf["p25FifoLength"].as<uint32_t>(P25_TX_BUFFER_LEN);
uint16_t nxdnFifoLength = (uint16_t)modemConf["nxdnFifoLength"].as<uint32_t>(NXDN_TX_BUFFER_LEN);
float rxLevel = modemConf["rxLevel"].as<float>(50.0F);
float cwIdTXLevel = modemConf["cwIdTxLevel"].as<float>(50.0F);
float dmrTXLevel = modemConf["dmrTxLevel"].as<float>(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<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = networkConf["restPassword"].as<std::string>();
bool restApiDebug = networkConf["restDebug"].as<bool>(false);
uint32_t id = networkConf["id"].as<uint32_t>(0U);
uint32_t id = networkConf["id"].as<uint32_t>(1001U);
uint32_t jitter = networkConf["talkgroupHang"].as<uint32_t>(360U);
std::string password = networkConf["password"].as<std::string>();
bool slot1 = networkConf["slot1"].as<bool>(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;

@ -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 <string>
@ -117,10 +117,11 @@ private:
std::vector<uint32_t> m_voiceChNo;
std::unordered_map<uint32_t, lookups::VoiceChData> 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;

File diff suppressed because it is too large Load Diff

@ -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 <string>
@ -48,7 +43,7 @@
// This class implements the interactive calibration mode.
// ---------------------------------------------------------------------------
class HOST_SW_API HostCal {
class HOST_SW_API HostCal : public HostSetup {
public:
/// <summary>Initializes a new instance of the HostCal class.</summary>
HostCal(const std::string& confFile);
@ -56,64 +51,10 @@ public:
~HostCal();
/// <summary>Executes the calibration processing loop.</summary>
int run();
int run(int argc, char **argv);
private:
const std::string& m_confFile;
yaml::Node m_conf;
modem::Modem* m_modem;
RingBuffer<uint8_t> 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;
/// <summary>Modem port open callback.</summary>
bool portModemOpen(modem::Modem* modem);
/// <summary>Modem port close callback.</summary>
bool portModemClose(modem::Modem* modem);
/// <summary>Modem clock callback.</summary>
bool portModemHandler(modem::Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM rspType, bool rspDblLen, const uint8_t* buffer, uint16_t len);
/// <summary>Helper to print the calibration help to the console.</summary>
void displayHelp();
@ -126,70 +67,17 @@ private:
bool setTXDCOffset(int incr);
/// <summary>Helper to change the Rx DC offset.</summary>
bool setRXDCOffset(int incr);
/// <summary>Helper to toggle modem transmit mode.</summary>
bool setTransmit();
/// <summary>Helper to change the DMR Symbol Level 3 adjust.</summary>
bool setDMRSymLevel3Adj(int incr);
/// <summary>Helper to change the DMR Symbol Level 1 adjust.</summary>
bool setDMRSymLevel1Adj(int incr);
/// <summary>Helper to change the P25 Symbol Level 3 adjust.</summary>
bool setP25SymLevel3Adj(int incr);
/// <summary>Helper to change the P25 Symbol Level 1 adjust.</summary>
bool setP25SymLevel1Adj(int incr);
/// <summary>Helper to change the NXDN Symbol Level 3 adjust.</summary>
bool setNXDNSymLevel3Adj(int incr);
/// <summary>Helper to change the NXDN Symbol Level 1 adjust.</summary>
bool setNXDNSymLevel1Adj(int incr);
/// <summary>Process DMR Rx BER.</summary>
void processDMRBER(const uint8_t* buffer, uint8_t seq);
/// <summary>Process DMR Tx 1011hz BER.</summary>
void processDMR1KBER(const uint8_t* buffer, uint8_t seq);
/// <summary>Process P25 Rx BER.</summary>
void processP25BER(const uint8_t* buffer);
/// <summary>Process P25 Tx 1011hz BER.</summary>
void processP251KBER(const uint8_t* buffer);
/// <summary>Process NXDN Rx BER.</summary>
void processNXDNBER(const uint8_t* buffer);
/// <summary>Helper to change the Tx coarse level.</summary>
bool setTXCoarseLevel(int incr);
/// <summary>Helper to change the Rx coarse level.</summary>
bool setRXCoarseLevel(int incr);
/// <summary>Helper to change the Rx fine level.</summary>
bool setRXFineLevel(int incr);
/// <summary>Helper to change the RSSI level.</summary>
bool setRSSICoarseLevel(int incr);
/// <summary>Write configuration to the modem DSP.</summary>
bool writeConfig();
/// <summary>Write configuration to the modem DSP.</summary>
bool writeConfig(uint8_t modeOverride);
/// <summary>Write RF parameters to the air interface modem.</summary>
bool writeRFParams();
/// <summary>Write symbol level adjustments to the modem DSP.</summary>
bool writeSymbolAdjust();
/// <summary>Helper to sleep the calibration thread.</summary>
void sleep(uint32_t ms);
/// <summary>Read the configuration area on the air interface modem.</summary>
bool readFlash();
/// <summary>Process the configuration data from the air interface modem.</summary>
void processFlashConfig(const uint8_t *buffer);
/// <summary>Erase the configuration area on the air interface modem.</summary>
bool eraseFlash();
/// <summary>Write the configuration area on the air interface modem.</summary>
bool writeFlash();
/// <summary>Helper to clock the calibration BER timer.</summary>
void timerClock();
/// <summary>Helper to start the calibration BER timer.</summary>
void timerStart();
/// <summary>Helper to stop the calibration BER timer.</summary>
void timerStop();
/// <summary>Retrieve the current status from the air interface modem.</summary>
void getStatus();
/// <summary>Prints the current status of the calibration.</summary>
void printStatus();
/// <summary>Add data frame to the data ring buffer.</summary>
void addFrame(const uint8_t* data, uint32_t length, uint32_t maxFrameSize);
/// <summary>Counts the total number of bit errors between bytes.</summary>
uint8_t countErrs(uint8_t a, uint8_t b);
};
#endif // __HOST_CAL_H__

@ -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 <cstdio>
#include <cstdarg>
#include <algorithm>
#include <functional>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <pwd.h>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define IDLE_WARMUP_MS 5U
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the HostFNE class.
/// </summary>
/// <param name="confFile">Full-path to the configuration file.</param>
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 */
}
/// <summary>
/// Finalizes a instance of the HostFNE class.
/// </summary>
HostFNE::~HostFNE()
{
/* stub */
}
/// <summary>
/// Executes the main FNE processing loop.
/// </summary>
/// <returns>Zero if successful, otherwise error occurred.</returns>
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<bool>(false);
if (m_daemon && g_foreground)
m_daemon = false;
// initialize system logging
yaml::Node logConf = m_conf["log"];
ret = ::LogInitialise(logConf["filePath"].as<std::string>(), logConf["fileRoot"].as<std::string>(),
logConf["fileLevel"].as<uint32_t>(0U), logConf["displayLevel"].as<uint32_t>(0U));
if (!ret) {
::fatal("unable to open the log file\n");
}
ret = ::ActivityLogInitialise(logConf["activityFilePath"].as<std::string>(), logConf["fileRoot"].as<std::string>());
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<std::string>();
uint32_t ridReloadTime = systemConf["radio_id"]["time"].as<uint32_t>(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
// ---------------------------------------------------------------------------
/// <summary>
/// Reads basic configuration parameters from the YAML configuration file.
/// </summary>
bool HostFNE::readParams()
{
yaml::Node systemConf = m_conf["system"];
m_pingTime = systemConf["pingTime"].as<uint32_t>(5U);
m_maxMissedPings = systemConf["maxMissedPings"].as<uint32_t>(5U);
m_updateLookupTime = systemConf["tgRuleUpdateTime"].as<uint32_t>(10U);
bool sendTalkgroups = systemConf["sendTalkgroups"].as<bool>(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<bool>(true);
m_allowDiagnosticTransfer = systemConf["allowDiagnosticTransfer"].as<bool>(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<std::string>();
uint32_t talkgroupConfigReload = talkgroupRules["time"].as<uint32_t>(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;
}
/// <summary>
/// Initializes master FNE network connectivity.
/// </summary>
bool HostFNE::createMasterNetwork()
{
yaml::Node masterConf = m_conf["master"];
std::string address = masterConf["address"].as<std::string>();
uint16_t port = (uint16_t)masterConf["port"].as<uint32_t>(TRAFFIC_DEFAULT_PORT);
uint32_t id = masterConf["peerId"].as<uint32_t>(1001U);
std::string password = masterConf["password"].as<std::string>();
bool verbose = masterConf["verbose"].as<bool>(false);
bool debug = masterConf["debug"].as<bool>(false);
m_dmrEnabled = masterConf["allowDMRTraffic"].as<bool>(true);
m_p25Enabled = masterConf["allowP25Traffic"].as<bool>(true);
m_nxdnEnabled = masterConf["allowNXDNTraffic"].as<bool>(true);
uint32_t parrotDelay = masterConf["parrotDelay"].as<uint32_t>(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;
}
/// <summary>
/// Initializes peer FNE network connectivity.
/// </summary>
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<bool>(false);
std::string address = peerConf["address"].as<std::string>();
uint16_t port = (uint16_t)peerConf["port"].as<uint32_t>(TRAFFIC_DEFAULT_PORT);
std::string masterAddress = peerConf["masterAddress"].as<std::string>();
uint16_t masterPort = (uint16_t)peerConf["masterPort"].as<uint32_t>(TRAFFIC_DEFAULT_PORT);
std::string password = peerConf["password"].as<std::string>();
uint32_t id = peerConf["peerId"].as<uint32_t>(1001U);
bool debug = peerConf["debug"].as<bool>(false);
std::string identity = peerConf["identity"].as<std::string>();
uint32_t rxFrequency = peerConf["rxFrequency"].as<uint32_t>(0U);
uint32_t txFrequency = peerConf["txFrequency"].as<uint32_t>(0U);
float latitude = peerConf["latitude"].as<float>(0.0F);
float longitude = peerConf["longitude"].as<float>(0.0F);
std::string location = peerConf["location"].as<std::string>();
::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;
}
/// <summary>
/// Processes any peer network traffic.
/// </summary>
/// <param name="peerNetwork"></param>
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);
}
}
}

@ -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 <string>
#include <unordered_map>
#include <vector>
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the core FNE service logic.
// ---------------------------------------------------------------------------
class HOST_SW_API HostFNE {
public:
/// <summary>Initializes a new instance of the HostFNE class.</summary>
HostFNE(const std::string& confFile);
/// <summary>Finalizes a instance of the HostFNE class.</summary>
~HostFNE();
/// <summary>Executes the main FNE host processing loop.</summary>
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<std::string, network::Network*> m_peerNetworks;
uint32_t m_pingTime;
uint32_t m_maxMissedPings;
uint32_t m_updateLookupTime;
bool m_allowActivityTransfer;
bool m_allowDiagnosticTransfer;
/// <summary>Reads basic configuration parameters from the INI.</summary>
bool readParams();
/// <summary>Initializes master FNE network connectivity.</summary>
bool createMasterNetwork();
/// <summary>Initializes peer FNE network connectivity.</summary>
bool createPeerNetworks();
/// <summary>Processes any peer network traffic.</summary>
void processPeer(network::Network* peerNetwork);
};
#endif // __HOST_FNE_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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the base class for adjustment windows.
// ---------------------------------------------------------------------------
class HOST_SW_API AdjustWndBase : public finalcut::FDialog
{
public:
/// <summary>
/// Initializes a new instance of the AdjustWndBase class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
explicit AdjustWndBase(HostSetup* setup, FWidget* widget = nullptr) : FDialog{widget},
m_setup(setup)
{
/* stub */
}
protected:
HostSetup *m_setup;
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
virtual void adjustSize() override
{
FDialog::adjustSize();
}
/*
** Event Handlers
*/
/// <summary>
///
/// </summary>
/// <param name="e"></param>
virtual void onKeyPress(finalcut::FKeyEvent* e)
{
const auto key = e->key();
if (key == FKey::F12) {
setTransmit();
}
else if (key == FKey::F2) {
m_setup->saveConfig();
}
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
virtual void onClose(FCloseEvent* e) override
{
hide();
}
private:
FLabel m_connectedLabel{"Modem Disconnected", this};
FButton m_txButton{"Transmit", this};
FButton m_closeButton{"Close", this};
/// <summary>
///
/// </summary>
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__

@ -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 <array>
#include <map>
#include <vector>
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the bit error rate display window.
// ---------------------------------------------------------------------------
class HOST_SW_API BERDisplayWnd final : public finalcut::FDialog
{
public:
/// <summary>
/// Initializes a new instance of the BERDisplayWnd class.
/// </summary>
/// <param name="widget"></param>
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} }
};
}
/// <summary>Copy constructor.</summary>
BERDisplayWnd(const BERDisplayWnd&) = delete;
/// <summary>Move constructor.</summary>
BERDisplayWnd(BERDisplayWnd&&) noexcept = delete;
/// <summary>Finalizes an instance of the ModemStatusWnd class.</summary>
~BERDisplayWnd() noexcept override = default;
/// <summary>Disable copy assignment operator (=).</summary>
auto operator= (const BERDisplayWnd&) -> BERDisplayWnd& = delete;
/// <summary>Disable move assignment operator (=).</summary>
auto operator= (BERDisplayWnd&&) noexcept -> BERDisplayWnd& = delete;
/// <summary>Disable set X coordinate.</summary>
void setX(int, bool = true) override { }
/// <summary>Disable set Y coordinate.</summary>
void setY(int, bool = true) override { }
/// <summary>Disable set position.</summary>
void setPos(const FPoint&, bool = true) override { }
/// <summary>
/// Helper to set the BER text.
/// </summary>
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();
}
/// <summary>Helper to set the segment color.</summary>
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<wchar_t, Segment> m_code{};
std::array<FString, 3> m_line{};
FColor m_segmentColor{FColor::LightRed};
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
void draw() override
{
std::vector<FVTermBuffer> 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, ' '};
}
/// <summary>
///
/// </summary>
/// <param name="c"></param>
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<wchar_t, 3> h{{0x20, 0x2584, 0x2588}};
constexpr std::array<wchar_t, 3> 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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the channel configuration window.
// ---------------------------------------------------------------------------
class HOST_SW_API ChannelConfigSetWnd final : public CloseWndBase
{
public:
/// <summary>
/// Initializes a new instance of the ChannelConfigSetWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Channel Configuration");
FDialog::setSize(FSize{60, 17});
m_enableSetButton = false;
CloseWndBase::initLayout();
}
/// <summary>
///
/// </summary>
void initControls()
{
yaml::Node rfssConfig = m_setup->m_conf["system"]["config"];
m_setup->m_channelId = (uint8_t)rfssConfig["channelId"].as<uint32_t>(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<std::string>("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<std::string>("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();
}
/// <summary>
///
/// </summary>
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__

@ -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 <final/final.h>
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:
/// <summary>
/// Initializes a new instance of the CloseWndBase class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
virtual void adjustSize() override
{
FDialog::adjustSize();
}
/*
** Event Handlers
*/
/// <summary>
///
/// </summary>
/// <param name="e"></param>
virtual void onKeyPress(finalcut::FKeyEvent* e)
{
const auto key = e->key();
if (key == FKey::F2) {
m_setup->saveConfig();
}
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
virtual void onClose(FCloseEvent* e) override
{
hide();
}
private:
FButton m_closeButton{"Close", this};
};
#endif // __CLOSE_WND_BASE_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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the FIFO buffer adjustment window.
// ---------------------------------------------------------------------------
class HOST_SW_API FIFOBufferAdjustWnd final : public CloseWndBase
{
public:
/// <summary>
/// Initializes a new instance of the FIFOBufferAdjustWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("FIFO Buffer Adjustment");
FDialog::setSize(FSize{60, 13});
m_enableSetButton = true;
CloseWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the hotspot bandwidth adjustment window.
// ---------------------------------------------------------------------------
class HOST_SW_API HSBandwidthAdjustWnd final : public AdjustWndBase
{
public:
/// <summary>
/// Initializes a new instance of the HSBandwidthAdjustWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Hotspot Bandwidth Adjustment");
FDialog::setSize(FSize{60, 15});
AdjustWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the hotspot gain adjustment window.
// ---------------------------------------------------------------------------
class HOST_SW_API HSGainAdjustWnd final : public AdjustWndBase
{
public:
/// <summary>
/// Initializes a new instance of the HSGainAdjustWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Hotspot Gain & AFC Adjustment");
FDialog::setSize(FSize{50, 22});
AdjustWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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__

File diff suppressed because it is too large Load Diff

@ -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 <string>
// ---------------------------------------------------------------------------
// 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:
/// <summary>Initializes a new instance of the HostSetup class.</summary>
HostSetup(const std::string& confFile);
/// <summary>Finalizes a instance of the HostSetup class.</summary>
~HostSetup();
virtual ~HostSetup();
#if defined(ENABLE_SETUP_TUI)
/// <summary>Executes the processing loop.</summary>
int run();
virtual int run(int argc, char** argv);
#else
/// <summary>Executes the processing loop.</summary>
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<uint8_t> 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;
/// <summary>Helper to print the help to the console.</summary>
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;
/// <summary>Modem port open callback.</summary>
bool portModemOpen(modem::Modem* modem);
/// <summary>Modem port close callback.</summary>
bool portModemClose(modem::Modem* modem);
/// <summary>Modem clock callback.</summary>
bool portModemHandler(modem::Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM rspType, bool rspDblLen, const uint8_t* buffer, uint16_t len);
/// <summary>Helper to save configuration.</summary>
void saveConfig();
/// <summary>Helper to calculate the Rx/Tx frequencies.</summary>
bool calculateRxTxFreq();
bool calculateRxTxFreq(bool consoleDisplay = false);
/// <summary>Helper to log the system configuration parameters.</summary>
void displayConfigParams();
/// <summary>Initializes the modem DSP.</summary>
bool createModem(bool consoleDisplay = false);
/// <summary>Helper to toggle modem transmit mode.</summary>
bool setTransmit();
/// <summary>Helper to update BER display window.</summary>
void updateTUIBER(float ber);
/// <summary>Process DMR Rx BER.</summary>
void processDMRBER(const uint8_t* buffer, uint8_t seq);
/// <summary>Process DMR Tx 1011hz BER.</summary>
void processDMR1KBER(const uint8_t* buffer, uint8_t seq);
/// <summary>Process P25 Rx BER.</summary>
void processP25BER(const uint8_t* buffer);
/// <summary>Process P25 Tx 1011hz BER.</summary>
void processP251KBER(const uint8_t* buffer);
/// <summary>Process NXDN Rx BER.</summary>
void processNXDNBER(const uint8_t* buffer);
/// <summary>Helper to sleep the thread.</summary>
/// <summary>Write configuration to the modem DSP.</summary>
bool writeConfig();
/// <summary>Write configuration to the modem DSP.</summary>
bool writeConfig(uint8_t modeOverride);
/// <summary>Write RF parameters to the air interface modem.</summary>
bool writeRFParams();
/// <summary>Write symbol level adjustments to the modem DSP.</summary>
bool writeSymbolAdjust();
/// <summary>Write transmit FIFO buffer lengths.</summary>
bool writeFifoLength();
/// <summary>Helper to sleep the calibration thread.</summary>
void sleep(uint32_t ms);
/// <summary>Read the configuration area on the air interface modem.</summary>
bool readFlash();
/// <summary>Process the configuration data from the air interface modem.</summary>
void processFlashConfig(const uint8_t *buffer);
/// <summary>Erase the configuration area on the air interface modem.</summary>
bool eraseFlash();
/// <summary>Write the configuration area on the air interface modem.</summary>
bool writeFlash();
/// <summary>Helper to clock the calibration BER timer.</summary>
void timerClock();
/// <summary>Helper to start the calibration BER timer.</summary>
void timerStart();
/// <summary>Helper to stop the calibration BER timer.</summary>
void timerStop();
/// <summary>Retrieve the current status from the air interface modem.</summary>
void getStatus();
#if defined(ENABLE_SETUP_TUI)
/// <summary>Prints the current status.</summary>
void printStatus();
virtual void printStatus();
#else
/// <summary>Prints the current status.</summary>
virtual void printStatus() = 0;
#endif // defined(ENABLE_SETUP_TUI)
/// <summary>Add data frame to the data ring buffer.</summary>
void addFrame(const uint8_t* data, uint32_t length, uint32_t maxFrameSize);
/// <summary>Counts the total number of bit errors between bytes.</summary>
uint8_t countErrs(uint8_t a, uint8_t b);
};
#endif // __HOST_SETUP_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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the modem level adjustment window.
// ---------------------------------------------------------------------------
class HOST_SW_API LevelAdjustWnd final : public AdjustWndBase
{
public:
/// <summary>
/// Initializes a new instance of the LevelAdjustWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Modem Level Adjustment");
FDialog::setSize(FSize{65, 22});
AdjustWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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__

@ -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 <final/final.h>
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:
/// <summary>
/// Initializes a new instance of the LogDisplayWnd class.
/// </summary>
/// <param name="widget"></param>
explicit LogDisplayWnd(FWidget* widget = nullptr) : FDialog{widget}
{
m_scrollText.ignorePadding();
m_timerId = addTimer(250); // starts the timer every 250 milliseconds
}
/// <summary>Copy constructor.</summary>
LogDisplayWnd(const LogDisplayWnd&) = delete;
/// <summary>Move constructor.</summary>
LogDisplayWnd(LogDisplayWnd&&) noexcept = delete;
/// <summary>Finalizes an instance of the LogDisplayWnd class.</summary>
~LogDisplayWnd() noexcept override = default;
/// <summary>Disable copy assignment operator (=).</summary>
auto operator= (const LogDisplayWnd&) -> LogDisplayWnd& = delete;
/// <summary>Disable move assignment operator (=).</summary>
auto operator= (LogDisplayWnd&&) noexcept -> LogDisplayWnd& = delete;
/// <summary>Disable set X coordinate.</summary>
void setX(int, bool = true) override { }
/// <summary>Disable set Y coordinate.</summary>
void setY(int, bool = true) override { }
/// <summary>Disable set position.</summary>
void setPos(const FPoint&, bool = true) override { }
private:
FTextView m_scrollText{this};
int m_timerId;
/// <summary>
///
/// </summary>
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
*/
/// <summary>
///
/// </summary>
/// <param name="timer"></param>
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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the logging and data configuration window.
// ---------------------------------------------------------------------------
class HOST_SW_API LoggingAndDataSetWnd final : public CloseWndBase
{
public:
/// <summary>
/// Initializes a new instance of the LoggingAndDataSetWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Logging and Data Configuration");
FDialog::setSize(FSize{68, 19});
m_enableSetButton = false;
CloseWndBase::initLayout();
}
/// <summary>
///
/// </summary>
void initControls()
{
yaml::Node logConf = m_setup->m_conf["log"];
uint32_t logLevel = logConf["fileLevel"].as<uint32_t>(1U);
std::string logFilePath = logConf["filePath"].as<std::string>();
std::string logActFilePath = logConf["activityFilePath"].as<std::string>();
// 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<std::string>();
yaml::Node radioId = m_setup->m_conf["system"]["radio_id"];
std::string ridFilePath = radioId["file"].as<std::string>();
yaml::Node talkgroupId = m_setup->m_conf["system"]["talkgroup_id"];
std::string tgidFilePath = talkgroupId["file"].as<std::string>();
// 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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the modem status display window.
// ---------------------------------------------------------------------------
class HOST_SW_API ModemStatusWnd final : public finalcut::FDialog
{
public:
/// <summary>
/// Initializes a new instance of the ModemStatusWnd class.
/// </summary>
/// <param name="widget"></param>
explicit ModemStatusWnd(FWidget* widget = nullptr) : FDialog{widget}
{
m_scrollText.ignorePadding();
}
/// <summary>Copy constructor.</summary>
ModemStatusWnd(const ModemStatusWnd&) = delete;
/// <summary>Move constructor.</summary>
ModemStatusWnd(ModemStatusWnd&&) noexcept = delete;
/// <summary>Finalizes an instance of the ModemStatusWnd class.</summary>
~ModemStatusWnd() noexcept override = default;
/// <summary>Disable copy assignment operator (=).</summary>
auto operator= (const ModemStatusWnd&) -> ModemStatusWnd& = delete;
/// <summary>Disable move assignment operator (=).</summary>
auto operator= (ModemStatusWnd&&) noexcept -> ModemStatusWnd& = delete;
/// <summary>Disable set X coordinate.</summary>
void setX(int, bool = true) override { }
/// <summary>Disable set Y coordinate.</summary>
void setY(int, bool = true) override { }
/// <summary>Disable set position.</summary>
void setPos(const FPoint&, bool = true) override { }
/// <summary>
/// Helper to append text.
/// </summary>
void append(std::string str)
{
if (str.empty()) {
return;
}
m_scrollText.append(str);
m_scrollText.scrollToEnd();
redraw();
}
/// <summary>
/// Helper to clear the text.
/// </summary>
void clear() { m_scrollText.clear(); }
private:
FTextView m_scrollText{this};
/// <summary>
///
/// </summary>
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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the finalcut application.
// ---------------------------------------------------------------------------
class HOST_SW_API SetupApplication final : public finalcut::FApplication {
public:
/// <summary>
/// Initializes a new instance of the SetupWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="argc"></param>
/// <param name="argv"></param>
explicit SetupApplication(HostSetup* setup, const int& argc, char** argv) : FApplication{argc, argv},
m_setup(setup)
{
m_statusRefreshTimer = addTimer(1000);
}
protected:
/// <summary>
///
/// </summary>
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<p25::NID> 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
*/
/// <summary>
///
/// </summary>
/// <param name="timer"></param>
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__

@ -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 <final/final.h>
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:
/// <summary>
/// Initializes a new instance of the SetupMainWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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();
});
}
/// <summary>
/// Helper to set menu states.
/// </summary>
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();
}
/// <summary>
/// Helper to update menu states.
/// </summary>
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();
}
}
}
/// <summary>Gets the instance of HostSetup.</summary>
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};
/// <summary>
/// Helper to reset the BER window to a default state.
/// </summary>
void resetBERWnd(bool show = false)
{
if (show) {
m_berWnd.show();
}
else {
m_berWnd.hide();
}
m_berWnd.ber("-.---");
m_berWnd.segmentColor(FColor::LightGray);
}
/*
** Event Handlers
*/
/// <summary>
///
/// </summary>
/// <param name="e"></param>
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
*/
/// <summary>
/// "Save Settings" menu item click callback.
/// </summary>
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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the site parameters configuration window.
// ---------------------------------------------------------------------------
class HOST_SW_API SiteParamSetWnd final : public CloseWndBase
{
public:
/// <summary>
/// Initializes a new instance of the SiteParamSetWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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)
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Site Parameters");
FDialog::setSize(FSize{63, 20});
m_enableSetButton = false;
CloseWndBase::initLayout();
}
/// <summary>
///
/// </summary>
void initControls()
{
yaml::Node cwId = m_setup->m_conf["system"]["cwId"];
bool enabled = cwId["enable"].as<bool>(false);
uint32_t cwTime = cwId["time"].as<uint32_t>(10U);
std::string callsign = cwId["callsign"].as<std::string>();
// 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<uint32_t>(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<std::string>("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<uint32_t>(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<std::string>("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<std::string>("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<std::string>("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<std::string>("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<std::string>("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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the symbol level adjustment window.
// ---------------------------------------------------------------------------
class HOST_SW_API SymbLevelAdjustWnd final : public AdjustWndBase
{
public:
/// <summary>
/// Initializes a new instance of the SymbLevelAdjustWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Symbol Level Adjustment");
FDialog::setSize(FSize{60, 16});
AdjustWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the system configuration window.
// ---------------------------------------------------------------------------
class HOST_SW_API SystemConfigSetWnd final : public CloseWndBase
{
public:
/// <summary>
/// Initializes a new instance of the SystemConfigSetWnd class.
/// </summary>
/// <param name="setup"></param>
/// <param name="widget"></param>
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)
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("System Configuration");
FDialog::setSize(FSize{56, 22});
m_enableSetButton = false;
CloseWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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<std::string>("/dev/ttyUSB0");
uint32_t portSpeed = uartConfig["speed"].as<uint32_t>(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<std::string>();
uint32_t timeout = m_setup->m_conf["system"]["timeout"].as<uint32_t>();
bool duplex = m_setup->m_conf["system"]["duplex"].as<bool>(true);
bool simplexSameFrequency = m_setup->m_conf["system"]["simplexSameFrequency"].as<bool>(false);
uint32_t modeHang = m_setup->m_conf["system"]["modeHang"].as<uint32_t>();
uint32_t rfTalkgroupHang = m_setup->m_conf["system"]["rfTalkgroupHang"].as<uint32_t>();
bool fixedMode = m_setup->m_conf["system"]["fixedMode"].as<bool>(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<bool>(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<bool>(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<bool>(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__

@ -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;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save

Powered by TurnKey Linux.