Compare commits
336 Commits
max-skip-t
...
master
Author | SHA1 | Date |
---|---|---|
Max | 6f1b25d2d1 | |
Max | 1d56794504 | |
Max | a47bb712d2 | |
Max | 2feb62e8cf | |
Max | 310ad30a21 | |
Max | eef44be741 | |
Max | b05a4bcc4a | |
Max | c10dde6065 | |
Max | fbe4b881bb | |
Max | e63c106ce2 | |
Max | 8a3bbae9bc | |
Max | dc6856d26c | |
Max | ac9cfa58b7 | |
Max | cbb6462106 | |
Max | 7b31ebd34e | |
Max | 7bc0fb2c1b | |
Max | 874b72042c | |
Max | ebc980c205 | |
Max | 7dc6d61f75 | |
Max | d90ecd7c89 | |
Max | de370340cf | |
Max | 245ff05489 | |
Max | 1cd344a838 | |
Max | 16e8b53fbf | |
Max | 4028ec6a4e | |
Max | e7fc1c6c45 | |
Max | f010f3f5dc | |
Matt Ames | dc90a85c02 | |
Max | dd830a1463 | |
Max | 1e7b8b43a2 | |
Max | f51999c49f | |
Max | 72ccc09ee0 | |
Max | 86a609d743 | |
Max | b2ec8b376e | |
Max | bc0ba19e9b | |
Max | c6b3799b96 | |
Max | 3af1dbe0db | |
Max | 387cf57662 | |
Max | ff96eb549b | |
Max | 6966b5fbe3 | |
Max | 48b0de1986 | |
Max | e965205121 | |
Max | 713e632e6c | |
Max | f8a9b6128a | |
Max | 23cd0111b7 | |
Max | cca08a1694 | |
Max | 702370af8c | |
Max | d1ee843822 | |
Max | 3b687098d0 | |
Max | ecc0c6f6b1 | |
Max | 84042249fc | |
Max | 61144fa477 | |
Max | b9752d54bf | |
Max | 53490d0c8c | |
Max | f31302bff7 | |
Max | 5e87fdc889 | |
Max | e540ade02b | |
Max | eb9a8be83d | |
Max | a148cf1fe5 | |
Max | 3492bd70b1 | |
Max | fbae3bcfde | |
Max | 2ee08e9c24 | |
Max | 5818d58dba | |
Max | 8a948a3bee | |
Max | 8b77120005 | |
Max | d9794b5123 | |
Max | e40f2c53da | |
Max | ddd70bdd07 | |
Max | 13a3bccb9e | |
Max | 7f37fada79 | |
Max | c06a318222 | |
Max | f14bf6deb8 | |
Max | 676409131d | |
Max | 5ef6bd7862 | |
Max | 0ecddbb1bf | |
Matt Ames | c2cebde6d5 | |
Max | 56daa99b94 | |
Max | f93460a0bd | |
Max | e080f32fc6 | |
Max | 65b5c318b0 | |
Max | a48ba912ea | |
Max | a4c7d3852f | |
Max | 233f810d3b | |
Max | 855b590f2d | |
Max | 3e2aece2e6 | |
Max | a8d8dbc882 | |
Max | 3a3509fbf0 | |
Max | ecc8db384f | |
Max | 5c53f3f643 | |
Max | 70143fa2f7 | |
Max | ced242a072 | |
Max | b1e7d81f5e | |
Max | a169f37c3a | |
Max | d964703e83 | |
Max | b57db0d599 | |
Max | 6fb7a6b1ca | |
Max | 7373d10e1c | |
Max | 7290f13486 | |
Max | 47052abc1a | |
Max | db9e7db946 | |
Max | f212d1b6ff | |
Max | 806d7c44ca | |
Max | 51042858e8 | |
Max | 180a4dec97 | |
Max | aec5a8012c | |
Max | ccc0001872 | |
Max | dced944480 | |
Max | 91dead3b5b | |
Max | 3aefbf332b | |
Max | c3010f3265 | |
Max | 18c1cc0a54 | |
Max | ed3334ca6c | |
Max | 2dcbbbcdc2 | |
Max | e971078c55 | |
Max | 59a631b700 | |
Max | ccf9a9f20b | |
Max | 76ca14e5e8 | |
Max | e540ac9c54 | |
Max | 5c5bd11a0c | |
Max | 27ea95ee38 | |
Matt Ames | d8c01956ba | |
Matt Ames | bdd6707dc5 | |
Max | 3612767021 | |
Matt Ames | 679336d55d | |
Max | 19b720715c | |
Max | 856ed97419 | |
Max | a6bd25163a | |
Max | 28ef6960cc | |
Max | 1180f9d8b6 | |
Max | a11ef18339 | |
Max | 478d2cba70 | |
Max | 3a0901e646 | |
Max | 62e2ef92f0 | |
Max | a5df0ec044 | |
Max | 94dc7536e6 | |
Max | 9d16effff3 | |
Max | 0421a55968 | |
Max | 602cb0659f | |
Max | 1da0714da5 | |
Max | 5e5566f0f3 | |
Max | ffb2035c67 | |
Max | 82364672ed | |
Max | 921a1d8ea0 | |
Max | 5e01df4883 | |
Max | 3b69f5e335 | |
Max | 0686f2de14 | |
Max | 733e891d22 | |
Max | 57a682d5f0 | |
Max | c26b51e0c1 | |
Max | 18042e1368 | |
Max | 8b00b8459a | |
Max | 92ba1df2c0 | |
Max | 6f02fc63cc | |
Max | b91039ecd0 | |
Max | 3de2d3e440 | |
Max | e25e7be488 | |
Max | b6b0eeb7b8 | |
Max | 140a76ac76 | |
Max | 538ae6015b | |
Max | 111b311d88 | |
Max | f974a32a19 | |
Max | 6064229027 | |
Max | 66d0ee86fd | |
Max | 4a7cf6b79c | |
Max | c9fa2f6a31 | |
Max | c4fe5610ba | |
Max | 582ba4395a | |
Max | 0f2f040307 | |
Matt Ames | 652b29b7a2 | |
Matt Ames | 7afb19a5b4 | |
Matt Ames | a07ba04296 | |
Max | 74db47bd06 | |
Max | 971a7502aa | |
Matt Ames | 0ece965f78 | |
Matt Ames | 933cb72121 | |
Matt Ames | 37a9dbdce1 | |
Matt Ames | 302115e19e | |
Matt Ames | 9a5820d614 | |
Matt Ames | ff09558c41 | |
Max | 0ae2b9624f | |
Matt Ames | d408c7fee2 | |
Max | 9c01b98542 | |
Max | 94a34dc4df | |
Max | 384c8c28d0 | |
Max | 3c50b3c54b | |
Matt Ames | a9fc0c206a | |
Matt Ames | 8a8ab6e90b | |
Max | 332a5968b9 | |
Max | 31e11e999d | |
Max | c9bfc96ed0 | |
Max | e0eabf10f0 | |
Max | 7bc0a5ef96 | |
Max | c0bbdc9c70 | |
Max | 85d09681fe | |
Max | bd95950d8c | |
Max | e572cfe802 | |
Max | 9d344f283b | |
Max | 411d385125 | |
Max | abf9b4f06c | |
Max | 5ea5186c5d | |
Max | 4630e58564 | |
Max | c17443d9ac | |
Max | 69f2091f02 | |
Max | 993c52686c | |
Max | 2ff4ae5a38 | |
Max | a77c18d745 | |
Max | 6b45a95754 | |
Max | 7bdfe78aa0 | |
Max | 4cde5ac5d8 | |
Max | 43bd7e7ae1 | |
Max | d5ee60abf8 | |
Max | 34d08d9255 | |
Max | 6ecebfffcc | |
Max | 9d0747b1ca | |
Max | d16702fb9e | |
Max | b28a976768 | |
Max | dc5b77ff9b | |
Max | ee399ed962 | |
Max | 25ae933b47 | |
Max | 38b2cb60be | |
Max | d25d93cf9e | |
Max | 9b93b5699d | |
Max | cd16c15c54 | |
Max | 18e96a2307 | |
Max | 9f8b9af3a4 | |
Max | 682bd4abcb | |
Max | e9a6e3806f | |
Max | e79d7eaabc | |
Max | 5601e5290e | |
Max | 3b26dd2fd2 | |
Max | 5f264c68bf | |
Max | b055a0b6d0 | |
Max | db1bd03e25 | |
Max | 1b1c8842c1 | |
Max | 90fbe46518 | |
Max | dd9bc87c86 | |
Max | 1fc2df5ac1 | |
Max | 04692d23e2 | |
Max | 0fcef6a5be | |
Max | 35da7d958a | |
Max | 5edfc1c463 | |
Max | ce129911bc | |
Max | d6615c84fe | |
Max | 5b280ae2ef | |
Max | 0927bc11a9 | |
Max | 796c81f219 | |
Max | a3f822dcc1 | |
Max | 98e43839b9 | |
Max | 84f0a32ee1 | |
Max | ef586696a1 | |
Max | d6f3be0604 | |
Max | 2d52d95a0a | |
Max | d3719bd11c | |
Max | 53dbfb4347 | |
Max | 43a70b67db | |
Max | 8fc1995181 | |
Max | a3cab238b5 | |
Max | e2cfbe9d1e | |
Max | 9858e0cecc | |
Max | 9f6c73e280 | |
Max | 7ae554682a | |
Max | aa7a4be349 | |
Max | 87f546a947 | |
Max | e7d67538ae | |
Max | 77b21b5e32 | |
Max | e31207cf88 | |
Max | 6ddc299edf | |
Max | c412a8fd5b | |
Max | bbe9506b46 | |
Max | bb38456f9c | |
Max | c8cf7a7da8 | |
Max | a9522ee636 | |
Max | 15f0acfa17 | |
Max | 37f2c3bbfc | |
Max | 68476ab6c1 | |
Max | 20743ddc60 | |
Max | a3f4963dea | |
Max | 04b16056df | |
Max | 93b19531ee | |
Max | 187f5d180c | |
Max | 2f3eb8908a | |
Max | 99926acda5 | |
Max | 8f79322244 | |
Max | 8c98a8be52 | |
Max | b5ec34562b | |
Max | d0eacef338 | |
Max | e9911c5df8 | |
Max | 45794418ae | |
Max | e6deba2cd5 | |
Max | 59a78e3671 | |
Max | 10aaadf1cd | |
Max | bcef5dfc29 | |
Max | 4ece3269e3 | |
Max | 596fe4a733 | |
Max | c348afa99e | |
Max | e383a7a719 | |
Max | 11cd46b8e7 | |
Max | ff3a65028e | |
Max | 82cf2c9217 | |
Max | 5972402ff5 | |
Max | 8b96413093 | |
Max | f9d7b65cc3 | |
Max | 299d5eb9bf | |
Max | b6390b9d17 | |
Max | f6693ab481 | |
Max | 356ce4b7ac | |
Max | a964f6ac52 | |
Max | f454b775ae | |
Max | d403abb882 | |
Max | 89ef1f3297 | |
Max | eb522cf5e5 | |
Max | 8242b8e0fc | |
Max | d0fd94fc2f | |
Max | fa76f07e56 | |
Max | cb410c1f95 | |
Max | 847621dd39 | |
Max | 560f0c5461 | |
Max | b81ad173b1 | |
Max | 72090fddbb | |
Max | 089a8f9284 | |
Max | 02e0ad5814 | |
Max | 668ff60e34 | |
Max | aaee53c998 | |
Max | a101714375 | |
Max | a83fbd8c86 | |
Max | 7214bc2614 | |
Max | 0bfa16971f | |
Max | 3a558b03b2 | |
Max | a74d80c67c | |
Max | e0a0636ee3 | |
Max | dc552b5d51 | |
Max | d8ebe71374 | |
Max | 158463961b | |
Balint Seeber | e2cf7087fc | |
Max | d34fbe52ed | |
Balint Seeber | aea5479646 |
|
@ -0,0 +1,4 @@
|
|||
# Ignore build artifacts
|
||||
build/
|
||||
# Ignore .pyc compiled python bytecode files
|
||||
*.pyc
|
|
@ -1,6 +1,27 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
project(gr-op25 CXX C)
|
||||
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
set(CMAKE_CXX_FLAGS "-std=c++11")
|
||||
|
||||
execute_process(COMMAND python3 -c "
|
||||
import os
|
||||
import sys
|
||||
from distutils import sysconfig
|
||||
pfx = '/usr/local'
|
||||
m1 = os.path.join('lib', 'python' + sys.version[:3], 'dist-packages')
|
||||
m2 = sysconfig.get_python_lib(plat_specific=True, prefix='')
|
||||
f1 = os.path.join(pfx, m1)
|
||||
f2 = os.path.join(pfx, m2)
|
||||
ok2 = f2 in sys.path
|
||||
if ok2:
|
||||
print(m2)
|
||||
else:
|
||||
print(m1)
|
||||
" OUTPUT_VARIABLE OP25_PYTHON_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
MESSAGE(STATUS "OP25_PYTHON_DIR has been set to \"${OP25_PYTHON_DIR}\".")
|
||||
|
||||
add_subdirectory(op25/gr-op25)
|
||||
add_subdirectory(op25/gr-op25_repeater)
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
As of this writing (Sept. 2022) OP25 builds for python3 and GNU Radio 3.8.
|
||||
|
||||
The full list of supported versions is as follows:
|
||||
|
||||
PYTHON 2 AND GNU RADIO 3.7
|
||||
==========================
|
||||
It should still be possible to use the file gr3.8.patch (in reverse) to
|
||||
downgrade the source tree to build against Python 2 and GNU Radio 3.7,
|
||||
although this has not been tested.
|
||||
$ cat gr3.8.patch | patch -p1 -R
|
||||
Once this has been done, proceed by running the install.sh script.
|
||||
|
||||
PYTHON 3 AND GNU RADIO 3.8
|
||||
==========================
|
||||
It is no longer necessary to apply the gr3.8 patch to the op25 source tree,
|
||||
as Python3/GNU Radio 3.8 is now the default. You can proceed directly to
|
||||
running the install.sh script.
|
||||
|
||||
PYTHON 3 AND GNU RADIO 3.9 / 3.10
|
||||
=================================
|
||||
It is no longer necessary to apply the gr3.8 patch to the op25 source tree,
|
||||
See the file README-gr3.9 for procedures for both GNU Radio 3.9 and 3.10.
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
Updated Sept. 2022
|
||||
|
||||
By default, OP25 now builds for Python3 and GNU Radio3.8.
|
||||
Accordingly, it is no longer necessary to apply the op25 patch
|
||||
for gr3.8.
|
||||
|
||||
It should be possible to use the gr3.8.patch in reverse to downgrade
|
||||
the source tree to build for Python 2 and GNU Radio 3.7. See the
|
||||
file README in this directory for details.
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
Running OP25 in Gnuradio 3.9 and 3.10 Date: May 31, 2022
|
||||
==============================================================
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
0. First remove any existing OP25 installation
|
||||
cd ...path-to-existing/op25/build
|
||||
sudo make uninstall
|
||||
|
||||
1. Step 1 is removed.
|
||||
|
||||
2. Run the command
|
||||
./install-gr3.9.sh
|
||||
(Note, do not use the standard install.sh script for gr3.9 and/or gr3.10).
|
||||
|
||||
Bugs
|
||||
----
|
||||
|
||||
1. The location for WIN_HANN, WIN_HAMMING, and so forth has moved to
|
||||
fft.window (formerly filter.firdes). The python has not yet been
|
||||
updated to reflect.
|
||||
|
||||
2. A strange hang condition has been observed in rx.py - the bug is not
|
||||
fully understood. The strace output shows many futex calls ending
|
||||
with a "timeout" - not clear if some missing event might be causing this
|
||||
stall condition... User feedback is requested...
|
||||
|
||||
3. Currently the cmake process is not centralized; 'make uninstall' must
|
||||
therefore be run once from each of the 'build' and 'build_repeater'
|
||||
directories under 'src/'.
|
|
@ -0,0 +1,231 @@
|
|||
#! /usr/bin/python3
|
||||
|
||||
# Copyright 2022, Max H. Parke KA1RBI
|
||||
#
|
||||
# This file is part of GNU Radio and part of OP25
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import glob
|
||||
import shutil
|
||||
from gnuradio.modtool.core.newmod import ModToolNewModule
|
||||
from gnuradio.modtool.core.add import ModToolAdd
|
||||
from gnuradio.modtool.core.bind import ModToolGenBindings
|
||||
|
||||
msg = """
|
||||
This 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 3, or (at your option)
|
||||
any later version.
|
||||
"""
|
||||
|
||||
print('\n%s Copyright 2022, Max H. Parke KA1RBI\nhttps://osmocom.org/projects/op25\n%s' % (sys.argv[0], msg))
|
||||
|
||||
TLD = 'op25'
|
||||
|
||||
MODS={
|
||||
'op25': 'decoder_bf decoder_ff fsk4_demod_ff fsk4_slicer_fb pcap_source_b message msg_queue msg_handler'.split(),
|
||||
'op25_repeater': 'ambe_encoder_sb dmr_bs_tx_bb dstar_tx_sb frame_assembler fsk4_slicer_fb gardner_costas_cc nxdn_tx_sb p25_frame_assembler vocoder ysf_tx_sb'.split()
|
||||
}
|
||||
|
||||
SKIP_CC = 'd2460.cc qa_op25.cc test_op25.cc qa_op25_repeater.cc test_op25_repeater.cc message.cc msg_queue.cc msg_handler.cc'.split()
|
||||
|
||||
SRC_DIR = sys.argv[1]
|
||||
DEST_DIR = sys.argv[2]
|
||||
|
||||
if '..' in SRC_DIR or not SRC_DIR.startswith('/'):
|
||||
sys.stderr.write('error, %s must be an absolute path\n' % SRC_DIR)
|
||||
sys.exit(1)
|
||||
|
||||
if not os.access(SRC_DIR, os.R_OK):
|
||||
sys.stderr.write('error, unable to access %s\n' % SRC_DIR)
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isdir(SRC_DIR + '/op25/gr-op25_repeater'):
|
||||
sys.stderr.write('error, op25 package not found in %s\n' % SRC_DIR)
|
||||
sys.exit(3)
|
||||
|
||||
if os.access(DEST_DIR, os.F_OK) or os.path.isdir(DEST_DIR):
|
||||
sys.stderr.write('error, destination path %s must not exist\n' % DEST_DIR)
|
||||
sys.exit(4)
|
||||
|
||||
os.mkdir(DEST_DIR)
|
||||
op25_dir = DEST_DIR + '/op25'
|
||||
os.mkdir(op25_dir)
|
||||
os.chdir(op25_dir)
|
||||
|
||||
SCRIPTS = SRC_DIR + '/scripts'
|
||||
|
||||
TXT = """add_library(op25-message SHARED message.cc msg_queue.cc msg_handler.cc)
|
||||
install(TARGETS op25-message EXPORT op25-message-export DESTINATION lib)
|
||||
install(EXPORT op25-message-export DESTINATION ${GR_CMAKE_DIR})
|
||||
target_include_directories(op25-message
|
||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
|
||||
PUBLIC $<INSTALL_INTERFACE:include>
|
||||
)
|
||||
"""
|
||||
|
||||
def edit_cmake(filename, mod, srcfiles):
|
||||
lines = open(filename).read().rstrip().split('\n')
|
||||
srcdefs = []
|
||||
state = 0
|
||||
end_mark = 0
|
||||
add_library = 0
|
||||
tll = 0 # target_link_library
|
||||
srcfiles = [s.split('/')[-1] for s in srcfiles if s.endswith('.cc') or s.endswith('.c') or s.endswith('.cpp')]
|
||||
lines = [l for l in lines if l.strip() not in SKIP_CC]
|
||||
for i in range(len(lines)):
|
||||
if 'add_library' in lines[i] and 'gnuradio-op25' in lines[i]:
|
||||
add_library = i
|
||||
if lines[i].startswith('list(APPEND op25_') and ('_sources' in lines[i] or '_python_files' in lines[i]):
|
||||
state = 1
|
||||
continue
|
||||
elif ')' in lines[i] and state:
|
||||
state = 0
|
||||
end_mark = i
|
||||
continue
|
||||
elif lines[i].startswith('target_link_libraries(gnuradio-op25'):
|
||||
assert lines[i].endswith(')')
|
||||
tll = i
|
||||
continue
|
||||
if state:
|
||||
srcdefs.append(lines[i].strip())
|
||||
srcfiles = [" %s" % s for s in srcfiles if s not in srcdefs and s not in SKIP_CC]
|
||||
tlls = {
|
||||
'op25': 'target_link_libraries(gnuradio-op25 gnuradio::gnuradio-runtime Boost::system Boost::program_options Boost::filesystem Boost::thread itpp pcap op25-message)',
|
||||
'op25_repeater': 'target_link_libraries(gnuradio-op25_repeater PUBLIC gnuradio::gnuradio-runtime gnuradio::gnuradio-filter op25-message PRIVATE imbe_vocoder)'
|
||||
}
|
||||
assert tll # fail if target_link_libraries line not found
|
||||
lines[tll] = tlls[mod]
|
||||
if mod == 'op25_repeater':
|
||||
lines = lines[:tll] + ['\n' + 'add_subdirectory(imbe_vocoder)\n'] + lines[tll:]
|
||||
elif mod == 'op25':
|
||||
assert add_library > 0
|
||||
lines = lines[:add_library] + [s for s in TXT.split('\n')] + lines[add_library:]
|
||||
|
||||
new_lines = lines[:end_mark] + srcfiles + lines[end_mark:]
|
||||
s = '\n'.join(new_lines)
|
||||
s += '\n'
|
||||
with open(filename, 'w') as fp:
|
||||
fp.write(s)
|
||||
|
||||
def get_args_from_h(mod):
|
||||
lines = open(mod).read().rstrip().split('\n')
|
||||
|
||||
lines = [line for line in lines if 'make' in line]
|
||||
|
||||
answer = []
|
||||
for s in lines:
|
||||
s = s.rstrip()
|
||||
if s[-1] != ';':
|
||||
continue
|
||||
s = s[:-1]
|
||||
s = s.rstrip()
|
||||
if s[-1] != ')':
|
||||
continue
|
||||
s = s[:-1]
|
||||
lp = s.find('(')
|
||||
if lp > 0:
|
||||
s =s[lp+1:]
|
||||
else:
|
||||
continue
|
||||
for arg in s.split(','):
|
||||
eq = arg.find('=')
|
||||
if eq > 0:
|
||||
arg = arg[:eq]
|
||||
answer.append(arg)
|
||||
return ','.join(answer)
|
||||
return ''
|
||||
|
||||
for mod in sorted(MODS.keys()):
|
||||
m = ModToolNewModule(module_name=mod, srcdir=None)
|
||||
m.run()
|
||||
print('gr_modtool newmod %s getcwd now %s' % (mod, os.getcwd()))
|
||||
pfx = '%s/op25/gr-%s' % (SRC_DIR, mod)
|
||||
lib = '%s/lib' % pfx
|
||||
s_py = '%s/python/op25/bindings' % pfx
|
||||
incl = '%s/include/%s' % (pfx, mod)
|
||||
d_pfx = '%s/op25/gr-%s' % (DEST_DIR, mod)
|
||||
d_lib = '%s/lib' % d_pfx
|
||||
d_py = '%s/python/op25/bindings' % d_pfx
|
||||
d_incl_alt1 = '%s/include/%s' % (d_pfx, mod)
|
||||
d_incl_alt2 = '%s/include/gnuradio/%s' % (d_pfx, mod)
|
||||
if os.path.isdir(d_incl_alt1):
|
||||
d_incl = d_incl_alt1
|
||||
elif os.path.isdir(d_incl_alt2):
|
||||
d_incl = d_incl_alt2
|
||||
sl = 'gnuradio/%s' % mod
|
||||
os.symlink(sl, '%s/include/%s' % (d_pfx, mod))
|
||||
if mod == 'op25_repeater':
|
||||
p_pfx = '%s/op25/gr-%s' % (DEST_DIR, 'op25')
|
||||
p_incl = '%s/include/%s' % (p_pfx, 'op25')
|
||||
d = '/'.join(d_incl.split('/')[:-1])
|
||||
os.symlink(p_incl, '%s/include/%s' % (d_pfx, 'op25'))
|
||||
|
||||
else:
|
||||
sys.stderr.write('neither %s nor %s found, aborting\n' % (d_incl_alt1, d_incl_alt2))
|
||||
sys.exit(1)
|
||||
|
||||
for block in MODS[mod]:
|
||||
include = '%s/%s.h' % (incl, block)
|
||||
args = get_args_from_h(include)
|
||||
t = 'sync' if block == 'fsk4_slicer_fb' or block == 'pcap_source_b' else 'general'
|
||||
if block == 'message' or block == 'msg_queue' or block == 'msg_handler':
|
||||
t = 'noblock'
|
||||
print ('add %s %s type %s directory %s args %s' % (mod, block, t, os.getcwd(), args))
|
||||
m = ModToolAdd(blockname=block,block_type=t,lang='cpp',copyright='Steve Glass, OP25 Group', argument_list=args)
|
||||
m.run()
|
||||
|
||||
srcfiles = []
|
||||
srcfiles += glob.glob('%s/lib/*.cc' % pfx)
|
||||
srcfiles += glob.glob('%s/lib/*.cpp' % pfx)
|
||||
srcfiles += glob.glob('%s/lib/*.c' % pfx)
|
||||
srcfiles += glob.glob('%s/lib/*.h' % pfx)
|
||||
hfiles = glob.glob('%s/*.h' % incl)
|
||||
|
||||
assert os.path.isdir(d_lib)
|
||||
assert os.path.isdir(d_incl)
|
||||
|
||||
for f in srcfiles:
|
||||
shutil.copy(f, d_lib)
|
||||
|
||||
for f in hfiles:
|
||||
shutil.copy(f, d_incl)
|
||||
|
||||
os.system('/bin/bash %s/%s %s' % (SCRIPTS, 'do_sedm.sh', d_incl))
|
||||
|
||||
if mod == 'op25_repeater':
|
||||
for d in 'imbe_vocoder ezpwd'.split():
|
||||
os.mkdir('%s/%s' % (d_lib, d))
|
||||
imbefiles = []
|
||||
imbefiles += glob.glob('%s/%s/*' % (lib, d))
|
||||
dest = '%s/%s' % (d_lib, d)
|
||||
for f in imbefiles:
|
||||
shutil.copy(f, dest)
|
||||
|
||||
edit_cmake('%s/CMakeLists.txt' % d_lib, mod, srcfiles)
|
||||
|
||||
os.system('/bin/bash %s/do_sed.sh' % (SCRIPTS))
|
||||
f = '%s/CMakeLists.txt' % (d_pfx)
|
||||
if mod == 'op25':
|
||||
exe = '%s/do_sedb.sh %s' % (SCRIPTS, f)
|
||||
elif mod == 'op25_repeater':
|
||||
exe = '%s/do_sedc.sh %s' % (SCRIPTS, f)
|
||||
os.system('/bin/bash %s %s' % (exe, f))
|
||||
os.system('/bin/bash %s/do_sedp.sh %s' % (SCRIPTS, f))
|
||||
os.system('/bin/bash %s/do_sedp2.sh %s' % (SCRIPTS, d_pfx))
|
||||
|
||||
for block in MODS[mod]:
|
||||
print ('bind %s %s' % (mod, block))
|
||||
m = ModToolGenBindings(block, addl_includes='', define_symbols='', update_hash_only=False)
|
||||
m.run()
|
||||
|
||||
if mod == 'op25':
|
||||
py_cc_srcfiles = 'message_python.cc msg_handler_python.cc msg_queue_python.cc'.split()
|
||||
for f in py_cc_srcfiles:
|
||||
shutil.copy('%s/%s' % (s_py, f), d_py)
|
||||
|
||||
os.chdir(op25_dir)
|
|
@ -1,210 +0,0 @@
|
|||
# Copyright 2010-2011 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
# GNU Radio 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
if(DEFINED __INCLUDED_GR_MISC_UTILS_CMAKE)
|
||||
return()
|
||||
endif()
|
||||
set(__INCLUDED_GR_MISC_UTILS_CMAKE TRUE)
|
||||
|
||||
########################################################################
|
||||
# Set global variable macro.
|
||||
# Used for subdirectories to export settings.
|
||||
# Example: include and library paths.
|
||||
########################################################################
|
||||
function(GR_SET_GLOBAL var)
|
||||
set(${var} ${ARGN} CACHE INTERNAL "" FORCE)
|
||||
endfunction(GR_SET_GLOBAL)
|
||||
|
||||
########################################################################
|
||||
# Set the pre-processor definition if the condition is true.
|
||||
# - def the pre-processor definition to set and condition name
|
||||
########################################################################
|
||||
function(GR_ADD_COND_DEF def)
|
||||
if(${def})
|
||||
add_definitions(-D${def})
|
||||
endif(${def})
|
||||
endfunction(GR_ADD_COND_DEF)
|
||||
|
||||
########################################################################
|
||||
# Check for a header and conditionally set a compile define.
|
||||
# - hdr the relative path to the header file
|
||||
# - def the pre-processor definition to set
|
||||
########################################################################
|
||||
function(GR_CHECK_HDR_N_DEF hdr def)
|
||||
include(CheckIncludeFileCXX)
|
||||
CHECK_INCLUDE_FILE_CXX(${hdr} ${def})
|
||||
GR_ADD_COND_DEF(${def})
|
||||
endfunction(GR_CHECK_HDR_N_DEF)
|
||||
|
||||
########################################################################
|
||||
# Include subdirectory macro.
|
||||
# Sets the CMake directory variables,
|
||||
# includes the subdirectory CMakeLists.txt,
|
||||
# resets the CMake directory variables.
|
||||
#
|
||||
# This macro includes subdirectories rather than adding them
|
||||
# so that the subdirectory can affect variables in the level above.
|
||||
# This provides a work-around for the lack of convenience libraries.
|
||||
# This way a subdirectory can append to the list of library sources.
|
||||
########################################################################
|
||||
macro(GR_INCLUDE_SUBDIRECTORY subdir)
|
||||
#insert the current directories on the front of the list
|
||||
list(INSERT _cmake_source_dirs 0 ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
list(INSERT _cmake_binary_dirs 0 ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
#set the current directories to the names of the subdirs
|
||||
set(CMAKE_CURRENT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${subdir})
|
||||
set(CMAKE_CURRENT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${subdir})
|
||||
|
||||
#include the subdirectory CMakeLists to run it
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt)
|
||||
|
||||
#reset the value of the current directories
|
||||
list(GET _cmake_source_dirs 0 CMAKE_CURRENT_SOURCE_DIR)
|
||||
list(GET _cmake_binary_dirs 0 CMAKE_CURRENT_BINARY_DIR)
|
||||
|
||||
#pop the subdir names of the front of the list
|
||||
list(REMOVE_AT _cmake_source_dirs 0)
|
||||
list(REMOVE_AT _cmake_binary_dirs 0)
|
||||
endmacro(GR_INCLUDE_SUBDIRECTORY)
|
||||
|
||||
########################################################################
|
||||
# Check if a compiler flag works and conditionally set a compile define.
|
||||
# - flag the compiler flag to check for
|
||||
# - have the variable to set with result
|
||||
########################################################################
|
||||
macro(GR_ADD_CXX_COMPILER_FLAG_IF_AVAILABLE flag have)
|
||||
include(CheckCXXCompilerFlag)
|
||||
CHECK_CXX_COMPILER_FLAG(${flag} ${have})
|
||||
if(${have})
|
||||
add_definitions(${flag})
|
||||
endif(${have})
|
||||
endmacro(GR_ADD_CXX_COMPILER_FLAG_IF_AVAILABLE)
|
||||
|
||||
########################################################################
|
||||
# Generates the .la libtool file
|
||||
# This appears to generate libtool files that cannot be used by auto*.
|
||||
# Usage GR_LIBTOOL(TARGET [target] DESTINATION [dest])
|
||||
# Notice: there is not COMPONENT option, these will not get distributed.
|
||||
########################################################################
|
||||
function(GR_LIBTOOL)
|
||||
if(NOT DEFINED GENERATE_LIBTOOL)
|
||||
set(GENERATE_LIBTOOL OFF) #disabled by default
|
||||
endif()
|
||||
|
||||
if(GENERATE_LIBTOOL)
|
||||
include(CMakeParseArgumentsCopy)
|
||||
CMAKE_PARSE_ARGUMENTS(GR_LIBTOOL "" "TARGET;DESTINATION" "" ${ARGN})
|
||||
|
||||
find_program(LIBTOOL libtool)
|
||||
if(LIBTOOL)
|
||||
include(CMakeMacroLibtoolFile)
|
||||
CREATE_LIBTOOL_FILE(${GR_LIBTOOL_TARGET} /${GR_LIBTOOL_DESTINATION})
|
||||
endif(LIBTOOL)
|
||||
endif(GENERATE_LIBTOOL)
|
||||
|
||||
endfunction(GR_LIBTOOL)
|
||||
|
||||
########################################################################
|
||||
# Do standard things to the library target
|
||||
# - set target properties
|
||||
# - make install rules
|
||||
# Also handle gnuradio custom naming conventions w/ extras mode.
|
||||
########################################################################
|
||||
function(GR_LIBRARY_FOO target)
|
||||
#parse the arguments for component names
|
||||
include(CMakeParseArgumentsCopy)
|
||||
CMAKE_PARSE_ARGUMENTS(GR_LIBRARY "" "RUNTIME_COMPONENT;DEVEL_COMPONENT" "" ${ARGN})
|
||||
|
||||
#set additional target properties
|
||||
set_target_properties(${target} PROPERTIES SOVERSION ${LIBVER})
|
||||
|
||||
#install the generated files like so...
|
||||
install(TARGETS ${target}
|
||||
LIBRARY DESTINATION ${GR_LIBRARY_DIR} COMPONENT ${GR_LIBRARY_RUNTIME_COMPONENT} # .so/.dylib file
|
||||
ARCHIVE DESTINATION ${GR_LIBRARY_DIR} COMPONENT ${GR_LIBRARY_DEVEL_COMPONENT} # .lib file
|
||||
RUNTIME DESTINATION ${GR_RUNTIME_DIR} COMPONENT ${GR_LIBRARY_RUNTIME_COMPONENT} # .dll file
|
||||
)
|
||||
|
||||
#extras mode enabled automatically on linux
|
||||
if(NOT DEFINED LIBRARY_EXTRAS)
|
||||
set(LIBRARY_EXTRAS ${LINUX})
|
||||
endif()
|
||||
|
||||
#special extras mode to enable alternative naming conventions
|
||||
if(LIBRARY_EXTRAS)
|
||||
|
||||
#create .la file before changing props
|
||||
GR_LIBTOOL(TARGET ${target} DESTINATION ${GR_LIBRARY_DIR})
|
||||
|
||||
#give the library a special name with ultra-zero soversion
|
||||
set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_NAME ${target}-${LIBVER} SOVERSION "0.0.0")
|
||||
set(target_name lib${target}-${LIBVER}.so.0.0.0)
|
||||
|
||||
#custom command to generate symlinks
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${target_name} ${CMAKE_CURRENT_BINARY_DIR}/lib${target}.so
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${target_name} ${CMAKE_CURRENT_BINARY_DIR}/lib${target}-${LIBVER}.so.0
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${target_name} #so the symlinks point to something valid so cmake 2.6 will install
|
||||
)
|
||||
|
||||
#and install the extra symlinks
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/lib${target}.so
|
||||
${CMAKE_CURRENT_BINARY_DIR}/lib${target}-${LIBVER}.so.0
|
||||
DESTINATION ${GR_LIBRARY_DIR} COMPONENT ${GR_LIBRARY_RUNTIME_COMPONENT}
|
||||
)
|
||||
|
||||
endif(LIBRARY_EXTRAS)
|
||||
endfunction(GR_LIBRARY_FOO)
|
||||
|
||||
########################################################################
|
||||
# Create a dummy custom command that depends on other targets.
|
||||
# Usage:
|
||||
# GR_GEN_TARGET_DEPS(unique_name target_deps <target1> <target2> ...)
|
||||
# ADD_CUSTOM_COMMAND(<the usual args> ${target_deps})
|
||||
#
|
||||
# Custom command cant depend on targets, but can depend on executables,
|
||||
# and executables can depend on targets. So this is the process:
|
||||
########################################################################
|
||||
function(GR_GEN_TARGET_DEPS name var)
|
||||
file(
|
||||
WRITE ${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp.in
|
||||
"int main(void){return 0;}\n"
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp
|
||||
)
|
||||
add_executable(${name} ${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp)
|
||||
if(ARGN)
|
||||
add_dependencies(${name} ${ARGN})
|
||||
endif(ARGN)
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
set(${var} "DEPENDS;${name}" PARENT_SCOPE) #cant call command when cross
|
||||
else()
|
||||
set(${var} "DEPENDS;${name};COMMAND;${name}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction(GR_GEN_TARGET_DEPS)
|
|
@ -1,46 +0,0 @@
|
|||
# Copyright 2011 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
# GNU Radio 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
if(DEFINED __INCLUDED_GR_PLATFORM_CMAKE)
|
||||
return()
|
||||
endif()
|
||||
set(__INCLUDED_GR_PLATFORM_CMAKE TRUE)
|
||||
|
||||
########################################################################
|
||||
# Setup additional defines for OS types
|
||||
########################################################################
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if(LINUX AND EXISTS "/etc/debian_version")
|
||||
set(DEBIAN TRUE)
|
||||
endif()
|
||||
|
||||
if(LINUX AND EXISTS "/etc/redhat-release")
|
||||
set(REDHAT TRUE)
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# when the library suffix should be 64 (applies to redhat linux family)
|
||||
########################################################################
|
||||
if(NOT DEFINED LIB_SUFFIX AND REDHAT AND CMAKE_SYSTEM_PROCESSOR MATCHES "64$")
|
||||
set(LIB_SUFFIX 64)
|
||||
endif()
|
||||
set(LIB_SUFFIX ${LIB_SUFFIX} CACHE STRING "lib directory suffix")
|
|
@ -1,227 +0,0 @@
|
|||
# Copyright 2010-2011 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
# GNU Radio 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
if(DEFINED __INCLUDED_GR_PYTHON_CMAKE)
|
||||
return()
|
||||
endif()
|
||||
set(__INCLUDED_GR_PYTHON_CMAKE TRUE)
|
||||
|
||||
########################################################################
|
||||
# Setup the python interpreter:
|
||||
# This allows the user to specify a specific interpreter,
|
||||
# or finds the interpreter via the built-in cmake module.
|
||||
########################################################################
|
||||
#this allows the user to override PYTHON_EXECUTABLE
|
||||
if(PYTHON_EXECUTABLE)
|
||||
|
||||
set(PYTHONINTERP_FOUND TRUE)
|
||||
|
||||
#otherwise if not set, try to automatically find it
|
||||
else(PYTHON_EXECUTABLE)
|
||||
|
||||
#use the built-in find script
|
||||
find_package(PythonInterp 2)
|
||||
|
||||
#and if that fails use the find program routine
|
||||
if(NOT PYTHONINTERP_FOUND)
|
||||
find_program(PYTHON_EXECUTABLE NAMES python python2 python2.7 python2.6 python2.5)
|
||||
if(PYTHON_EXECUTABLE)
|
||||
set(PYTHONINTERP_FOUND TRUE)
|
||||
endif(PYTHON_EXECUTABLE)
|
||||
endif(NOT PYTHONINTERP_FOUND)
|
||||
|
||||
endif(PYTHON_EXECUTABLE)
|
||||
|
||||
#make the path to the executable appear in the cmake gui
|
||||
set(PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "python interpreter")
|
||||
|
||||
#make sure we can use -B with python (introduced in 2.6)
|
||||
if(PYTHON_EXECUTABLE)
|
||||
execute_process(
|
||||
COMMAND ${PYTHON_EXECUTABLE} -B -c ""
|
||||
OUTPUT_QUIET ERROR_QUIET
|
||||
RESULT_VARIABLE PYTHON_HAS_DASH_B_RESULT
|
||||
)
|
||||
if(PYTHON_HAS_DASH_B_RESULT EQUAL 0)
|
||||
set(PYTHON_DASH_B "-B")
|
||||
endif()
|
||||
endif(PYTHON_EXECUTABLE)
|
||||
|
||||
########################################################################
|
||||
# Check for the existence of a python module:
|
||||
# - desc a string description of the check
|
||||
# - mod the name of the module to import
|
||||
# - cmd an additional command to run
|
||||
# - have the result variable to set
|
||||
########################################################################
|
||||
macro(GR_PYTHON_CHECK_MODULE desc mod cmd have)
|
||||
message(STATUS "")
|
||||
message(STATUS "Python checking for ${desc}")
|
||||
execute_process(
|
||||
COMMAND ${PYTHON_EXECUTABLE} -c "
|
||||
#########################################
|
||||
try: import ${mod}
|
||||
except: exit(-1)
|
||||
try: assert ${cmd}
|
||||
except: exit(-1)
|
||||
#########################################"
|
||||
RESULT_VARIABLE ${have}
|
||||
)
|
||||
if(${have} EQUAL 0)
|
||||
message(STATUS "Python checking for ${desc} - found")
|
||||
set(${have} TRUE)
|
||||
else(${have} EQUAL 0)
|
||||
message(STATUS "Python checking for ${desc} - not found")
|
||||
set(${have} FALSE)
|
||||
endif(${have} EQUAL 0)
|
||||
endmacro(GR_PYTHON_CHECK_MODULE)
|
||||
|
||||
########################################################################
|
||||
# Sets the python installation directory GR_PYTHON_DIR
|
||||
########################################################################
|
||||
execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "
|
||||
from distutils import sysconfig
|
||||
print sysconfig.get_python_lib(plat_specific=True, prefix='')
|
||||
" OUTPUT_VARIABLE GR_PYTHON_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
file(TO_CMAKE_PATH ${GR_PYTHON_DIR} GR_PYTHON_DIR)
|
||||
|
||||
########################################################################
|
||||
# Create an always-built target with a unique name
|
||||
# Usage: GR_UNIQUE_TARGET(<description> <dependencies list>)
|
||||
########################################################################
|
||||
function(GR_UNIQUE_TARGET desc)
|
||||
file(RELATIVE_PATH reldir ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import re, hashlib
|
||||
unique = hashlib.md5('${reldir}${ARGN}').hexdigest()[:5]
|
||||
print(re.sub('\\W', '_', '${desc} ${reldir} ' + unique))"
|
||||
OUTPUT_VARIABLE _target OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
add_custom_target(${_target} ALL DEPENDS ${ARGN})
|
||||
endfunction(GR_UNIQUE_TARGET)
|
||||
|
||||
########################################################################
|
||||
# Install python sources (also builds and installs byte-compiled python)
|
||||
########################################################################
|
||||
function(GR_PYTHON_INSTALL)
|
||||
include(CMakeParseArgumentsCopy)
|
||||
CMAKE_PARSE_ARGUMENTS(GR_PYTHON_INSTALL "" "DESTINATION;COMPONENT" "FILES;PROGRAMS" ${ARGN})
|
||||
|
||||
####################################################################
|
||||
if(GR_PYTHON_INSTALL_FILES)
|
||||
####################################################################
|
||||
install(${ARGN}) #installs regular python files
|
||||
|
||||
#create a list of all generated files
|
||||
unset(pysrcfiles)
|
||||
unset(pycfiles)
|
||||
unset(pyofiles)
|
||||
foreach(pyfile ${GR_PYTHON_INSTALL_FILES})
|
||||
get_filename_component(pyfile ${pyfile} ABSOLUTE)
|
||||
list(APPEND pysrcfiles ${pyfile})
|
||||
|
||||
#determine if this file is in the source or binary directory
|
||||
file(RELATIVE_PATH source_rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${pyfile})
|
||||
string(LENGTH "${source_rel_path}" source_rel_path_len)
|
||||
file(RELATIVE_PATH binary_rel_path ${CMAKE_CURRENT_BINARY_DIR} ${pyfile})
|
||||
string(LENGTH "${binary_rel_path}" binary_rel_path_len)
|
||||
|
||||
#and set the generated path appropriately
|
||||
if(${source_rel_path_len} GREATER ${binary_rel_path_len})
|
||||
set(pygenfile ${CMAKE_CURRENT_BINARY_DIR}/${binary_rel_path})
|
||||
else()
|
||||
set(pygenfile ${CMAKE_CURRENT_BINARY_DIR}/${source_rel_path})
|
||||
endif()
|
||||
list(APPEND pycfiles ${pygenfile}c)
|
||||
list(APPEND pyofiles ${pygenfile}o)
|
||||
|
||||
#ensure generation path exists
|
||||
get_filename_component(pygen_path ${pygenfile} PATH)
|
||||
file(MAKE_DIRECTORY ${pygen_path})
|
||||
|
||||
endforeach(pyfile)
|
||||
|
||||
#the command to generate the pyc files
|
||||
add_custom_command(
|
||||
DEPENDS ${pysrcfiles} OUTPUT ${pycfiles}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_BINARY_DIR}/python_compile_helper.py ${pysrcfiles} ${pycfiles}
|
||||
)
|
||||
|
||||
#the command to generate the pyo files
|
||||
add_custom_command(
|
||||
DEPENDS ${pysrcfiles} OUTPUT ${pyofiles}
|
||||
COMMAND ${PYTHON_EXECUTABLE} -O ${CMAKE_BINARY_DIR}/python_compile_helper.py ${pysrcfiles} ${pyofiles}
|
||||
)
|
||||
|
||||
#create install rule and add generated files to target list
|
||||
set(python_install_gen_targets ${pycfiles} ${pyofiles})
|
||||
install(FILES ${python_install_gen_targets}
|
||||
DESTINATION ${GR_PYTHON_INSTALL_DESTINATION}
|
||||
COMPONENT ${GR_PYTHON_INSTALL_COMPONENT}
|
||||
)
|
||||
|
||||
|
||||
####################################################################
|
||||
elseif(GR_PYTHON_INSTALL_PROGRAMS)
|
||||
####################################################################
|
||||
file(TO_NATIVE_PATH ${PYTHON_EXECUTABLE} pyexe_native)
|
||||
|
||||
foreach(pyfile ${GR_PYTHON_INSTALL_PROGRAMS})
|
||||
get_filename_component(pyfile_name ${pyfile} NAME)
|
||||
get_filename_component(pyfile ${pyfile} ABSOLUTE)
|
||||
string(REPLACE "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" pyexefile "${pyfile}.exe")
|
||||
list(APPEND python_install_gen_targets ${pyexefile})
|
||||
|
||||
get_filename_component(pyexefile_path ${pyexefile} PATH)
|
||||
file(MAKE_DIRECTORY ${pyexefile_path})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${pyexefile} DEPENDS ${pyfile}
|
||||
COMMAND ${PYTHON_EXECUTABLE} -c
|
||||
\"open('${pyexefile}', 'w').write('\#!${pyexe_native}\\n'+open('${pyfile}').read())\"
|
||||
COMMENT "Shebangin ${pyfile_name}"
|
||||
)
|
||||
|
||||
#on windows, python files need an extension to execute
|
||||
get_filename_component(pyfile_ext ${pyfile} EXT)
|
||||
if(WIN32 AND NOT pyfile_ext)
|
||||
set(pyfile_name "${pyfile_name}.py")
|
||||
endif()
|
||||
|
||||
install(PROGRAMS ${pyexefile} RENAME ${pyfile_name}
|
||||
DESTINATION ${GR_PYTHON_INSTALL_DESTINATION}
|
||||
COMPONENT ${GR_PYTHON_INSTALL_COMPONENT}
|
||||
)
|
||||
endforeach(pyfile)
|
||||
|
||||
endif()
|
||||
|
||||
GR_UNIQUE_TARGET("pygen" ${python_install_gen_targets})
|
||||
|
||||
endfunction(GR_PYTHON_INSTALL)
|
||||
|
||||
########################################################################
|
||||
# Write the python helper script that generates byte code files
|
||||
########################################################################
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/python_compile_helper.py "
|
||||
import sys, py_compile
|
||||
files = sys.argv[1:]
|
||||
srcs, gens = files[:len(files)/2], files[len(files)/2:]
|
||||
for src, gen in zip(srcs, gens):
|
||||
py_compile.compile(file=src, cfile=gen, doraise=True)
|
||||
")
|
|
@ -1,229 +0,0 @@
|
|||
# Copyright 2010-2011 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
# GNU Radio 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
if(DEFINED __INCLUDED_GR_SWIG_CMAKE)
|
||||
return()
|
||||
endif()
|
||||
set(__INCLUDED_GR_SWIG_CMAKE TRUE)
|
||||
|
||||
include(GrPython)
|
||||
|
||||
########################################################################
|
||||
# Builds a swig documentation file to be generated into python docstrings
|
||||
# Usage: GR_SWIG_MAKE_DOCS(output_file input_path input_path....)
|
||||
#
|
||||
# Set the following variable to specify extra dependent targets:
|
||||
# - GR_SWIG_DOCS_SOURCE_DEPS
|
||||
# - GR_SWIG_DOCS_TARGET_DEPS
|
||||
########################################################################
|
||||
function(GR_SWIG_MAKE_DOCS output_file)
|
||||
find_package(Doxygen)
|
||||
if(DOXYGEN_FOUND)
|
||||
|
||||
#setup the input files variable list, quote formated
|
||||
set(input_files)
|
||||
unset(INPUT_PATHS)
|
||||
foreach(input_path ${ARGN})
|
||||
if (IS_DIRECTORY ${input_path}) #when input path is a directory
|
||||
file(GLOB input_path_h_files ${input_path}/*.h)
|
||||
else() #otherwise its just a file, no glob
|
||||
set(input_path_h_files ${input_path})
|
||||
endif()
|
||||
list(APPEND input_files ${input_path_h_files})
|
||||
set(INPUT_PATHS "${INPUT_PATHS} \"${input_path}\"")
|
||||
endforeach(input_path)
|
||||
|
||||
#determine the output directory
|
||||
get_filename_component(name ${output_file} NAME_WE)
|
||||
get_filename_component(OUTPUT_DIRECTORY ${output_file} PATH)
|
||||
set(OUTPUT_DIRECTORY ${OUTPUT_DIRECTORY}/${name}_swig_docs)
|
||||
make_directory(${OUTPUT_DIRECTORY})
|
||||
|
||||
#generate the Doxyfile used by doxygen
|
||||
configure_file(
|
||||
${CMAKE_SOURCE_DIR}/docs/doxygen/Doxyfile.swig_doc.in
|
||||
${OUTPUT_DIRECTORY}/Doxyfile
|
||||
@ONLY)
|
||||
|
||||
#Create a dummy custom command that depends on other targets
|
||||
include(GrMiscUtils)
|
||||
GR_GEN_TARGET_DEPS(_${name}_tag tag_deps ${GR_SWIG_DOCS_TARGET_DEPS})
|
||||
|
||||
#call doxygen on the Doxyfile + input headers
|
||||
add_custom_command(
|
||||
OUTPUT ${OUTPUT_DIRECTORY}/xml/index.xml
|
||||
DEPENDS ${input_files} ${GR_SWIG_DOCS_SOURCE_DEPS} ${tag_deps}
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${OUTPUT_DIRECTORY}/Doxyfile
|
||||
COMMENT "Generating doxygen xml for ${name} docs"
|
||||
)
|
||||
|
||||
#call the swig_doc script on the xml files
|
||||
add_custom_command(
|
||||
OUTPUT ${output_file}
|
||||
DEPENDS ${input_files} ${OUTPUT_DIRECTORY}/xml/index.xml
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PYTHON_DASH_B}
|
||||
${CMAKE_SOURCE_DIR}/docs/doxygen/swig_doc.py
|
||||
${OUTPUT_DIRECTORY}/xml
|
||||
${output_file}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/docs/doxygen
|
||||
)
|
||||
|
||||
else(DOXYGEN_FOUND)
|
||||
file(WRITE ${output_file} "\n") #no doxygen -> empty file
|
||||
endif(DOXYGEN_FOUND)
|
||||
endfunction(GR_SWIG_MAKE_DOCS)
|
||||
|
||||
########################################################################
|
||||
# Build a swig target for the common gnuradio use case. Usage:
|
||||
# GR_SWIG_MAKE(target ifile ifile ifile...)
|
||||
#
|
||||
# Set the following variables before calling:
|
||||
# - GR_SWIG_FLAGS
|
||||
# - GR_SWIG_INCLUDE_DIRS
|
||||
# - GR_SWIG_LIBRARIES
|
||||
# - GR_SWIG_SOURCE_DEPS
|
||||
# - GR_SWIG_TARGET_DEPS
|
||||
# - GR_SWIG_DOC_FILE
|
||||
# - GR_SWIG_DOC_DIRS
|
||||
########################################################################
|
||||
macro(GR_SWIG_MAKE name)
|
||||
set(ifiles ${ARGN})
|
||||
|
||||
#do swig doc generation if specified
|
||||
if (GR_SWIG_DOC_FILE)
|
||||
set(GR_SWIG_DOCS_SOURCE_DEPS ${GR_SWIG_SOURCE_DEPS})
|
||||
set(GR_SWIG_DOCS_TAREGT_DEPS ${GR_SWIG_TARGET_DEPS})
|
||||
GR_SWIG_MAKE_DOCS(${GR_SWIG_DOC_FILE} ${GR_SWIG_DOC_DIRS})
|
||||
list(APPEND GR_SWIG_SOURCE_DEPS ${GR_SWIG_DOC_FILE})
|
||||
endif()
|
||||
|
||||
#append additional include directories
|
||||
find_package(PythonLibs 2)
|
||||
list(APPEND GR_SWIG_INCLUDE_DIRS ${PYTHON_INCLUDE_PATH}) #deprecated name (now dirs)
|
||||
list(APPEND GR_SWIG_INCLUDE_DIRS ${PYTHON_INCLUDE_DIRS})
|
||||
list(APPEND GR_SWIG_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
list(APPEND GR_SWIG_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
#determine include dependencies for swig file
|
||||
execute_process(
|
||||
COMMAND ${PYTHON_EXECUTABLE}
|
||||
${CMAKE_BINARY_DIR}/get_swig_deps.py
|
||||
"${ifiles}" "${GR_SWIG_INCLUDE_DIRS}"
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
OUTPUT_VARIABLE SWIG_MODULE_${name}_EXTRA_DEPS
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
#Create a dummy custom command that depends on other targets
|
||||
include(GrMiscUtils)
|
||||
GR_GEN_TARGET_DEPS(_${name}_swig_tag tag_deps ${GR_SWIG_TARGET_DEPS})
|
||||
set(tag_file ${CMAKE_CURRENT_BINARY_DIR}/${name}.tag)
|
||||
add_custom_command(
|
||||
OUTPUT ${tag_file}
|
||||
DEPENDS ${GR_SWIG_SOURCE_DEPS} ${tag_deps}
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${tag_file}
|
||||
)
|
||||
|
||||
#append the specified include directories
|
||||
include_directories(${GR_SWIG_INCLUDE_DIRS})
|
||||
list(APPEND SWIG_MODULE_${name}_EXTRA_DEPS ${tag_file})
|
||||
|
||||
#setup the swig flags with flags and include directories
|
||||
set(CMAKE_SWIG_FLAGS -fvirtual -modern -keyword -w511 -module ${name} ${GR_SWIG_FLAGS})
|
||||
foreach(dir ${GR_SWIG_INCLUDE_DIRS})
|
||||
list(APPEND CMAKE_SWIG_FLAGS "-I${dir}")
|
||||
endforeach(dir)
|
||||
|
||||
#set the C++ property on the swig .i file so it builds
|
||||
set_source_files_properties(${ifiles} PROPERTIES CPLUSPLUS ON)
|
||||
|
||||
#setup the actual swig library target to be built
|
||||
include(UseSWIG)
|
||||
SWIG_ADD_MODULE(${name} python ${ifiles})
|
||||
SWIG_LINK_LIBRARIES(${name} ${PYTHON_LIBRARIES} ${GR_SWIG_LIBRARIES})
|
||||
|
||||
endmacro(GR_SWIG_MAKE)
|
||||
|
||||
########################################################################
|
||||
# Install swig targets generated by GR_SWIG_MAKE. Usage:
|
||||
# GR_SWIG_INSTALL(
|
||||
# TARGETS target target target...
|
||||
# [DESTINATION destination]
|
||||
# [COMPONENT component]
|
||||
# )
|
||||
########################################################################
|
||||
macro(GR_SWIG_INSTALL)
|
||||
|
||||
include(CMakeParseArgumentsCopy)
|
||||
CMAKE_PARSE_ARGUMENTS(GR_SWIG_INSTALL "" "DESTINATION;COMPONENT" "TARGETS" ${ARGN})
|
||||
|
||||
foreach(name ${GR_SWIG_INSTALL_TARGETS})
|
||||
install(TARGETS ${SWIG_MODULE_${name}_REAL_NAME}
|
||||
DESTINATION ${GR_SWIG_INSTALL_DESTINATION}
|
||||
COMPONENT ${GR_SWIG_INSTALL_COMPONENT}
|
||||
)
|
||||
|
||||
include(GrPython)
|
||||
GR_PYTHON_INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${name}.py
|
||||
DESTINATION ${GR_SWIG_INSTALL_DESTINATION}
|
||||
COMPONENT ${GR_SWIG_INSTALL_COMPONENT}
|
||||
)
|
||||
|
||||
GR_LIBTOOL(
|
||||
TARGET ${SWIG_MODULE_${name}_REAL_NAME}
|
||||
DESTINATION ${GR_SWIG_INSTALL_DESTINATION}
|
||||
)
|
||||
|
||||
endforeach(name)
|
||||
|
||||
endmacro(GR_SWIG_INSTALL)
|
||||
|
||||
########################################################################
|
||||
# Generate a python file that can determine swig dependencies.
|
||||
# Used by the make macro above to determine extra dependencies.
|
||||
# When you build C++, CMake figures out the header dependencies.
|
||||
# This code essentially performs that logic for swig includes.
|
||||
########################################################################
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/get_swig_deps.py "
|
||||
|
||||
import os, sys, re
|
||||
|
||||
include_matcher = re.compile('[#|%]include\\s*[<|\"](.*)[>|\"]')
|
||||
include_dirs = sys.argv[2].split(';')
|
||||
|
||||
def get_swig_incs(file_path):
|
||||
file_contents = open(file_path, 'r').read()
|
||||
return include_matcher.findall(file_contents, re.MULTILINE)
|
||||
|
||||
def get_swig_deps(file_path, level):
|
||||
deps = [file_path]
|
||||
if level == 0: return deps
|
||||
for inc_file in get_swig_incs(file_path):
|
||||
for inc_dir in include_dirs:
|
||||
inc_path = os.path.join(inc_dir, inc_file)
|
||||
if not os.path.exists(inc_path): continue
|
||||
deps.extend(get_swig_deps(inc_path, level-1))
|
||||
return deps
|
||||
|
||||
if __name__ == '__main__':
|
||||
ifiles = sys.argv[1].split(';')
|
||||
deps = sum([get_swig_deps(ifile, 3) for ifile in ifiles], [])
|
||||
#sys.stderr.write(';'.join(set(deps)) + '\\n\\n')
|
||||
print(';'.join(set(deps)))
|
||||
")
|
|
@ -1,133 +0,0 @@
|
|||
# Copyright 2010-2011 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
# GNU Radio 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
if(DEFINED __INCLUDED_GR_TEST_CMAKE)
|
||||
return()
|
||||
endif()
|
||||
set(__INCLUDED_GR_TEST_CMAKE TRUE)
|
||||
|
||||
########################################################################
|
||||
# Add a unit test and setup the environment for a unit test.
|
||||
# Takes the same arguments as the ADD_TEST function.
|
||||
#
|
||||
# Before calling set the following variables:
|
||||
# GR_TEST_TARGET_DEPS - built targets for the library path
|
||||
# GR_TEST_LIBRARY_DIRS - directories for the library path
|
||||
# GR_TEST_PYTHON_DIRS - directories for the python path
|
||||
########################################################################
|
||||
function(GR_ADD_TEST test_name)
|
||||
|
||||
if(WIN32)
|
||||
#Ensure that the build exe also appears in the PATH.
|
||||
list(APPEND GR_TEST_TARGET_DEPS ${ARGN})
|
||||
|
||||
#In the land of windows, all libraries must be in the PATH.
|
||||
#Since the dependent libraries are not yet installed,
|
||||
#we must manually set them in the PATH to run tests.
|
||||
#The following appends the path of a target dependency.
|
||||
foreach(target ${GR_TEST_TARGET_DEPS})
|
||||
get_target_property(location ${target} LOCATION)
|
||||
if(location)
|
||||
get_filename_component(path ${location} PATH)
|
||||
string(REGEX REPLACE "\\$\\(.*\\)" ${CMAKE_BUILD_TYPE} path ${path})
|
||||
list(APPEND GR_TEST_LIBRARY_DIRS ${path})
|
||||
endif(location)
|
||||
endforeach(target)
|
||||
|
||||
#SWIG generates the python library files into a subdirectory.
|
||||
#Therefore, we must append this subdirectory into PYTHONPATH.
|
||||
#Only do this for the python directories matching the following:
|
||||
foreach(pydir ${GR_TEST_PYTHON_DIRS})
|
||||
get_filename_component(name ${pydir} NAME)
|
||||
if(name MATCHES "^(swig|lib|src)$")
|
||||
list(APPEND GR_TEST_PYTHON_DIRS ${pydir}/${CMAKE_BUILD_TYPE})
|
||||
endif()
|
||||
endforeach(pydir)
|
||||
endif(WIN32)
|
||||
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR} srcdir)
|
||||
file(TO_NATIVE_PATH "${GR_TEST_LIBRARY_DIRS}" libpath) #ok to use on dir list?
|
||||
file(TO_NATIVE_PATH "${GR_TEST_PYTHON_DIRS}" pypath) #ok to use on dir list?
|
||||
|
||||
set(environs "GR_DONT_LOAD_PREFS=1" "srcdir=${srcdir}")
|
||||
|
||||
#http://www.cmake.org/pipermail/cmake/2009-May/029464.html
|
||||
#Replaced this add test + set environs code with the shell script generation.
|
||||
#Its nicer to be able to manually run the shell script to diagnose problems.
|
||||
#ADD_TEST(${ARGV})
|
||||
#SET_TESTS_PROPERTIES(${test_name} PROPERTIES ENVIRONMENT "${environs}")
|
||||
|
||||
if(UNIX)
|
||||
set(binpath "${CMAKE_CURRENT_BINARY_DIR}:$PATH")
|
||||
#set both LD and DYLD paths to cover multiple UNIX OS library paths
|
||||
list(APPEND libpath "$LD_LIBRARY_PATH" "$DYLD_LIBRARY_PATH")
|
||||
list(APPEND pypath "$PYTHONPATH")
|
||||
|
||||
#replace list separator with the path separator
|
||||
string(REPLACE ";" ":" libpath "${libpath}")
|
||||
string(REPLACE ";" ":" pypath "${pypath}")
|
||||
list(APPEND environs "PATH=${binpath}" "LD_LIBRARY_PATH=${libpath}" "DYLD_LIBRARY_PATH=${libpath}" "PYTHONPATH=${pypath}")
|
||||
|
||||
#generate a bat file that sets the environment and runs the test
|
||||
find_program(SHELL sh)
|
||||
set(sh_file ${CMAKE_CURRENT_BINARY_DIR}/${test_name}_test.sh)
|
||||
file(WRITE ${sh_file} "#!${SHELL}\n")
|
||||
#each line sets an environment variable
|
||||
foreach(environ ${environs})
|
||||
file(APPEND ${sh_file} "export ${environ}\n")
|
||||
endforeach(environ)
|
||||
#load the command to run with its arguments
|
||||
foreach(arg ${ARGN})
|
||||
file(APPEND ${sh_file} "${arg} ")
|
||||
endforeach(arg)
|
||||
file(APPEND ${sh_file} "\n")
|
||||
|
||||
#make the shell file executable
|
||||
execute_process(COMMAND chmod +x ${sh_file})
|
||||
|
||||
add_test(${test_name} ${SHELL} ${sh_file})
|
||||
|
||||
endif(UNIX)
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND libpath ${DLL_PATHS} "%PATH%")
|
||||
list(APPEND pypath "%PYTHONPATH%")
|
||||
|
||||
#replace list separator with the path separator (escaped)
|
||||
string(REPLACE ";" "\\;" libpath "${libpath}")
|
||||
string(REPLACE ";" "\\;" pypath "${pypath}")
|
||||
list(APPEND environs "PATH=${libpath}" "PYTHONPATH=${pypath}")
|
||||
|
||||
#generate a bat file that sets the environment and runs the test
|
||||
set(bat_file ${CMAKE_CURRENT_BINARY_DIR}/${test_name}_test.bat)
|
||||
file(WRITE ${bat_file} "@echo off\n")
|
||||
#each line sets an environment variable
|
||||
foreach(environ ${environs})
|
||||
file(APPEND ${bat_file} "SET ${environ}\n")
|
||||
endforeach(environ)
|
||||
#load the command to run with its arguments
|
||||
foreach(arg ${ARGN})
|
||||
file(APPEND ${bat_file} "${arg} ")
|
||||
endforeach(arg)
|
||||
file(APPEND ${bat_file} "\n")
|
||||
|
||||
add_test(${test_name} ${bat_file})
|
||||
endif(WIN32)
|
||||
|
||||
endfunction(GR_ADD_TEST)
|
|
@ -0,0 +1,367 @@
|
|||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 56f95f4..c43483a 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -4,6 +4,24 @@ project(gr-op25 CXX C)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
set(CMAKE_CXX_FLAGS "-std=c++11")
|
||||
|
||||
+execute_process(COMMAND python3 -c "
|
||||
+import os
|
||||
+import sys
|
||||
+from distutils import sysconfig
|
||||
+pfx = '/usr/local'
|
||||
+m1 = os.path.join('lib', 'python' + sys.version[:3], 'dist-packages')
|
||||
+m2 = sysconfig.get_python_lib(plat_specific=True, prefix='')
|
||||
+f1 = os.path.join(pfx, m1)
|
||||
+f2 = os.path.join(pfx, m2)
|
||||
+ok2 = f2 in sys.path
|
||||
+if ok2:
|
||||
+ print(m2)
|
||||
+else:
|
||||
+ print(m1)
|
||||
+" OUTPUT_VARIABLE OP25_PYTHON_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
+)
|
||||
+MESSAGE(STATUS "OP25_PYTHON_DIR has been set to \"${OP25_PYTHON_DIR}\".")
|
||||
+
|
||||
add_subdirectory(op25/gr-op25)
|
||||
add_subdirectory(op25/gr-op25_repeater)
|
||||
|
||||
diff --git a/install.sh b/install.sh
|
||||
index 10fbeb5..4246447 100755
|
||||
--- a/install.sh
|
||||
+++ b/install.sh
|
||||
@@ -10,9 +10,12 @@ if [ ! -d op25/gr-op25 ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
+sudo sed -i -- 's/^# *deb-src/deb-src/' /etc/apt/sources.list
|
||||
+
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep gnuradio
|
||||
-sudo apt-get install gnuradio gnuradio-dev gr-osmosdr librtlsdr-dev libuhd-dev libhackrf-dev libitpp-dev libpcap-dev cmake git swig build-essential pkg-config doxygen python-numpy python-waitress python-requests
|
||||
+sudo apt-get install gnuradio gnuradio-dev gr-osmosdr librtlsdr-dev libuhd-dev libhackrf-dev libitpp-dev libpcap-dev cmake git swig build-essential pkg-config doxygen python3-numpy python3-waitress python3-requests
|
||||
+sudo apt-get install liborc-dev
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
diff --git a/op25/gr-op25/CMakeLists.txt b/op25/gr-op25/CMakeLists.txt
|
||||
index 6c05df5..1c6fa23 100644
|
||||
--- a/op25/gr-op25/CMakeLists.txt
|
||||
+++ b/op25/gr-op25/CMakeLists.txt
|
||||
@@ -68,6 +68,9 @@ endif()
|
||||
########################################################################
|
||||
find_package(CppUnit)
|
||||
|
||||
+set(ENABLE_PYTHON "TRUE" CACHE BOOL "enable python")
|
||||
+cmake_policy(SET CMP0012 NEW)
|
||||
+
|
||||
# To run a more advanced search for GNU Radio and it's components and
|
||||
# versions, use the following. Add any components required to the list
|
||||
# of GR_REQUIRED_COMPONENTS (in all caps) and change "version" to the
|
||||
@@ -76,11 +79,12 @@ find_package(CppUnit)
|
||||
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
|
||||
# find_package(Gnuradio "version")
|
||||
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
||||
-find_package(Gnuradio)
|
||||
-
|
||||
-if(NOT GNURADIO_RUNTIME_FOUND)
|
||||
- message(FATAL_ERROR "GnuRadio Runtime required to compile op25")
|
||||
+SET(MIN_GR_VERSION "3.8.0")
|
||||
+find_package(Gnuradio REQUIRED)
|
||||
+if("${Gnuradio_VERSION}" VERSION_LESS MIN_GR_VERSION)
|
||||
+ MESSAGE(FATAL_ERROR "GnuRadio version required: >=\"" ${MIN_GR_VERSION} "\" found: \"" ${Gnuradio_VERSION} "\"")
|
||||
endif()
|
||||
+
|
||||
if(NOT CPPUNIT_FOUND)
|
||||
message(FATAL_ERROR "CppUnit required to compile op25")
|
||||
endif()
|
||||
diff --git a/op25/gr-op25/lib/CMakeLists.txt b/op25/gr-op25/lib/CMakeLists.txt
|
||||
index 1befdd9..609fa84 100644
|
||||
--- a/op25/gr-op25/lib/CMakeLists.txt
|
||||
+++ b/op25/gr-op25/lib/CMakeLists.txt
|
||||
@@ -63,7 +63,7 @@ list(APPEND op25_sources
|
||||
)
|
||||
|
||||
add_library(gnuradio-op25 SHARED ${op25_sources})
|
||||
-target_link_libraries(gnuradio-op25 ${Boost_LIBRARIES} ${GNURADIO_RUNTIME_LIBRARIES} itpp pcap)
|
||||
+target_link_libraries(gnuradio-op25 ${Boost_LIBRARIES} gnuradio::gnuradio-runtime itpp pcap)
|
||||
set_target_properties(gnuradio-op25 PROPERTIES DEFINE_SYMBOL "gnuradio_op25_EXPORTS")
|
||||
|
||||
########################################################################
|
||||
diff --git a/op25/gr-op25/python/CMakeLists.txt b/op25/gr-op25/python/CMakeLists.txt
|
||||
index 03361a2..e497faa 100644
|
||||
--- a/op25/gr-op25/python/CMakeLists.txt
|
||||
+++ b/op25/gr-op25/python/CMakeLists.txt
|
||||
@@ -31,7 +31,7 @@ endif()
|
||||
GR_PYTHON_INSTALL(
|
||||
FILES
|
||||
__init__.py
|
||||
- DESTINATION ${GR_PYTHON_DIR}/op25
|
||||
+ DESTINATION ${OP25_PYTHON_DIR}/op25
|
||||
)
|
||||
|
||||
########################################################################
|
||||
diff --git a/op25/gr-op25/swig/CMakeLists.txt b/op25/gr-op25/swig/CMakeLists.txt
|
||||
index e99226f..fd7bd85 100644
|
||||
--- a/op25/gr-op25/swig/CMakeLists.txt
|
||||
+++ b/op25/gr-op25/swig/CMakeLists.txt
|
||||
@@ -21,7 +21,7 @@
|
||||
# Include swig generation macros
|
||||
########################################################################
|
||||
find_package(SWIG)
|
||||
-find_package(PythonLibs 2)
|
||||
+find_package(PythonLibs 3)
|
||||
if(NOT SWIG_FOUND OR NOT PYTHONLIBS_FOUND)
|
||||
return()
|
||||
endif()
|
||||
@@ -31,9 +31,7 @@ include(GrPython)
|
||||
########################################################################
|
||||
# Setup swig generation
|
||||
########################################################################
|
||||
-foreach(incdir ${GNURADIO_RUNTIME_INCLUDE_DIRS})
|
||||
- list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/gnuradio/swig)
|
||||
-endforeach(incdir)
|
||||
+set(GR_SWIG_INCLUDE_DIRS $<TARGET_PROPERTY:gnuradio::runtime_swig,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
set(GR_SWIG_LIBRARIES gnuradio-op25)
|
||||
set(GR_SWIG_DOC_FILE ${CMAKE_CURRENT_BINARY_DIR}/op25_swig_doc.i)
|
||||
@@ -44,7 +42,7 @@ GR_SWIG_MAKE(op25_swig op25_swig.i)
|
||||
########################################################################
|
||||
# Install the build swig module
|
||||
########################################################################
|
||||
-GR_SWIG_INSTALL(TARGETS op25_swig DESTINATION ${GR_PYTHON_DIR}/op25)
|
||||
+GR_SWIG_INSTALL(TARGETS op25_swig DESTINATION ${OP25_PYTHON_DIR}/op25)
|
||||
|
||||
########################################################################
|
||||
# Install swig .i files for development
|
||||
diff --git a/op25/gr-op25_repeater/CMakeLists.txt b/op25/gr-op25_repeater/CMakeLists.txt
|
||||
index fa29b9e..dc1d8e7 100644
|
||||
--- a/op25/gr-op25_repeater/CMakeLists.txt
|
||||
+++ b/op25/gr-op25_repeater/CMakeLists.txt
|
||||
@@ -68,6 +68,9 @@ endif()
|
||||
########################################################################
|
||||
find_package(CppUnit)
|
||||
|
||||
+set(ENABLE_PYTHON "TRUE" CACHE BOOL "enable python")
|
||||
+cmake_policy(SET CMP0012 NEW)
|
||||
+
|
||||
# To run a more advanced search for GNU Radio and it's components and
|
||||
# versions, use the following. Add any components required to the list
|
||||
# of GR_REQUIRED_COMPONENTS (in all caps) and change "version" to the
|
||||
@@ -75,11 +78,12 @@ find_package(CppUnit)
|
||||
#
|
||||
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
||||
# find_package(Gnuradio "version")
|
||||
-find_package(Gnuradio)
|
||||
-
|
||||
-if(NOT GNURADIO_RUNTIME_FOUND)
|
||||
- message(FATAL_ERROR "GnuRadio Runtime required to compile op25_repeater")
|
||||
+set(MIN_GR_VERSION "3.8.0")
|
||||
+find_package(Gnuradio REQUIRED)
|
||||
+if("${Gnuradio_VERSION}" VERSION_LESS MIN_GR_VERSION)
|
||||
+ MESSAGE(FATAL_ERROR "GnuRadio version required: >=\"" ${MIN_GR_VERSION} "\" found: \"" ${Gnuradio_VERSION} "\"")
|
||||
endif()
|
||||
+
|
||||
if(NOT CPPUNIT_FOUND)
|
||||
message(FATAL_ERROR "CppUnit required to compile op25_repeater")
|
||||
endif()
|
||||
diff --git a/op25/gr-op25_repeater/apps/audio.py b/op25/gr-op25_repeater/apps/audio.py
|
||||
index 26cbe4f..255812f 100755
|
||||
--- a/op25/gr-op25_repeater/apps/audio.py
|
||||
+++ b/op25/gr-op25_repeater/apps/audio.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017, 2018 Graham Norbury
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/http_server.py b/op25/gr-op25_repeater/apps/http_server.py
|
||||
index f402353..f4b047f 100755
|
||||
--- a/op25/gr-op25_repeater/apps/http_server.py
|
||||
+++ b/op25/gr-op25_repeater/apps/http_server.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017, 2018, 2019, 2020 Max H. Parke KA1RBI
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/multi_rx.py b/op25/gr-op25_repeater/apps/multi_rx.py
|
||||
index e4c71ca..42625f5 100755
|
||||
--- a/op25/gr-op25_repeater/apps/multi_rx.py
|
||||
+++ b/op25/gr-op25_repeater/apps/multi_rx.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Max H. Parke KA1RBI
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/rx.py b/op25/gr-op25_repeater/apps/rx.py
|
||||
index c671120..b226f8a 100755
|
||||
--- a/op25/gr-op25_repeater/apps/rx.py
|
||||
+++ b/op25/gr-op25_repeater/apps/rx.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2008-2011 Steve Glass
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/sockaudio.py b/op25/gr-op25_repeater/apps/sockaudio.py
|
||||
index 76282ac..68c6adb 100755
|
||||
--- a/op25/gr-op25_repeater/apps/sockaudio.py
|
||||
+++ b/op25/gr-op25_repeater/apps/sockaudio.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017, 2018 Graham Norbury
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/terminal.py b/op25/gr-op25_repeater/apps/terminal.py
|
||||
index c732a3a..fa73af4 100755
|
||||
--- a/op25/gr-op25_repeater/apps/terminal.py
|
||||
+++ b/op25/gr-op25_repeater/apps/terminal.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2008-2011 Steve Glass
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/tx/dv_tx.py b/op25/gr-op25_repeater/apps/tx/dv_tx.py
|
||||
index 342ba3f..fba399f 100755
|
||||
--- a/op25/gr-op25_repeater/apps/tx/dv_tx.py
|
||||
+++ b/op25/gr-op25_repeater/apps/tx/dv_tx.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
#################################################################################
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/tx/generate-tsbks.py b/op25/gr-op25_repeater/apps/tx/generate-tsbks.py
|
||||
index f4b06d9..de3eb28 100755
|
||||
--- a/op25/gr-op25_repeater/apps/tx/generate-tsbks.py
|
||||
+++ b/op25/gr-op25_repeater/apps/tx/generate-tsbks.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#! /usr/bin/python
|
||||
+#! /usr/bin/python3
|
||||
|
||||
from p25craft import make_fakecc_tsdu
|
||||
|
||||
diff --git a/op25/gr-op25_repeater/apps/tx/multi_tx.py b/op25/gr-op25_repeater/apps/tx/multi_tx.py
|
||||
index a54bc01..1707502 100755
|
||||
--- a/op25/gr-op25_repeater/apps/tx/multi_tx.py
|
||||
+++ b/op25/gr-op25_repeater/apps/tx/multi_tx.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
#################################################################################
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/tx/op25_tx.py b/op25/gr-op25_repeater/apps/tx/op25_tx.py
|
||||
index 3e7afe4..7baa6ae 100755
|
||||
--- a/op25/gr-op25_repeater/apps/tx/op25_tx.py
|
||||
+++ b/op25/gr-op25_repeater/apps/tx/op25_tx.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2005,2006,2007 Free Software Foundation, Inc.
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/tx/p25craft.py b/op25/gr-op25_repeater/apps/tx/p25craft.py
|
||||
index 7fae739..b6e3999 100755
|
||||
--- a/op25/gr-op25_repeater/apps/tx/p25craft.py
|
||||
+++ b/op25/gr-op25_repeater/apps/tx/p25craft.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/python
|
||||
+#!/usr/bin/python3
|
||||
#
|
||||
# p25craft.py - utility for crafting APCO P25 packets
|
||||
#
|
||||
diff --git a/op25/gr-op25_repeater/apps/tx/unpack.py b/op25/gr-op25_repeater/apps/tx/unpack.py
|
||||
index 7cbf05b..73a861a 100755
|
||||
--- a/op25/gr-op25_repeater/apps/tx/unpack.py
|
||||
+++ b/op25/gr-op25_repeater/apps/tx/unpack.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
from gnuradio import gr, audio, eng_notation, blocks
|
||||
from gnuradio.eng_option import eng_option
|
||||
from optparse import OptionParser
|
||||
diff --git a/op25/gr-op25_repeater/apps/util/arb-resample.py b/op25/gr-op25_repeater/apps/util/arb-resample.py
|
||||
index 56a762f..2bf75af 100755
|
||||
--- a/op25/gr-op25_repeater/apps/util/arb-resample.py
|
||||
+++ b/op25/gr-op25_repeater/apps/util/arb-resample.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import math
|
||||
diff --git a/op25/gr-op25_repeater/apps/util/cqpsk-demod-file.py b/op25/gr-op25_repeater/apps/util/cqpsk-demod-file.py
|
||||
index 051ddf0..171878d 100755
|
||||
--- a/op25/gr-op25_repeater/apps/util/cqpsk-demod-file.py
|
||||
+++ b/op25/gr-op25_repeater/apps/util/cqpsk-demod-file.py
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env python
|
||||
+#!/usr/bin/env python3
|
||||
|
||||
#
|
||||
# (C) Copyright 2010, 2014 Max H. Parke, KA1RBI
|
||||
diff --git a/op25/gr-op25_repeater/lib/CMakeLists.txt b/op25/gr-op25_repeater/lib/CMakeLists.txt
|
||||
index da84e14..1464230 100644
|
||||
--- a/op25/gr-op25_repeater/lib/CMakeLists.txt
|
||||
+++ b/op25/gr-op25_repeater/lib/CMakeLists.txt
|
||||
@@ -68,7 +68,7 @@ else(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
find_library(GR_FILTER_LIBRARY libgnuradio-filter.so )
|
||||
endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(GR_FILTER_LIBRARIES ${GR_FILTER_LIBRARY})
|
||||
-target_link_libraries(gnuradio-op25_repeater ${Boost_LIBRARIES} ${GNURADIO_RUNTIME_LIBRARIES} ${GR_FILTER_LIBRARIES} imbe_vocoder)
|
||||
+target_link_libraries(gnuradio-op25_repeater ${Boost_LIBRARIES} gnuradio::gnuradio-runtime ${GR_FILTER_LIBRARIES} imbe_vocoder)
|
||||
set_target_properties(gnuradio-op25_repeater PROPERTIES DEFINE_SYMBOL "gnuradio_op25_repeater_EXPORTS")
|
||||
|
||||
########################################################################
|
||||
diff --git a/op25/gr-op25_repeater/python/CMakeLists.txt b/op25/gr-op25_repeater/python/CMakeLists.txt
|
||||
index 9958577..bb117a0 100644
|
||||
--- a/op25/gr-op25_repeater/python/CMakeLists.txt
|
||||
+++ b/op25/gr-op25_repeater/python/CMakeLists.txt
|
||||
@@ -31,7 +31,7 @@ endif()
|
||||
GR_PYTHON_INSTALL(
|
||||
FILES
|
||||
__init__.py
|
||||
- DESTINATION ${GR_PYTHON_DIR}/op25_repeater
|
||||
+ DESTINATION ${OP25_PYTHON_DIR}/op25_repeater
|
||||
)
|
||||
|
||||
########################################################################
|
||||
diff --git a/op25/gr-op25_repeater/swig/CMakeLists.txt b/op25/gr-op25_repeater/swig/CMakeLists.txt
|
||||
index 1d88bbd..50819d7 100644
|
||||
--- a/op25/gr-op25_repeater/swig/CMakeLists.txt
|
||||
+++ b/op25/gr-op25_repeater/swig/CMakeLists.txt
|
||||
@@ -21,7 +21,7 @@
|
||||
# Include swig generation macros
|
||||
########################################################################
|
||||
find_package(SWIG)
|
||||
-find_package(PythonLibs 2)
|
||||
+find_package(PythonLibs 3)
|
||||
if(NOT SWIG_FOUND OR NOT PYTHONLIBS_FOUND)
|
||||
return()
|
||||
endif()
|
||||
@@ -31,9 +31,7 @@ include(GrPython)
|
||||
########################################################################
|
||||
# Setup swig generation
|
||||
########################################################################
|
||||
-foreach(incdir ${GNURADIO_RUNTIME_INCLUDE_DIRS})
|
||||
- list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/gnuradio/swig)
|
||||
-endforeach(incdir)
|
||||
+set(GR_SWIG_INCLUDE_DIRS $<TARGET_PROPERTY:gnuradio::runtime_swig,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
set(GR_SWIG_LIBRARIES gnuradio-op25_repeater)
|
||||
set(GR_SWIG_DOC_FILE ${CMAKE_CURRENT_BINARY_DIR}/op25_repeater_swig_doc.i)
|
||||
@@ -44,7 +42,7 @@ GR_SWIG_MAKE(op25_repeater_swig op25_repeater_swig.i)
|
||||
########################################################################
|
||||
# Install the build swig module
|
||||
########################################################################
|
||||
-GR_SWIG_INSTALL(TARGETS op25_repeater_swig DESTINATION ${GR_PYTHON_DIR}/op25_repeater)
|
||||
+GR_SWIG_INSTALL(TARGETS op25_repeater_swig DESTINATION ${OP25_PYTHON_DIR}/op25_repeater)
|
||||
|
||||
########################################################################
|
||||
# Install swig .i files for development
|
|
@ -0,0 +1,72 @@
|
|||
#! /bin/bash
|
||||
|
||||
# op25 install script for debian based systems
|
||||
# including ubuntu 14/16 and raspbian
|
||||
#
|
||||
# *** this script for gnuradio 3.9 and 3.10 ***
|
||||
|
||||
TREE_DIR="$PWD/src" # directory in which our gr3.9 tree will be built
|
||||
|
||||
if [ ! -d op25/gr-op25 ]; then
|
||||
echo ====== error, op25 top level directories not found
|
||||
echo ====== you must change to the op25 top level directory
|
||||
echo ====== before running this script
|
||||
exit
|
||||
fi
|
||||
|
||||
TOP_DIR=$PWD
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep gnuradio
|
||||
sudo apt-get install gnuradio gnuradio-dev gr-osmosdr librtlsdr-dev libuhd-dev libhackrf-dev libitpp-dev libpcap-dev cmake git swig build-essential pkg-config doxygen python3-numpy python3-waitress python3-requests python3-pip pybind11-dev clang-format libsndfile1-dev
|
||||
|
||||
# setup and populate gr3.9 src tree
|
||||
echo
|
||||
echo " = = = = = = = generating source tree for gr3.9, this could take a while = = = = = = ="
|
||||
echo
|
||||
python3 add_gr3.9.py $PWD $TREE_DIR
|
||||
if [ ! -d $TREE_DIR ]; then
|
||||
echo ==== Error, directory $TREE_DIR creation failed, exiting
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd $TREE_DIR
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../op25/gr-op25
|
||||
make
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
cd ../
|
||||
|
||||
mkdir build_repeater
|
||||
cd build_repeater
|
||||
cmake ../op25/gr-op25_repeater
|
||||
make
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
cd ../
|
||||
|
||||
cd $TOP_DIR/op25
|
||||
sh ../scripts/do_sedpy.sh
|
||||
|
||||
echo ======
|
||||
echo ====== NOTICE
|
||||
echo ======
|
||||
echo ====== The gnuplot package is not installed by default here,
|
||||
echo ====== as its installation requires numerous prerequisite packages
|
||||
echo ====== that you may not want to install.
|
||||
echo ======
|
||||
echo ====== In order to do plotting in rx.py using the \-P option
|
||||
echo ====== you must install gnuplot, e.g., manually as follows:
|
||||
echo ======
|
||||
echo ====== sudo apt-get install gnuplot-x11
|
||||
echo ======
|
||||
echo ======
|
||||
echo ====== Separately, we suggest you set device and driver permissions:
|
||||
echo ====== \$ cd scripts
|
||||
echo ====== \$ ./udev_rules.sh
|
||||
echo ====== It is only necessary to do this once. Currently this script
|
||||
echo ====== handles the rtl-sdr and airspy only.
|
||||
echo ======
|
|
@ -0,0 +1,45 @@
|
|||
#! /bin/sh
|
||||
|
||||
# op25 install script for debian based systems
|
||||
# including ubuntu 14/16 and raspbian
|
||||
|
||||
if [ ! -d op25/gr-op25 ]; then
|
||||
echo ====== error, op25 top level directories not found
|
||||
echo ====== you must change to the op25 top level directory
|
||||
echo ====== before running this script
|
||||
exit
|
||||
fi
|
||||
|
||||
sudo sed -i -- 's/^# *deb-src/deb-src/' /etc/apt/sources.list
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep gnuradio
|
||||
sudo apt-get install gnuradio gnuradio-dev gr-osmosdr librtlsdr-dev libuhd-dev libhackrf-dev libitpp-dev libpcap-dev cmake git swig build-essential pkg-config doxygen python3-numpy python3-waitress python3-requests
|
||||
sudo apt-get install liborc-dev
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../
|
||||
make
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
|
||||
echo ======
|
||||
echo ====== NOTICE
|
||||
echo ======
|
||||
echo ====== The gnuplot package is not installed by default here,
|
||||
echo ====== as its installation requires numerous prerequisite packages
|
||||
echo ====== that you may not want to install.
|
||||
echo ======
|
||||
echo ====== In order to do plotting in rx.py using the \-P option
|
||||
echo ====== you must install gnuplot, e.g., manually as follows:
|
||||
echo ======
|
||||
echo ====== sudo apt-get install gnuplot-x11
|
||||
echo ======
|
||||
echo ======
|
||||
echo ====== Separately, we suggest you set device and driver permissions:
|
||||
echo ====== \$ cd scripts
|
||||
echo ====== \$ ./udev_rules.sh
|
||||
echo ====== It is only necessary to do this once. Currently this script
|
||||
echo ====== handles the rtl-sdr and airspy only.
|
||||
echo ======
|
|
@ -63,6 +63,32 @@ if(NOT Boost_FOUND)
|
|||
message(FATAL_ERROR "Boost required to compile op25")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Find gnuradio build dependencies
|
||||
########################################################################
|
||||
find_package(CppUnit)
|
||||
|
||||
set(ENABLE_PYTHON "TRUE" CACHE BOOL "enable python")
|
||||
cmake_policy(SET CMP0012 NEW)
|
||||
|
||||
# To run a more advanced search for GNU Radio and it's components and
|
||||
# versions, use the following. Add any components required to the list
|
||||
# of GR_REQUIRED_COMPONENTS (in all caps) and change "version" to the
|
||||
# minimum API compatible version required.
|
||||
#
|
||||
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
|
||||
# find_package(Gnuradio "version")
|
||||
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
||||
SET(MIN_GR_VERSION "3.8.0")
|
||||
find_package(Gnuradio REQUIRED)
|
||||
if("${Gnuradio_VERSION}" VERSION_LESS MIN_GR_VERSION)
|
||||
MESSAGE(FATAL_ERROR "GnuRadio version required: >=\"" ${MIN_GR_VERSION} "\" found: \"" ${Gnuradio_VERSION} "\"")
|
||||
endif()
|
||||
|
||||
if(NOT CPPUNIT_FOUND)
|
||||
message(FATAL_ERROR "CppUnit required to compile op25")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Install directories
|
||||
########################################################################
|
||||
|
@ -80,31 +106,6 @@ set(GR_LIBEXEC_DIR libexec)
|
|||
set(GR_PKG_LIBEXEC_DIR ${GR_LIBEXEC_DIR}/${CMAKE_PROJECT_NAME})
|
||||
set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
|
||||
|
||||
########################################################################
|
||||
# Find gnuradio build dependencies
|
||||
########################################################################
|
||||
find_package(GnuradioRuntime)
|
||||
find_package(CppUnit)
|
||||
|
||||
# To run a more advanced search for GNU Radio and it's components and
|
||||
# versions, use the following. Add any components required to the list
|
||||
# of GR_REQUIRED_COMPONENTS (in all caps) and change "version" to the
|
||||
# minimum API compatible version required.
|
||||
#
|
||||
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
|
||||
# find_package(Gnuradio "version")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
||||
find_package(Gnuradio)
|
||||
endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
|
||||
if(NOT GNURADIO_RUNTIME_FOUND)
|
||||
message(FATAL_ERROR "GnuRadio Runtime required to compile op25")
|
||||
endif()
|
||||
if(NOT CPPUNIT_FOUND)
|
||||
message(FATAL_ERROR "CppUnit required to compile op25")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Setup the include and linker paths
|
||||
########################################################################
|
||||
|
|
|
@ -4,17 +4,17 @@
|
|||
<key>op25_decoder_ff</key>
|
||||
<category>op25</category>
|
||||
<import>import op25</import>
|
||||
<make>op25.decoder_ff($)</make>
|
||||
<make>op25.decoder_ff()</make>
|
||||
<!-- Make one 'param' node for every Parameter you want settable from the GUI.
|
||||
Sub-nodes:
|
||||
* name
|
||||
* key (makes the value accessible as $keyname, e.g. in the make node)
|
||||
* type -->
|
||||
<param>
|
||||
<!--<param>
|
||||
<name>...</name>
|
||||
<key>...</key>
|
||||
<type>...</type>
|
||||
</param>
|
||||
</param>-->
|
||||
|
||||
<!-- Make one 'sink' node per input. Sub-nodes:
|
||||
* name (an identifier for the GUI)
|
||||
|
@ -23,7 +23,7 @@
|
|||
* optional (set to 1 for optional inputs) -->
|
||||
<sink>
|
||||
<name>in</name>
|
||||
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
|
||||
<type>float</type>
|
||||
</sink>
|
||||
|
||||
<!-- Make one 'source' node per output. Sub-nodes:
|
||||
|
@ -32,7 +32,7 @@
|
|||
* vlen
|
||||
* optional (set to 1 for optional inputs) -->
|
||||
<source>
|
||||
<name>out</name>
|
||||
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
|
||||
<name>audio</name>
|
||||
<type>float</type>
|
||||
</source>
|
||||
</block>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<key>op25_fsk4_demod_ff</key>
|
||||
<category>op25</category>
|
||||
<import>import op25</import>
|
||||
<make>op25.fsk4_demod_ff(self.auto_tune_msgq, $sample_rate, $symbol_rate)</make>
|
||||
<make>op25.fsk4_demod_ff($(id)_msgq_out, $sample_rate, $symbol_rate)</make>
|
||||
|
||||
<param>
|
||||
<name>Sample Rate</name>
|
||||
|
@ -20,7 +20,8 @@
|
|||
<type>real</type>
|
||||
</param>
|
||||
|
||||
<param>
|
||||
<!-- Must connect tune message queue as the block expects a queue as first argument! -->
|
||||
<!--<param>
|
||||
<name>Output Auto Tune</name>
|
||||
<key>tune_out</key>
|
||||
<value>True</value>
|
||||
|
@ -34,7 +35,7 @@
|
|||
<name>No</name>
|
||||
<key>False</key>
|
||||
</option>
|
||||
</param>
|
||||
</param>-->
|
||||
|
||||
<sink>
|
||||
<name>in</name>
|
||||
|
@ -45,4 +46,10 @@
|
|||
<name>dibits</name>
|
||||
<type>float</type>
|
||||
</source>
|
||||
|
||||
<source>
|
||||
<name>tune</name>
|
||||
<type>msg</type>
|
||||
<!--<optional>1</optional>-->
|
||||
</source>
|
||||
</block>
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace gr {
|
|||
* class. op25::decoder_bf::make is the public interface for
|
||||
* creating new instances.
|
||||
*/
|
||||
static sptr make();
|
||||
static sptr make(bool idle_silence = true, bool verbose = false);
|
||||
|
||||
/**
|
||||
* Return a pointer to a string identifying the destination of
|
||||
|
@ -78,6 +78,17 @@ namespace gr {
|
|||
* message queue.
|
||||
*/
|
||||
virtual void set_msgq(gr::msg_queue::sptr msgq) = 0;
|
||||
|
||||
virtual void set_idle_silence(bool idle_silence = true) = 0;
|
||||
|
||||
virtual void set_logging(bool verbose = true) = 0;
|
||||
|
||||
typedef std::vector<uint8_t> key_type;
|
||||
typedef std::map<uint16_t, key_type> key_map_type;
|
||||
|
||||
virtual void set_key(const key_type& key) = 0;
|
||||
|
||||
virtual void set_key_map(const key_map_type& keys) = 0;
|
||||
};
|
||||
|
||||
} // namespace op25
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/* -*- c++ -*- */
|
||||
/*
|
||||
* Copyright 2005,2013 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_OP25_MESSAGE_H
|
||||
#define INCLUDED_OP25_MESSAGE_H
|
||||
|
||||
#include <op25/api.h>
|
||||
#include <gnuradio/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace gr {
|
||||
namespace op25 {
|
||||
|
||||
/*!
|
||||
* \brief Message class.
|
||||
*
|
||||
* \ingroup misc
|
||||
* The ideas and method names for adjustable message length were
|
||||
* lifted from the click modular router "Packet" class.
|
||||
*/
|
||||
class OP25_API message
|
||||
{
|
||||
public:
|
||||
typedef std::shared_ptr<message> sptr;
|
||||
|
||||
private:
|
||||
sptr d_next; // link field for msg queue
|
||||
long d_type; // type of the message
|
||||
double d_arg1; // optional arg1
|
||||
double d_arg2; // optional arg2
|
||||
|
||||
std::vector<unsigned char> d_buf;
|
||||
unsigned char* d_msg_start; // where the msg starts
|
||||
unsigned char* d_msg_end; // one beyond end of msg
|
||||
|
||||
message(long type, double arg1, double arg2, size_t length);
|
||||
|
||||
friend class msg_queue;
|
||||
|
||||
unsigned char* buf_data() { return d_buf.data(); }
|
||||
size_t buf_len() const { return d_buf.size(); }
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief public constructor for message
|
||||
*/
|
||||
static sptr make(long type = 0, double arg1 = 0, double arg2 = 0, size_t length = 0);
|
||||
|
||||
static sptr make_from_string(const std::string s,
|
||||
long type = 0,
|
||||
double arg1 = 0,
|
||||
double arg2 = 0);
|
||||
|
||||
|
||||
~message();
|
||||
|
||||
long type() const { return d_type; }
|
||||
double arg1() const { return d_arg1; }
|
||||
double arg2() const { return d_arg2; }
|
||||
|
||||
void set_type(long type) { d_type = type; }
|
||||
void set_arg1(double arg1) { d_arg1 = arg1; }
|
||||
void set_arg2(double arg2) { d_arg2 = arg2; }
|
||||
|
||||
unsigned char* msg() const { return d_msg_start; }
|
||||
size_t length() const { return d_msg_end - d_msg_start; }
|
||||
std::string to_string() const;
|
||||
};
|
||||
|
||||
OP25_API long message_ncurrently_allocated();
|
||||
|
||||
} /* namespace op25 */
|
||||
} /* namespace gr */
|
||||
|
||||
#endif /* INCLUDED_OP25_MESSAGE_H */
|
|
@ -0,0 +1,39 @@
|
|||
/* -*- c++ -*- */
|
||||
/*
|
||||
* Copyright 2005,2013 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_OP25_MSG_HANDLER_H
|
||||
#define INCLUDED_OP25_MSG_HANDLER_H
|
||||
|
||||
#include <op25/api.h>
|
||||
#include <op25/message.h>
|
||||
|
||||
namespace gr {
|
||||
namespace op25 {
|
||||
|
||||
class msg_handler;
|
||||
typedef std::shared_ptr<msg_handler> msg_handler_sptr;
|
||||
|
||||
/*!
|
||||
* \brief abstract class of message handlers
|
||||
* \ingroup base
|
||||
*/
|
||||
class OP25_API msg_handler
|
||||
{
|
||||
public:
|
||||
virtual ~msg_handler();
|
||||
|
||||
//! handle \p msg
|
||||
virtual void handle(message::sptr msg) = 0;
|
||||
};
|
||||
|
||||
} /* namespace op25 */
|
||||
} /* namespace gr */
|
||||
|
||||
#endif /* INCLUDED_OP25_MSG_HANDLER_H */
|
|
@ -0,0 +1,85 @@
|
|||
/* -*- c++ -*- */
|
||||
/*
|
||||
* Copyright 2005,2009 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_OP25_MSG_QUEUE_H
|
||||
#define INCLUDED_OP25_MSG_QUEUE_H
|
||||
|
||||
#include <op25/api.h>
|
||||
#include <op25/msg_handler.h>
|
||||
#include <gnuradio/thread/thread.h>
|
||||
|
||||
namespace gr {
|
||||
namespace op25 {
|
||||
|
||||
/*!
|
||||
* \brief thread-safe message queue
|
||||
* \ingroup misc
|
||||
*/
|
||||
class OP25_API msg_queue : public msg_handler
|
||||
{
|
||||
gr::thread::mutex d_mutex;
|
||||
gr::thread::condition_variable d_not_empty;
|
||||
gr::thread::condition_variable d_not_full;
|
||||
message::sptr d_head;
|
||||
message::sptr d_tail;
|
||||
unsigned int d_count; // # of messages in queue.
|
||||
unsigned int d_limit; // max # of messages in queue. 0 -> unbounded
|
||||
|
||||
public:
|
||||
typedef std::shared_ptr<msg_queue> sptr;
|
||||
|
||||
static sptr make(unsigned int limit = 0);
|
||||
|
||||
msg_queue(unsigned int limit);
|
||||
~msg_queue() override;
|
||||
|
||||
//! Generic msg_handler method: insert the message.
|
||||
void handle(message::sptr msg) override { insert_tail(msg); }
|
||||
|
||||
/*!
|
||||
* \brief Insert message at tail of queue.
|
||||
* \param msg message
|
||||
*
|
||||
* Block if queue if full.
|
||||
*/
|
||||
void insert_tail(message::sptr msg);
|
||||
|
||||
/*!
|
||||
* \brief Delete message from head of queue and return it.
|
||||
* Block if no message is available.
|
||||
*/
|
||||
message::sptr delete_head();
|
||||
|
||||
/*!
|
||||
* \brief If there's a message in the q, delete it and return it.
|
||||
* If no message is available, return 0.
|
||||
*/
|
||||
message::sptr delete_head_nowait();
|
||||
|
||||
//! Delete all messages from the queue
|
||||
void flush();
|
||||
|
||||
//! is the queue empty?
|
||||
bool empty_p() const { return d_count == 0; }
|
||||
|
||||
//! is the queue full?
|
||||
bool full_p() const { return d_limit != 0 && d_count >= d_limit; }
|
||||
|
||||
//! return number of messages in queue
|
||||
unsigned int count() const { return d_count; }
|
||||
|
||||
//! return limit on number of message in queue. 0 -> unbounded
|
||||
unsigned int limit() const { return d_limit; }
|
||||
};
|
||||
|
||||
} /* namespace op25 */
|
||||
} /* namespace gr */
|
||||
|
||||
#endif /* INCLUDED_OP25_MSG_QUEUE_H */
|
|
@ -53,10 +53,17 @@ list(APPEND op25_sources
|
|||
value_string.cc
|
||||
pickle.cc
|
||||
pcap_source_b_impl.cc
|
||||
bch.cc
|
||||
ldu.cc
|
||||
crypto.cc
|
||||
crypto_module_du_handler.cc
|
||||
deskey.c
|
||||
desport.c
|
||||
dessp.c
|
||||
)
|
||||
|
||||
add_library(gnuradio-op25 SHARED ${op25_sources})
|
||||
target_link_libraries(gnuradio-op25 ${Boost_LIBRARIES} ${GNURADIO_RUNTIME_LIBRARIES} itpp pcap)
|
||||
target_link_libraries(gnuradio-op25 ${Boost_LIBRARIES} gnuradio::gnuradio-runtime itpp pcap)
|
||||
set_target_properties(gnuradio-op25 PROPERTIES DEFINE_SYMBOL "gnuradio_op25_EXPORTS")
|
||||
|
||||
########################################################################
|
||||
|
|
|
@ -55,10 +55,10 @@ abstract_data_unit::correct_errors()
|
|||
}
|
||||
|
||||
void
|
||||
abstract_data_unit::decode_audio(imbe_decoder& imbe)
|
||||
abstract_data_unit::decode_audio(imbe_decoder& imbe, crypto_module::sptr crypto_mod)
|
||||
{
|
||||
if(is_complete()) {
|
||||
do_decode_audio(d_frame_body, imbe);
|
||||
do_decode_audio(d_frame_body, imbe, crypto_mod);
|
||||
} else {
|
||||
ostringstream msg;
|
||||
msg << "cannot decode audio - frame is not complete" << endl;
|
||||
|
@ -153,7 +153,8 @@ abstract_data_unit::dump(ostream& os) const
|
|||
}
|
||||
|
||||
abstract_data_unit::abstract_data_unit(const_bit_queue& frame_body) :
|
||||
d_frame_body(frame_body.size())
|
||||
d_frame_body(frame_body.size()),
|
||||
d_logging_enabled(false)
|
||||
{
|
||||
copy(frame_body.begin(), frame_body.end(), d_frame_body.begin());
|
||||
}
|
||||
|
@ -164,7 +165,7 @@ abstract_data_unit::do_correct_errors(bit_vector& frame_body)
|
|||
}
|
||||
|
||||
void
|
||||
abstract_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe)
|
||||
abstract_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -179,3 +180,15 @@ abstract_data_unit::frame_size() const
|
|||
{
|
||||
return d_frame_body.size();
|
||||
}
|
||||
|
||||
void
|
||||
abstract_data_unit::set_logging(bool on)
|
||||
{
|
||||
d_logging_enabled = on;
|
||||
}
|
||||
|
||||
bool
|
||||
abstract_data_unit::logging_enabled() const
|
||||
{
|
||||
return d_logging_enabled;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "data_unit.h"
|
||||
#include "op25_yank.h"
|
||||
#include "crypto.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
@ -62,7 +63,7 @@ public:
|
|||
* \precondition is_complete() == true.
|
||||
* \param imbe The imbe_decoder to use to generate the audio.
|
||||
*/
|
||||
virtual void decode_audio(imbe_decoder& imbe);
|
||||
virtual void decode_audio(imbe_decoder& imbe, crypto_module::sptr crypto_mod);
|
||||
|
||||
/**
|
||||
* Decode the frame into an octet vector.
|
||||
|
@ -117,6 +118,15 @@ public:
|
|||
*/
|
||||
virtual std::string snapshot() const;
|
||||
|
||||
/**
|
||||
* Returns a string describing the Data Unit ID (DUID).
|
||||
*
|
||||
* \return A string identifying the DUID.
|
||||
*/
|
||||
virtual std::string duid_str() const = 0;
|
||||
|
||||
virtual void set_logging(bool on);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
|
@ -140,7 +150,7 @@ protected:
|
|||
* \param frame_body The const_bit_vector to decode.
|
||||
* \param imbe The imbe_decoder to use.
|
||||
*/
|
||||
virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe);
|
||||
virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod);
|
||||
|
||||
/**
|
||||
* Decode frame_body and write the decoded frame contents to msg.
|
||||
|
@ -152,13 +162,6 @@ protected:
|
|||
*/
|
||||
virtual size_t decode_frame(const_bit_vector& frame_body, size_t msg_sz, uint8_t *msg);
|
||||
|
||||
/**
|
||||
* Returns a string describing the Data Unit ID (DUID).
|
||||
*
|
||||
* \return A string identifying the DUID.
|
||||
*/
|
||||
virtual std::string duid_str() const = 0;
|
||||
|
||||
/**
|
||||
* Return a reference to the frame body.
|
||||
*/
|
||||
|
@ -180,6 +183,8 @@ protected:
|
|||
*/
|
||||
virtual uint16_t frame_size() const;
|
||||
|
||||
virtual bool logging_enabled() const;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
@ -187,6 +192,7 @@ private:
|
|||
*/
|
||||
bit_vector d_frame_body;
|
||||
|
||||
bool d_logging_enabled;
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_ABSTRACT_DATA_UNIT_H */
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include "bch.h"
|
||||
/*
|
||||
* Copyright 2010, KA1RBI
|
||||
*/
|
||||
static const int bchGFexp[64] = {
|
||||
1, 2, 4, 8, 16, 32, 3, 6, 12, 24, 48, 35, 5, 10, 20, 40,
|
||||
19, 38, 15, 30, 60, 59, 53, 41, 17, 34, 7, 14, 28, 56, 51, 37,
|
||||
9, 18, 36, 11, 22, 44, 27, 54, 47, 29, 58, 55, 45, 25, 50, 39,
|
||||
13, 26, 52, 43, 21, 42, 23, 46, 31, 62, 63, 61, 57, 49, 33, 0
|
||||
};
|
||||
|
||||
static const int bchGFlog[64] = {
|
||||
-1, 0, 1, 6, 2, 12, 7, 26, 3, 32, 13, 35, 8, 48, 27, 18,
|
||||
4, 24, 33, 16, 14, 52, 36, 54, 9, 45, 49, 38, 28, 41, 19, 56,
|
||||
5, 62, 25, 11, 34, 31, 17, 47, 15, 23, 53, 51, 37, 44, 55, 40,
|
||||
10, 61, 46, 30, 50, 22, 39, 43, 29, 60, 42, 21, 20, 59, 57, 58
|
||||
};
|
||||
|
||||
static const int bchG[48] = {
|
||||
1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0,
|
||||
1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0,
|
||||
1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1
|
||||
};
|
||||
|
||||
int bchDec(bit_vector& Codeword)
|
||||
{
|
||||
|
||||
int elp[24][ 22], S[23];
|
||||
int D[23], L[24], uLu[24];
|
||||
int root[11], locn[11], reg[12];
|
||||
int i,j,U,q,count;
|
||||
int SynError, CantDecode;
|
||||
|
||||
SynError = 0; CantDecode = 0;
|
||||
|
||||
for(i = 1; i <= 22; i++) {
|
||||
S[i] = 0;
|
||||
// FOR j = 0 TO 62
|
||||
for(j = 0; j <= 62; j++) {
|
||||
if( Codeword[j]) { S[i] = S[i] ^ bchGFexp[(i * j) % 63]; }
|
||||
}
|
||||
if( S[i]) { SynError = 1; }
|
||||
S[i] = bchGFlog[S[i]];
|
||||
// printf("S[%d] %d\n", i, S[i]);
|
||||
}
|
||||
|
||||
if( SynError) { //if there are errors, try to correct them
|
||||
L[0] = 0; uLu[0] = -1; D[0] = 0; elp[0][ 0] = 0;
|
||||
L[1] = 0; uLu[1] = 0; D[1] = S[1]; elp[1][ 0] = 1;
|
||||
//FOR i = 1 TO 21
|
||||
for(i = 1; i <= 21; i++) {
|
||||
elp[0][ i] = -1; elp[1][ i] = 0;
|
||||
}
|
||||
U = 0;
|
||||
|
||||
do {
|
||||
U = U + 1;
|
||||
if( D[U] == -1) {
|
||||
L[U + 1] = L[U];
|
||||
// FOR i = 0 TO L[U]
|
||||
for(i = 0; i <= L[U]; i++) {
|
||||
elp[U + 1][ i] = elp[U][ i]; elp[U][ i] = bchGFlog[elp[U][ i]];
|
||||
}
|
||||
} else {
|
||||
//search for words with greatest uLu(q) for which d(q)!=0
|
||||
q = U - 1;
|
||||
while((D[q] == -1) &&(q > 0)) { q = q - 1; }
|
||||
//have found first non-zero d(q)
|
||||
if( q > 0) {
|
||||
j = q;
|
||||
do { j = j - 1; if((D[j] != -1) &&(uLu[q] < uLu[j])) { q = j; }
|
||||
} while( j > 0) ;
|
||||
}
|
||||
|
||||
//store degree of new elp polynomial
|
||||
if( L[U] > L[q] + U - q) {
|
||||
L[U + 1] = L[U] ;
|
||||
} else {
|
||||
L[U + 1] = L[q] + U - q;
|
||||
}
|
||||
|
||||
///* form new elp(x) */
|
||||
// FOR i = 0 TO 21
|
||||
for(i = 0; i <= 21; i++) {
|
||||
elp[U + 1][ i] = 0;
|
||||
}
|
||||
// FOR i = 0 TO L(q)
|
||||
for(i = 0; i <= L[q]; i++) {
|
||||
if( elp[q][ i] != -1) {
|
||||
elp[U + 1][ i + U - q] = bchGFexp[(D[U] + 63 - D[q] + elp[q][ i]) % 63];
|
||||
}
|
||||
}
|
||||
// FOR i = 0 TO L(U)
|
||||
for(i = 0; i <= L[U]; i++) {
|
||||
elp[U + 1][ i] = elp[U + 1][ i] ^ elp[U][ i];
|
||||
elp[U][ i] = bchGFlog[elp[U][ i]];
|
||||
}
|
||||
}
|
||||
uLu[U + 1] = U - L[U + 1];
|
||||
|
||||
//form(u+1)th discrepancy
|
||||
if( U < 22) {
|
||||
//no discrepancy computed on last iteration
|
||||
if( S[U + 1] != -1) { D[U + 1] = bchGFexp[S[U + 1]]; } else { D[U + 1] = 0; }
|
||||
// FOR i = 1 TO L(U + 1)
|
||||
for(i = 1; i <= L[U + 1]; i++) {
|
||||
if((S[U + 1 - i] != -1) &&(elp[U + 1][ i] != 0)) {
|
||||
D[U + 1] = D[U + 1] ^ bchGFexp[(S[U + 1 - i] + bchGFlog[elp[U + 1][ i]]) % 63];
|
||||
}
|
||||
}
|
||||
//put d(u+1) into index form */
|
||||
D[U + 1] = bchGFlog[D[U + 1]];
|
||||
}
|
||||
} while((U < 22) &&(L[U + 1] <= 11));
|
||||
|
||||
U = U + 1;
|
||||
if( L[U] <= 11) { // /* Can correct errors */
|
||||
//put elp into index form
|
||||
// FOR i = 0 TO L[U]
|
||||
for(i = 0; i <= L[U]; i++) {
|
||||
elp[U][ i] = bchGFlog[elp[U][ i]];
|
||||
}
|
||||
|
||||
//Chien search: find roots of the error location polynomial
|
||||
// FOR i = 1 TO L(U)
|
||||
for(i = 1; i <= L[U]; i++) {
|
||||
reg[i] = elp[U][ i];
|
||||
}
|
||||
count = 0;
|
||||
// FOR i = 1 TO 63
|
||||
for(i = 1; i <= 63; i++) {
|
||||
q = 1;
|
||||
//FOR j = 1 TO L(U)
|
||||
for(j = 1; j <= L[U]; j++) {
|
||||
if( reg[j] != -1) {
|
||||
reg[j] =(reg[j] + j) % 63; q = q ^ bchGFexp[reg[j]];
|
||||
}
|
||||
}
|
||||
if( q == 0) { //store root and error location number indices
|
||||
root[count] = i; locn[count] = 63 - i; count = count + 1;
|
||||
}
|
||||
}
|
||||
if( count == L[U]) {
|
||||
//no. roots = degree of elp hence <= t errors
|
||||
//FOR i = 0 TO L[U] - 1
|
||||
for(i = 0; i <= L[U]-1; i++) {
|
||||
Codeword[locn[i]] = Codeword[locn[i]] ^ 1;
|
||||
}
|
||||
CantDecode = count;
|
||||
} else { //elp has degree >t hence cannot solve
|
||||
CantDecode = -1;
|
||||
}
|
||||
} else {
|
||||
CantDecode = -2;
|
||||
}
|
||||
}
|
||||
return CantDecode;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#include <vector>
|
||||
typedef std::vector<bool> bit_vector;
|
||||
int bchDec(bit_vector& Codeword);
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
#include "crypto.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <boost/format.hpp>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
|
||||
extern "C" {
|
||||
#include "des.h"
|
||||
}
|
||||
|
||||
static unsigned long long swap_bytes(uint64_t l)
|
||||
{
|
||||
unsigned long long r;
|
||||
unsigned char* pL = (unsigned char*)&l;
|
||||
unsigned char* pR = (unsigned char*)&r;
|
||||
for (int i = 0; i < sizeof(l); ++i)
|
||||
pR[i] = pL[(sizeof(l) - 1) - i];
|
||||
return r;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
class null_algorithm : public crypto_algorithm // This is an algorithm skeleton (can be used for no encryption as pass-through)
|
||||
{
|
||||
private:
|
||||
size_t m_generated_bits;
|
||||
public:
|
||||
null_algorithm()
|
||||
: m_generated_bits(0)
|
||||
{
|
||||
}
|
||||
const type_id id() const
|
||||
{
|
||||
return crypto_algorithm::NONE;
|
||||
}
|
||||
bool update(const struct CryptoState& state)
|
||||
{
|
||||
fprintf(stderr, "NULL:\t%d bits generated\n", m_generated_bits);
|
||||
|
||||
m_generated_bits = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
bool set_key(const crypto_algorithm::key_type& key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
uint64_t generate(size_t n)
|
||||
{
|
||||
m_generated_bits += n;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class des_ofb : public crypto_algorithm
|
||||
{
|
||||
public:
|
||||
unsigned long long m_key_des, m_next_iv, m_ks;
|
||||
int m_ks_idx;
|
||||
DES_KS m_ksDES;
|
||||
int m_iterations;
|
||||
uint16_t m_current_kid;
|
||||
key_type m_default_key;
|
||||
key_map_type m_key_map;
|
||||
bool m_verbose;
|
||||
public:
|
||||
des_ofb()
|
||||
: m_current_kid(-1)
|
||||
{
|
||||
memset(&m_ksDES, 0, sizeof(m_ksDES));
|
||||
m_key_des = 0;
|
||||
m_next_iv = 0;
|
||||
m_ks_idx = 0;
|
||||
m_ks = 0;
|
||||
m_iterations = 0;
|
||||
}
|
||||
|
||||
void set_logging(bool on)
|
||||
{
|
||||
m_verbose = on;
|
||||
}
|
||||
|
||||
const type_id id() const
|
||||
{
|
||||
return crypto_algorithm::DES_OFB;
|
||||
}
|
||||
|
||||
bool update(const struct CryptoState& state)
|
||||
{
|
||||
if (m_current_kid != state.kid)
|
||||
{
|
||||
if (m_key_map.empty())
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
else
|
||||
{
|
||||
key_map_type::iterator it = m_key_map.find(state.kid);
|
||||
if (it != m_key_map.end())
|
||||
{
|
||||
set_key(it->second);
|
||||
}
|
||||
else if (!m_default_key.empty())
|
||||
{
|
||||
/*if (m_verbose) */fprintf(stderr, "Key 0x%04x not found in key map - using default key\n", state.kid);
|
||||
|
||||
set_key(m_default_key);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*if (m_verbose) */fprintf(stderr, "Key 0x%04x not found in key map and no default key\n", state.kid);
|
||||
}
|
||||
}
|
||||
|
||||
m_current_kid = state.kid;
|
||||
}
|
||||
|
||||
uint64_t iv = 0;
|
||||
size_t n = std::min(sizeof(iv), state.mi.size());
|
||||
memcpy(&iv, &state.mi[0], n);
|
||||
set_iv(iv);
|
||||
|
||||
return (n == 8);
|
||||
}
|
||||
|
||||
void set_key_map(const key_map_type& key_map)
|
||||
{
|
||||
m_key_map = key_map;
|
||||
|
||||
m_current_kid = -1; // To refresh on next update if it has changed
|
||||
}
|
||||
|
||||
bool set_key(const crypto_algorithm::key_type& key)
|
||||
{
|
||||
const size_t valid_key_length = 8;
|
||||
|
||||
if (key.size() != valid_key_length)
|
||||
{
|
||||
if (m_verbose) fprintf(stderr, "DES:\tIncorrect key length of %lu (should be %lu)\n", key.size(), valid_key_length);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_default_key = key;
|
||||
|
||||
memcpy(&m_key_des, &key[0], std::min(key.size(), sizeof(m_key_des)));
|
||||
|
||||
if (m_verbose)
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (int i = 0; i < valid_key_length; ++i)
|
||||
ss << boost::format("%02X") % (int)key[i];
|
||||
std::cerr << "DES:\tKey: " << ss.str() << std::endl;
|
||||
}
|
||||
|
||||
deskey(m_ksDES, (unsigned char*)&m_key_des, 0); // 0: encrypt (for OFB mode)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_iv(uint64_t iv)
|
||||
{
|
||||
if (m_iterations > 0)
|
||||
{
|
||||
if (m_verbose) fprintf(stderr, "DES:\t%i bits used from %i iterations\n", m_ks_idx, m_iterations);
|
||||
}
|
||||
|
||||
m_next_iv = iv;
|
||||
|
||||
m_ks_idx = 0;
|
||||
m_iterations = 0;
|
||||
|
||||
m_ks = m_next_iv;
|
||||
des(m_ksDES, (unsigned char*)&m_ks); // First initialisation
|
||||
++m_iterations;
|
||||
|
||||
des(m_ksDES, (unsigned char*)&m_ks); // Throw out first iteration & prepare for second
|
||||
++m_iterations;
|
||||
|
||||
generate(64); // Reserved 3 + first 5 of LC (3 left)
|
||||
generate(3 * 8); // Use remaining 3 bytes for LC
|
||||
}
|
||||
|
||||
uint64_t generate(size_t count) // 1..64
|
||||
{
|
||||
unsigned long long ullCurrent = swap_bytes(m_ks);
|
||||
const int max_len = 64;
|
||||
int pos = m_ks_idx % max_len;
|
||||
|
||||
m_ks_idx += count;
|
||||
|
||||
if ((pos + count) <= max_len) // Up to 64
|
||||
{
|
||||
if ((m_ks_idx % max_len) == 0)
|
||||
{
|
||||
des(m_ksDES, (unsigned char*)&m_ks); // Prepare for next iteration
|
||||
++m_iterations;
|
||||
}
|
||||
|
||||
unsigned long long result = (ullCurrent >> (((max_len - 1) - pos) - (count-1))) & ((count == max_len) ? (unsigned long long)-1 : ((1ULL << count) - 1));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Over-flow 64-bit boundary (so all of rest of current will be used)
|
||||
|
||||
des(m_ksDES, (unsigned char*)&m_ks); // Compute second part
|
||||
++m_iterations;
|
||||
|
||||
unsigned long long first = ullCurrent << pos; // RHS will be zeros
|
||||
|
||||
ullCurrent = swap_bytes(m_ks);
|
||||
int remainder = count - (max_len - pos);
|
||||
first >>= (((max_len - 1) - remainder) - ((max_len - 1) - pos));
|
||||
unsigned long long next = (ullCurrent >> (((max_len - 1) - 0) - (remainder-1))) & ((1ULL << remainder) - 1);
|
||||
|
||||
return (first | next);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
crypto_module::crypto_module(bool verbose/* = true*/)
|
||||
: d_verbose(verbose)
|
||||
{
|
||||
}
|
||||
|
||||
crypto_algorithm::sptr crypto_module::algorithm(crypto_algorithm::type_id algid)
|
||||
{
|
||||
if ((!d_current_algorithm && (algid == crypto_algorithm::NONE)) || // This line should be commented out if 'null_algorithm' is to be tested
|
||||
(d_current_algorithm && (algid == d_current_algorithm->id())))
|
||||
return d_current_algorithm;
|
||||
|
||||
switch (algid)
|
||||
{
|
||||
case crypto_algorithm::DES_OFB:
|
||||
d_current_algorithm = crypto_algorithm::sptr(new des_ofb());
|
||||
break;
|
||||
//case crypto_algorithm::NONE:
|
||||
// d_current_algorithm = crypto_algorithm::sptr(new null_algorithm());
|
||||
// break;
|
||||
default:
|
||||
d_current_algorithm = crypto_algorithm::sptr();
|
||||
};
|
||||
|
||||
if (d_current_algorithm)
|
||||
{
|
||||
d_current_algorithm->set_logging(logging_enabled());
|
||||
|
||||
if (!d_persistent_key_map.empty())
|
||||
d_current_algorithm->set_key_map(d_persistent_key_map);
|
||||
|
||||
if (!d_persistent_key.empty())
|
||||
d_current_algorithm->set_key(d_persistent_key);
|
||||
}
|
||||
|
||||
return d_current_algorithm;
|
||||
}
|
||||
|
||||
void crypto_module::set_key(const crypto_algorithm::key_type& key)
|
||||
{
|
||||
d_persistent_key = key;
|
||||
|
||||
if (d_current_algorithm)
|
||||
d_current_algorithm->set_key(d_persistent_key);
|
||||
}
|
||||
|
||||
void crypto_module::set_key_map(const crypto_algorithm::key_map_type& keys)
|
||||
{
|
||||
d_persistent_key_map = keys;
|
||||
|
||||
if (d_current_algorithm)
|
||||
d_current_algorithm->set_key_map(d_persistent_key_map);
|
||||
}
|
||||
|
||||
void crypto_module::set_logging(bool on/* = true*/)
|
||||
{
|
||||
d_verbose = on;
|
||||
|
||||
if (d_current_algorithm)
|
||||
d_current_algorithm->set_logging(on);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef INCLUDED_CRYPTO_H
|
||||
#define INCLUDED_CRYPTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
static const int MESSAGE_INDICATOR_LENGTH = 9;
|
||||
|
||||
class CryptoState
|
||||
{
|
||||
public:
|
||||
CryptoState() :
|
||||
mi(MESSAGE_INDICATOR_LENGTH), kid(0), algid(0)
|
||||
{ }
|
||||
public:
|
||||
std::vector<uint8_t> mi;
|
||||
uint16_t kid;
|
||||
uint8_t algid;
|
||||
};
|
||||
|
||||
class crypto_state_provider
|
||||
{
|
||||
public:
|
||||
virtual struct CryptoState crypto_state() const=0;
|
||||
};
|
||||
|
||||
class crypto_algorithm
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<class crypto_algorithm> sptr;
|
||||
typedef std::vector<uint8_t> key_type;
|
||||
typedef std::map<uint16_t, key_type > key_map_type;
|
||||
typedef uint8_t type_id;
|
||||
enum
|
||||
{
|
||||
NONE = 0x80,
|
||||
DES_OFB = 0x81,
|
||||
};
|
||||
public:
|
||||
virtual const type_id id() const=0;
|
||||
virtual bool set_key(const key_type& key)=0;
|
||||
virtual void set_key_map(const key_map_type& key_map)=0;
|
||||
virtual bool update(const struct CryptoState& state)=0;
|
||||
virtual uint64_t generate(size_t n_bits)=0; // Can request up to 64 bits of key stream at one time
|
||||
virtual void set_logging(bool on)=0;
|
||||
};
|
||||
|
||||
class crypto_module
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<class crypto_module> sptr;
|
||||
public:
|
||||
crypto_module(bool verbose = false);
|
||||
public:
|
||||
virtual crypto_algorithm::sptr algorithm(crypto_algorithm::type_id algid);
|
||||
virtual void set_key(const crypto_algorithm::key_type& key);
|
||||
virtual void set_key_map(const crypto_algorithm::key_map_type& keys);
|
||||
virtual void set_logging(bool on = true);
|
||||
protected:
|
||||
crypto_algorithm::sptr d_current_algorithm;
|
||||
crypto_algorithm::key_type d_persistent_key;
|
||||
crypto_algorithm::key_map_type d_persistent_key_map;
|
||||
bool d_verbose;
|
||||
public:
|
||||
virtual crypto_algorithm::sptr current_algorithm() const
|
||||
{ return d_current_algorithm; }
|
||||
virtual bool logging_enabled() const
|
||||
{ return d_verbose; }
|
||||
};
|
||||
|
||||
#endif // INCLUDED_CRYPTO_H
|
|
@ -0,0 +1,64 @@
|
|||
#include "crypto_module_du_handler.h"
|
||||
|
||||
#include "abstract_data_unit.h"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <sstream>
|
||||
#include <stdio.h>
|
||||
|
||||
crypto_module_du_handler::crypto_module_du_handler(data_unit_handler_sptr next, crypto_module::sptr crypto_mod)
|
||||
: data_unit_handler(next)
|
||||
, d_crypto_mod(crypto_mod)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
crypto_module_du_handler::handle(data_unit_sptr du)
|
||||
{
|
||||
if (!d_crypto_mod)
|
||||
{
|
||||
data_unit_handler::handle(du);
|
||||
return;
|
||||
}
|
||||
|
||||
crypto_state_provider* p = dynamic_cast<crypto_state_provider*>(du.get());
|
||||
if (p == NULL)
|
||||
{
|
||||
data_unit_handler::handle(du);
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoState state = p->crypto_state();
|
||||
|
||||
///////////////////////////////////
|
||||
|
||||
if (d_crypto_mod->logging_enabled())
|
||||
{
|
||||
std::string duid_str("?");
|
||||
abstract_data_unit* adu = dynamic_cast<abstract_data_unit*>(du.get());
|
||||
if (adu)
|
||||
duid_str = adu->duid_str();
|
||||
|
||||
std::stringstream ss;
|
||||
for (size_t n = 0; n < state.mi.size(); ++n)
|
||||
ss << (boost::format("%02x") % (int)state.mi[n]);
|
||||
|
||||
fprintf(stderr, "%s:\tAlgID: 0x%02x, KID: 0x%04x, MI: %s\n", duid_str.c_str(), state.algid, state.kid, ss.str().c_str());
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
|
||||
crypto_algorithm::sptr algorithm = d_crypto_mod->algorithm(state.algid);
|
||||
if (!algorithm)
|
||||
{
|
||||
data_unit_handler::handle(du);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Could do key management & selection here with 'state.kid'
|
||||
// Assuming we're only using one key (ignoring 'kid')
|
||||
|
||||
algorithm->update(state);
|
||||
|
||||
data_unit_handler::handle(du);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef INCLUDED_CRYPTO_MODULE_DU_HANDLER_H
|
||||
#define INCLUDED_CRYPTO_MODULE_DU_HANDLER_H
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "data_unit_handler.h"
|
||||
#include "crypto.h"
|
||||
|
||||
class crypto_module_du_handler : public data_unit_handler
|
||||
{
|
||||
public:
|
||||
crypto_module_du_handler(data_unit_handler_sptr next, crypto_module::sptr crypto_mod);
|
||||
public:
|
||||
typedef boost::shared_ptr<class crypto_module_du_handler> sptr;
|
||||
public:
|
||||
virtual void handle(data_unit_sptr du);
|
||||
private:
|
||||
crypto_module::sptr d_crypto_mod;
|
||||
};
|
||||
|
||||
#endif //INCLUDED_CRYPTO_MODULE_HANDLER_H
|
|
@ -32,6 +32,8 @@
|
|||
#include <iosfwd>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "crypto.h"
|
||||
|
||||
typedef std::deque<bool> bit_queue;
|
||||
typedef const std::deque<bool> const_bit_queue;
|
||||
|
||||
|
@ -76,7 +78,7 @@ public:
|
|||
* \precondition is_complete() == true.
|
||||
* \param imbe The imbe_decoder to use to generate the audio.
|
||||
*/
|
||||
virtual void decode_audio(imbe_decoder& imbe) = 0;
|
||||
virtual void decode_audio(imbe_decoder& imbe, crypto_module::sptr crypto_mod) = 0;
|
||||
|
||||
/**
|
||||
* Decode the frame into an octet vector.
|
||||
|
@ -132,6 +134,8 @@ public:
|
|||
*/
|
||||
virtual std::string snapshot() const = 0;
|
||||
|
||||
virtual void set_logging(bool on) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "offline_imbe_decoder.h"
|
||||
#include "voice_du_handler.h"
|
||||
#include "op25_yank.h"
|
||||
#include "bch.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -41,13 +42,13 @@ namespace gr {
|
|||
namespace op25 {
|
||||
|
||||
decoder_bf::sptr
|
||||
decoder_bf::make()
|
||||
decoder_bf::make(bool idle_silence /*= true*/, bool verbose /*= false*/)
|
||||
{
|
||||
return gnuradio::get_initial_sptr
|
||||
(new decoder_bf_impl());
|
||||
(new decoder_bf_impl(idle_silence, verbose));
|
||||
}
|
||||
|
||||
decoder_bf_impl::decoder_bf_impl() :
|
||||
decoder_bf_impl::decoder_bf_impl(bool idle_silence /*= true*/, bool verbose /*= false*/) :
|
||||
gr::block("decoder_bf",
|
||||
gr::io_signature::make(1, 1, sizeof(uint8_t)),
|
||||
gr::io_signature::make(0, 1, sizeof(float))),
|
||||
|
@ -56,14 +57,25 @@ namespace gr {
|
|||
d_frame_hdr(),
|
||||
d_imbe(imbe_decoder::make()),
|
||||
d_state(SYNCHRONIZING),
|
||||
d_p25cai_du_handler(NULL)
|
||||
d_p25cai_du_handler(NULL),
|
||||
d_idle_silence(idle_silence),
|
||||
d_verbose(false)
|
||||
{
|
||||
set_logging(verbose);
|
||||
|
||||
d_p25cai_du_handler = new p25cai_du_handler(d_data_unit_handler,
|
||||
"224.0.0.1", 23456);
|
||||
d_data_unit_handler = data_unit_handler_sptr(d_p25cai_du_handler);
|
||||
|
||||
d_snapshot_du_handler = new snapshot_du_handler(d_data_unit_handler);
|
||||
d_data_unit_handler = data_unit_handler_sptr(d_snapshot_du_handler);
|
||||
d_data_unit_handler = data_unit_handler_sptr(new voice_du_handler(d_data_unit_handler, d_imbe));
|
||||
|
||||
d_crypto_module = crypto_module::sptr(new crypto_module(verbose));
|
||||
|
||||
d_crypto_module_du_handler = crypto_module_du_handler::sptr(new crypto_module_du_handler(d_data_unit_handler, d_crypto_module));
|
||||
d_data_unit_handler = data_unit_handler_sptr(d_crypto_module_du_handler);
|
||||
|
||||
d_data_unit_handler = data_unit_handler_sptr(new voice_du_handler(d_data_unit_handler, d_imbe, d_crypto_module));
|
||||
}
|
||||
|
||||
decoder_bf_impl::~decoder_bf_impl()
|
||||
|
@ -104,34 +116,35 @@ namespace gr {
|
|||
gr_vector_void_star &output_items)
|
||||
{
|
||||
try {
|
||||
gr::thread::scoped_lock lock(d_mutex);
|
||||
|
||||
// process input
|
||||
const uint8_t *in = reinterpret_cast<const uint8_t*>(input_items[0]);
|
||||
for(int i = 0; i < ninput_items[0]; ++i) {
|
||||
dibit d = in[i] & 0x3;
|
||||
receive_symbol(d);
|
||||
}
|
||||
consume_each(ninput_items[0]);
|
||||
// process input
|
||||
const uint8_t *in = reinterpret_cast<const uint8_t*>(input_items[0]);
|
||||
for(int i = 0; i < ninput_items[0]; ++i) {
|
||||
dibit d = in[i] & 0x3;
|
||||
receive_symbol(d);
|
||||
}
|
||||
consume_each(ninput_items[0]);
|
||||
|
||||
// produce audio
|
||||
audio_samples *samples = d_imbe->audio();
|
||||
float *out = reinterpret_cast<float*>(output_items[0]);
|
||||
const int n = min(static_cast<int>(samples->size()), noutput_items);
|
||||
if(0 < n) {
|
||||
copy(samples->begin(), samples->begin() + n, out);
|
||||
samples->erase(samples->begin(), samples->begin() + n);
|
||||
}
|
||||
if(n < noutput_items) {
|
||||
fill(out + n, out + noutput_items, 0.0);
|
||||
}
|
||||
return noutput_items;
|
||||
// produce audio
|
||||
audio_samples *samples = d_imbe->audio();
|
||||
float *out = reinterpret_cast<float*>(output_items[0]);
|
||||
const int n = min(static_cast<int>(samples->size()), noutput_items);
|
||||
if(0 < n) {
|
||||
copy(samples->begin(), samples->begin() + n, out);
|
||||
samples->erase(samples->begin(), samples->begin() + n);
|
||||
}
|
||||
if((d_idle_silence) && (n < noutput_items)) {
|
||||
fill(out + n, out + noutput_items, 0.0);
|
||||
}
|
||||
return (d_idle_silence ? noutput_items : n);
|
||||
|
||||
} catch(const std::exception& x) {
|
||||
cerr << x.what() << endl;
|
||||
exit(1);
|
||||
cerr << x.what() << endl;
|
||||
exit(1);
|
||||
} catch(...) {
|
||||
cerr << "unhandled exception" << endl;
|
||||
exit(2); }
|
||||
cerr << "unhandled exception" << endl;
|
||||
exit(2); }
|
||||
}
|
||||
|
||||
const char*
|
||||
|
@ -177,14 +190,12 @@ namespace gr {
|
|||
};
|
||||
size_t NID_SZ = sizeof(NID) / sizeof(NID[0]);
|
||||
|
||||
itpp::bvec b(63), zeroes(16);
|
||||
itpp::BCH bch(63, 16, 11, "6 3 3 1 1 4 1 3 6 7 2 3 5 4 5 3", true);
|
||||
bit_vector b(NID_SZ);
|
||||
yank(d_frame_hdr, NID, NID_SZ, b, 0);
|
||||
b = bch.decode(b);
|
||||
if(b != zeroes) {
|
||||
b = bch.encode(b);
|
||||
if(bchDec(b) >= 0) {
|
||||
yank_back(b, 0, d_frame_hdr, NID, NID_SZ);
|
||||
d_data_unit = data_unit::make_data_unit(d_frame_hdr);
|
||||
d_data_unit->set_logging(d_verbose);
|
||||
} else {
|
||||
data_unit_sptr null;
|
||||
d_data_unit = null;
|
||||
|
@ -229,5 +240,36 @@ namespace gr {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
decoder_bf_impl::set_idle_silence(bool idle_silence/* = true*/)
|
||||
{
|
||||
gr::thread::scoped_lock lock(d_mutex);
|
||||
|
||||
d_idle_silence = idle_silence;
|
||||
}
|
||||
|
||||
void
|
||||
decoder_bf_impl::set_logging(bool verbose/* = true*/)
|
||||
{
|
||||
if (verbose) fprintf(stderr, "[%s<%lu>] verbose logging enabled\n", name().c_str(), unique_id());
|
||||
|
||||
d_verbose = verbose;
|
||||
|
||||
if (d_crypto_module)
|
||||
d_crypto_module->set_logging(verbose);
|
||||
}
|
||||
|
||||
void
|
||||
decoder_bf_impl::set_key(const crypto_algorithm::key_type& key)
|
||||
{
|
||||
d_crypto_module->set_key(key);
|
||||
}
|
||||
|
||||
void
|
||||
decoder_bf_impl::set_key_map(const crypto_algorithm::key_map_type& keys)
|
||||
{
|
||||
d_crypto_module->set_key_map(keys);
|
||||
}
|
||||
} /* namespace op25 */
|
||||
} /* namespace gr */
|
||||
|
|
|
@ -24,11 +24,14 @@
|
|||
#define INCLUDED_OP25_DECODER_BF_IMPL_H
|
||||
|
||||
#include <op25/decoder_bf.h>
|
||||
#include <gnuradio/thread/thread.h>
|
||||
#include "data_unit.h"
|
||||
#include "data_unit_handler.h"
|
||||
#include "imbe_decoder.h"
|
||||
#include "p25cai_du_handler.h"
|
||||
#include "snapshot_du_handler.h"
|
||||
#include "crypto.h"
|
||||
#include "crypto_module_du_handler.h"
|
||||
|
||||
namespace gr {
|
||||
namespace op25 {
|
||||
|
@ -102,8 +105,21 @@ namespace gr {
|
|||
*/
|
||||
class snapshot_du_handler *d_snapshot_du_handler;
|
||||
|
||||
/*
|
||||
* Whether or not to output silence when no audio is synthesised.
|
||||
*/
|
||||
bool d_idle_silence;
|
||||
|
||||
bool d_verbose;
|
||||
|
||||
crypto_module::sptr d_crypto_module;
|
||||
|
||||
crypto_module_du_handler::sptr d_crypto_module_du_handler;
|
||||
|
||||
gr::thread::mutex d_mutex;
|
||||
|
||||
public:
|
||||
decoder_bf_impl();
|
||||
decoder_bf_impl(bool idle_silence = true, bool verbose = false);
|
||||
~decoder_bf_impl();
|
||||
|
||||
// Where all the action really happens
|
||||
|
@ -139,6 +155,14 @@ namespace gr {
|
|||
* message queue.
|
||||
*/
|
||||
void set_msgq(gr::msg_queue::sptr msgq);
|
||||
|
||||
void set_idle_silence(bool idle_silence = true);
|
||||
|
||||
void set_logging(bool verbose = true);
|
||||
|
||||
void set_key(const crypto_algorithm::key_type& key);
|
||||
|
||||
void set_key_map(const crypto_algorithm::key_map_type& keys);
|
||||
};
|
||||
} // namespace op25
|
||||
} // namespace gr
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "offline_imbe_decoder.h"
|
||||
#include "voice_du_handler.h"
|
||||
#include "op25_yank.h"
|
||||
#include "bch.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -185,12 +186,9 @@ namespace gr {
|
|||
};
|
||||
size_t NID_SZ = sizeof(NID) / sizeof(NID[0]);
|
||||
|
||||
itpp::bvec b(63), zeroes(16);
|
||||
itpp::BCH bch(63, 16, 11, "6 3 3 1 1 4 1 3 6 7 2 3 5 4 5 3", true);
|
||||
bit_vector b(NID_SZ);
|
||||
yank(d_frame_hdr, NID, NID_SZ, b, 0);
|
||||
b = bch.decode(b);
|
||||
if(b != zeroes) {
|
||||
b = bch.encode(b);
|
||||
if(bchDec(b) >= 0) {
|
||||
yank_back(b, 0, d_frame_hdr, NID, NID_SZ);
|
||||
d_data_unit = data_unit::make_data_unit(d_frame_hdr);
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
typedef unsigned long DES_KS[16][2]; /* Single-key DES key schedule */
|
||||
typedef unsigned long DES3_KS[48][2]; /* Triple-DES key schedule */
|
||||
|
||||
/* In deskey.c: */
|
||||
void deskey(DES_KS,unsigned char *,int);
|
||||
void des3key(DES3_KS,unsigned char *,int);
|
||||
|
||||
/* In desport.c, desborl.cas or desgnu.s: */
|
||||
void des(DES_KS,unsigned char *);
|
||||
/* In des3port.c, des3borl.cas or des3gnu.s: */
|
||||
void des3(DES3_KS,unsigned char *);
|
||||
|
||||
extern int Asmversion; /* 1 if we're linked with an asm version, 0 if C */
|
||||
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/* Portable C code to create DES key schedules from user-provided keys
|
||||
* This doesn't have to be fast unless you're cracking keys or UNIX
|
||||
* passwords
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "des.h"
|
||||
|
||||
/* Key schedule-related tables from FIPS-46 */
|
||||
|
||||
/* permuted choice table (key) */
|
||||
static unsigned char pc1[] = {
|
||||
57, 49, 41, 33, 25, 17, 9,
|
||||
1, 58, 50, 42, 34, 26, 18,
|
||||
10, 2, 59, 51, 43, 35, 27,
|
||||
19, 11, 3, 60, 52, 44, 36,
|
||||
|
||||
63, 55, 47, 39, 31, 23, 15,
|
||||
7, 62, 54, 46, 38, 30, 22,
|
||||
14, 6, 61, 53, 45, 37, 29,
|
||||
21, 13, 5, 28, 20, 12, 4
|
||||
};
|
||||
|
||||
/* number left rotations of pc1 */
|
||||
static unsigned char totrot[] = {
|
||||
1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28
|
||||
};
|
||||
|
||||
/* permuted choice key (table) */
|
||||
static unsigned char pc2[] = {
|
||||
14, 17, 11, 24, 1, 5,
|
||||
3, 28, 15, 6, 21, 10,
|
||||
23, 19, 12, 4, 26, 8,
|
||||
16, 7, 27, 20, 13, 2,
|
||||
41, 52, 31, 37, 47, 55,
|
||||
30, 40, 51, 45, 33, 48,
|
||||
44, 49, 39, 56, 34, 53,
|
||||
46, 42, 50, 36, 29, 32
|
||||
};
|
||||
|
||||
/* End of DES-defined tables */
|
||||
|
||||
|
||||
/* bit 0 is left-most in byte */
|
||||
static int bytebit[] = {
|
||||
0200,0100,040,020,010,04,02,01
|
||||
};
|
||||
|
||||
|
||||
/* Generate key schedule for encryption or decryption
|
||||
* depending on the value of "decrypt"
|
||||
*/
|
||||
void
|
||||
deskey(DES_KS k,unsigned char *key,int decrypt)
|
||||
/* Key schedule array */
|
||||
/* 64 bits (will use only 56) */
|
||||
/* 0 = encrypt, 1 = decrypt */
|
||||
{
|
||||
unsigned char pc1m[56]; /* place to modify pc1 into */
|
||||
unsigned char pcr[56]; /* place to rotate pc1 into */
|
||||
register int i,j,l;
|
||||
int m;
|
||||
unsigned char ks[8];
|
||||
|
||||
for (j=0; j<56; j++) { /* convert pc1 to bits of key */
|
||||
l=pc1[j]-1; /* integer bit location */
|
||||
m = l & 07; /* find bit */
|
||||
pc1m[j]=(key[l>>3] & /* find which key byte l is in */
|
||||
bytebit[m]) /* and which bit of that byte */
|
||||
? 1 : 0; /* and store 1-bit result */
|
||||
}
|
||||
for (i=0; i<16; i++) { /* key chunk for each iteration */
|
||||
memset(ks,0,sizeof(ks)); /* Clear key schedule */
|
||||
for (j=0; j<56; j++) /* rotate pc1 the right amount */
|
||||
pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28];
|
||||
/* rotate left and right halves independently */
|
||||
for (j=0; j<48; j++){ /* select bits individually */
|
||||
/* check bit that goes to ks[j] */
|
||||
if (pcr[pc2[j]-1]){
|
||||
/* mask it in if it's there */
|
||||
l= j % 6;
|
||||
ks[j/6] |= bytebit[l] >> 2;
|
||||
}
|
||||
}
|
||||
/* Now convert to packed odd/even interleaved form */
|
||||
k[i][0] = ((long)ks[0] << 24)
|
||||
| ((long)ks[2] << 16)
|
||||
| ((long)ks[4] << 8)
|
||||
| ((long)ks[6]);
|
||||
k[i][1] = ((long)ks[1] << 24)
|
||||
| ((long)ks[3] << 16)
|
||||
| ((long)ks[5] << 8)
|
||||
| ((long)ks[7]);
|
||||
if(Asmversion){
|
||||
/* The assembler versions pre-shift each subkey 2 bits
|
||||
* so the Spbox indexes are already computed
|
||||
*/
|
||||
k[i][0] <<= 2;
|
||||
k[i][1] <<= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate key schedule for triple DES in E-D-E (or D-E-D) mode.
|
||||
*
|
||||
* The key argument is taken to be 24 bytes. The first 8 bytes are K1
|
||||
* for the first stage, the second 8 bytes are K2 for the middle stage
|
||||
* and the third 8 bytes are K3 for the last stage
|
||||
*/
|
||||
void
|
||||
des3key(DES3_KS k,unsigned char *key,int decrypt)
|
||||
/* 192 bits (will use only 168) */
|
||||
/* 0 = encrypt, 1 = decrypt */
|
||||
{
|
||||
if(!decrypt){
|
||||
deskey(&k[0],&key[0],0);
|
||||
deskey(&k[16],&key[8],1);
|
||||
deskey(&k[32],&key[16],0);
|
||||
} else {
|
||||
deskey(&k[32],&key[0],1);
|
||||
deskey(&k[16],&key[8],0);
|
||||
deskey(&k[0],&key[16],1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/* Portable C version of des() function */
|
||||
|
||||
#include <stdint.h>
|
||||
#include "des.h"
|
||||
|
||||
/* Tables defined in the Data Encryption Standard documents
|
||||
* Three of these tables, the initial permutation, the final
|
||||
* permutation and the expansion operator, are regular enough that
|
||||
* for speed, we hard-code them. They're here for reference only.
|
||||
* Also, the S and P boxes are used by a separate program, gensp.c,
|
||||
* to build the combined SP box, Spbox[]. They're also here just
|
||||
* for reference.
|
||||
*/
|
||||
#ifdef notdef
|
||||
/* initial permutation IP */
|
||||
static unsigned char ip[] = {
|
||||
58, 50, 42, 34, 26, 18, 10, 2,
|
||||
60, 52, 44, 36, 28, 20, 12, 4,
|
||||
62, 54, 46, 38, 30, 22, 14, 6,
|
||||
64, 56, 48, 40, 32, 24, 16, 8,
|
||||
57, 49, 41, 33, 25, 17, 9, 1,
|
||||
59, 51, 43, 35, 27, 19, 11, 3,
|
||||
61, 53, 45, 37, 29, 21, 13, 5,
|
||||
63, 55, 47, 39, 31, 23, 15, 7
|
||||
};
|
||||
|
||||
/* final permutation IP^-1 */
|
||||
static unsigned char fp[] = {
|
||||
40, 8, 48, 16, 56, 24, 64, 32,
|
||||
39, 7, 47, 15, 55, 23, 63, 31,
|
||||
38, 6, 46, 14, 54, 22, 62, 30,
|
||||
37, 5, 45, 13, 53, 21, 61, 29,
|
||||
36, 4, 44, 12, 52, 20, 60, 28,
|
||||
35, 3, 43, 11, 51, 19, 59, 27,
|
||||
34, 2, 42, 10, 50, 18, 58, 26,
|
||||
33, 1, 41, 9, 49, 17, 57, 25
|
||||
};
|
||||
/* expansion operation matrix */
|
||||
static unsigned char ei[] = {
|
||||
32, 1, 2, 3, 4, 5,
|
||||
4, 5, 6, 7, 8, 9,
|
||||
8, 9, 10, 11, 12, 13,
|
||||
12, 13, 14, 15, 16, 17,
|
||||
16, 17, 18, 19, 20, 21,
|
||||
20, 21, 22, 23, 24, 25,
|
||||
24, 25, 26, 27, 28, 29,
|
||||
28, 29, 30, 31, 32, 1
|
||||
};
|
||||
/* The (in)famous S-boxes */
|
||||
static unsigned char sbox[8][64] = {
|
||||
/* S1 */
|
||||
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13,
|
||||
|
||||
/* S2 */
|
||||
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9,
|
||||
|
||||
/* S3 */
|
||||
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12,
|
||||
|
||||
/* S4 */
|
||||
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14,
|
||||
|
||||
/* S5 */
|
||||
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3,
|
||||
|
||||
/* S6 */
|
||||
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13,
|
||||
|
||||
/* S7 */
|
||||
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12,
|
||||
|
||||
/* S8 */
|
||||
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
|
||||
};
|
||||
|
||||
/* 32-bit permutation function P used on the output of the S-boxes */
|
||||
static unsigned char p32i[] = {
|
||||
16, 7, 20, 21,
|
||||
29, 12, 28, 17,
|
||||
1, 15, 23, 26,
|
||||
5, 18, 31, 10,
|
||||
2, 8, 24, 14,
|
||||
32, 27, 3, 9,
|
||||
19, 13, 30, 6,
|
||||
22, 11, 4, 25
|
||||
};
|
||||
#endif
|
||||
|
||||
int Asmversion = 0;
|
||||
|
||||
/* Combined SP lookup table, linked in
|
||||
* For best results, ensure that this is aligned on a 32-bit boundary;
|
||||
* Borland C++ 3.1 doesn't guarantee this!
|
||||
*/
|
||||
extern uint64_t Spbox[8][64]; /* Combined S and P boxes */
|
||||
|
||||
/* Primitive function F.
|
||||
* Input is r, subkey array in keys, output is XORed into l.
|
||||
* Each round consumes eight 6-bit subkeys, one for
|
||||
* each of the 8 S-boxes, 2 longs for each round.
|
||||
* Each long contains four 6-bit subkeys, each taking up a byte.
|
||||
* The first long contains, from high to low end, the subkeys for
|
||||
* S-boxes 1, 3, 5 & 7; the second contains the subkeys for S-boxes
|
||||
* 2, 4, 6 & 8 (using the origin-1 S-box numbering in the standard,
|
||||
* not the origin-0 numbering used elsewhere in this code)
|
||||
* See comments elsewhere about the pre-rotated values of r and Spbox.
|
||||
*/
|
||||
#define F(l,r,key){\
|
||||
work = ((r >> 4) | (r << 28)) ^ key[0];\
|
||||
l ^= Spbox[6][work & 0x3f];\
|
||||
l ^= Spbox[4][(work >> 8) & 0x3f];\
|
||||
l ^= Spbox[2][(work >> 16) & 0x3f];\
|
||||
l ^= Spbox[0][(work >> 24) & 0x3f];\
|
||||
work = r ^ key[1];\
|
||||
l ^= Spbox[7][work & 0x3f];\
|
||||
l ^= Spbox[5][(work >> 8) & 0x3f];\
|
||||
l ^= Spbox[3][(work >> 16) & 0x3f];\
|
||||
l ^= Spbox[1][(work >> 24) & 0x3f];\
|
||||
}
|
||||
/* Encrypt or decrypt a block of data in ECB mode */
|
||||
void
|
||||
des(unsigned long ks[16][2],unsigned char block[8])
|
||||
/* Key schedule */
|
||||
/* Data block */
|
||||
{
|
||||
unsigned long left,right,work;
|
||||
|
||||
/* Read input block and place in left/right in big-endian order */
|
||||
left = ((unsigned long)block[0] << 24)
|
||||
| ((unsigned long)block[1] << 16)
|
||||
| ((unsigned long)block[2] << 8)
|
||||
| (unsigned long)block[3];
|
||||
right = ((unsigned long)block[4] << 24)
|
||||
| ((unsigned long)block[5] << 16)
|
||||
| ((unsigned long)block[6] << 8)
|
||||
| (unsigned long)block[7];
|
||||
|
||||
/* Hoey's clever initial permutation algorithm, from Outerbridge
|
||||
* (see Schneier p 478)
|
||||
*
|
||||
* The convention here is the same as Outerbridge: rotate each
|
||||
* register left by 1 bit, i.e., so that "left" contains permuted
|
||||
* input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32
|
||||
* (using origin-1 numbering as in the FIPS). This allows us to avoid
|
||||
* one of the two rotates that would otherwise be required in each of
|
||||
* the 16 rounds.
|
||||
*/
|
||||
work = ((left >> 4) ^ right) & 0x0f0f0f0f;
|
||||
right ^= work;
|
||||
left ^= work << 4;
|
||||
work = ((left >> 16) ^ right) & 0xffff;
|
||||
right ^= work;
|
||||
left ^= work << 16;
|
||||
work = ((right >> 2) ^ left) & 0x33333333;
|
||||
left ^= work;
|
||||
right ^= (work << 2);
|
||||
work = ((right >> 8) ^ left) & 0xff00ff;
|
||||
left ^= work;
|
||||
right ^= (work << 8);
|
||||
right = (right << 1) | (right >> 31);
|
||||
work = (left ^ right) & 0xaaaaaaaa;
|
||||
left ^= work;
|
||||
right ^= work;
|
||||
left = (left << 1) | (left >> 31);
|
||||
|
||||
/* Now do the 16 rounds */
|
||||
F(left,right,ks[0]);
|
||||
F(right,left,ks[1]);
|
||||
F(left,right,ks[2]);
|
||||
F(right,left,ks[3]);
|
||||
F(left,right,ks[4]);
|
||||
F(right,left,ks[5]);
|
||||
F(left,right,ks[6]);
|
||||
F(right,left,ks[7]);
|
||||
F(left,right,ks[8]);
|
||||
F(right,left,ks[9]);
|
||||
F(left,right,ks[10]);
|
||||
F(right,left,ks[11]);
|
||||
F(left,right,ks[12]);
|
||||
F(right,left,ks[13]);
|
||||
F(left,right,ks[14]);
|
||||
F(right,left,ks[15]);
|
||||
|
||||
/* Inverse permutation, also from Hoey via Outerbridge and Schneier */
|
||||
right = (right << 31) | (right >> 1);
|
||||
work = (left ^ right) & 0xaaaaaaaa;
|
||||
left ^= work;
|
||||
right ^= work;
|
||||
left = (left >> 1) | (left << 31);
|
||||
work = ((left >> 8) ^ right) & 0xff00ff;
|
||||
right ^= work;
|
||||
left ^= work << 8;
|
||||
work = ((left >> 2) ^ right) & 0x33333333;
|
||||
right ^= work;
|
||||
left ^= work << 2;
|
||||
work = ((right >> 16) ^ left) & 0xffff;
|
||||
left ^= work;
|
||||
right ^= work << 16;
|
||||
work = ((right >> 4) ^ left) & 0x0f0f0f0f;
|
||||
left ^= work;
|
||||
right ^= work << 4;
|
||||
|
||||
/* Put the block back into the user's buffer with final swap */
|
||||
block[0] = right >> 24;
|
||||
block[1] = right >> 16;
|
||||
block[2] = right >> 8;
|
||||
block[3] = right;
|
||||
block[4] = left >> 24;
|
||||
block[5] = left >> 16;
|
||||
block[6] = left >> 8;
|
||||
block[7] = left;
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
#include <stdint.h>
|
||||
uint64_t Spbox[8][64] = {
|
||||
0x01010400,0x00000000,0x00010000,0x01010404,
|
||||
0x01010004,0x00010404,0x00000004,0x00010000,
|
||||
0x00000400,0x01010400,0x01010404,0x00000400,
|
||||
0x01000404,0x01010004,0x01000000,0x00000004,
|
||||
0x00000404,0x01000400,0x01000400,0x00010400,
|
||||
0x00010400,0x01010000,0x01010000,0x01000404,
|
||||
0x00010004,0x01000004,0x01000004,0x00010004,
|
||||
0x00000000,0x00000404,0x00010404,0x01000000,
|
||||
0x00010000,0x01010404,0x00000004,0x01010000,
|
||||
0x01010400,0x01000000,0x01000000,0x00000400,
|
||||
0x01010004,0x00010000,0x00010400,0x01000004,
|
||||
0x00000400,0x00000004,0x01000404,0x00010404,
|
||||
0x01010404,0x00010004,0x01010000,0x01000404,
|
||||
0x01000004,0x00000404,0x00010404,0x01010400,
|
||||
0x00000404,0x01000400,0x01000400,0x00000000,
|
||||
0x00010004,0x00010400,0x00000000,0x01010004,
|
||||
0x80108020,0x80008000,0x00008000,0x00108020,
|
||||
0x00100000,0x00000020,0x80100020,0x80008020,
|
||||
0x80000020,0x80108020,0x80108000,0x80000000,
|
||||
0x80008000,0x00100000,0x00000020,0x80100020,
|
||||
0x00108000,0x00100020,0x80008020,0x00000000,
|
||||
0x80000000,0x00008000,0x00108020,0x80100000,
|
||||
0x00100020,0x80000020,0x00000000,0x00108000,
|
||||
0x00008020,0x80108000,0x80100000,0x00008020,
|
||||
0x00000000,0x00108020,0x80100020,0x00100000,
|
||||
0x80008020,0x80100000,0x80108000,0x00008000,
|
||||
0x80100000,0x80008000,0x00000020,0x80108020,
|
||||
0x00108020,0x00000020,0x00008000,0x80000000,
|
||||
0x00008020,0x80108000,0x00100000,0x80000020,
|
||||
0x00100020,0x80008020,0x80000020,0x00100020,
|
||||
0x00108000,0x00000000,0x80008000,0x00008020,
|
||||
0x80000000,0x80100020,0x80108020,0x00108000,
|
||||
0x00000208,0x08020200,0x00000000,0x08020008,
|
||||
0x08000200,0x00000000,0x00020208,0x08000200,
|
||||
0x00020008,0x08000008,0x08000008,0x00020000,
|
||||
0x08020208,0x00020008,0x08020000,0x00000208,
|
||||
0x08000000,0x00000008,0x08020200,0x00000200,
|
||||
0x00020200,0x08020000,0x08020008,0x00020208,
|
||||
0x08000208,0x00020200,0x00020000,0x08000208,
|
||||
0x00000008,0x08020208,0x00000200,0x08000000,
|
||||
0x08020200,0x08000000,0x00020008,0x00000208,
|
||||
0x00020000,0x08020200,0x08000200,0x00000000,
|
||||
0x00000200,0x00020008,0x08020208,0x08000200,
|
||||
0x08000008,0x00000200,0x00000000,0x08020008,
|
||||
0x08000208,0x00020000,0x08000000,0x08020208,
|
||||
0x00000008,0x00020208,0x00020200,0x08000008,
|
||||
0x08020000,0x08000208,0x00000208,0x08020000,
|
||||
0x00020208,0x00000008,0x08020008,0x00020200,
|
||||
0x100802001,0x100002081,0x100002081,0x00000080,
|
||||
0x00802080,0x100800081,0x100800001,0x100002001,
|
||||
0x00000000,0x00802000,0x00802000,0x100802081,
|
||||
0x100000081,0x00000000,0x00800080,0x100800001,
|
||||
0x100000001,0x00002000,0x00800000,0x100802001,
|
||||
0x00000080,0x00800000,0x100002001,0x00002080,
|
||||
0x100800081,0x100000001,0x00002080,0x00800080,
|
||||
0x00002000,0x00802080,0x100802081,0x100000081,
|
||||
0x00800080,0x100800001,0x00802000,0x100802081,
|
||||
0x100000081,0x00000000,0x00000000,0x00802000,
|
||||
0x00002080,0x00800080,0x100800081,0x100000001,
|
||||
0x100802001,0x100002081,0x100002081,0x00000080,
|
||||
0x100802081,0x100000081,0x100000001,0x00002000,
|
||||
0x100800001,0x100002001,0x00802080,0x100800081,
|
||||
0x100002001,0x00002080,0x00800000,0x100802001,
|
||||
0x00000080,0x00800000,0x00002000,0x00802080,
|
||||
0x00000100,0x02080100,0x02080000,0x42000100,
|
||||
0x00080000,0x00000100,0x40000000,0x02080000,
|
||||
0x40080100,0x00080000,0x02000100,0x40080100,
|
||||
0x42000100,0x42080000,0x00080100,0x40000000,
|
||||
0x02000000,0x40080000,0x40080000,0x00000000,
|
||||
0x40000100,0x42080100,0x42080100,0x02000100,
|
||||
0x42080000,0x40000100,0x00000000,0x42000000,
|
||||
0x02080100,0x02000000,0x42000000,0x00080100,
|
||||
0x00080000,0x42000100,0x00000100,0x02000000,
|
||||
0x40000000,0x02080000,0x42000100,0x40080100,
|
||||
0x02000100,0x40000000,0x42080000,0x02080100,
|
||||
0x40080100,0x00000100,0x02000000,0x42080000,
|
||||
0x42080100,0x00080100,0x42000000,0x42080100,
|
||||
0x02080000,0x00000000,0x40080000,0x42000000,
|
||||
0x00080100,0x02000100,0x40000100,0x00080000,
|
||||
0x00000000,0x40080000,0x02080100,0x40000100,
|
||||
0x20000010,0x20400000,0x00004000,0x20404010,
|
||||
0x20400000,0x00000010,0x20404010,0x00400000,
|
||||
0x20004000,0x00404010,0x00400000,0x20000010,
|
||||
0x00400010,0x20004000,0x20000000,0x00004010,
|
||||
0x00000000,0x00400010,0x20004010,0x00004000,
|
||||
0x00404000,0x20004010,0x00000010,0x20400010,
|
||||
0x20400010,0x00000000,0x00404010,0x20404000,
|
||||
0x00004010,0x00404000,0x20404000,0x20000000,
|
||||
0x20004000,0x00000010,0x20400010,0x00404000,
|
||||
0x20404010,0x00400000,0x00004010,0x20000010,
|
||||
0x00400000,0x20004000,0x20000000,0x00004010,
|
||||
0x20000010,0x20404010,0x00404000,0x20400000,
|
||||
0x00404010,0x20404000,0x00000000,0x20400010,
|
||||
0x00000010,0x00004000,0x20400000,0x00404010,
|
||||
0x00004000,0x00400010,0x20004010,0x00000000,
|
||||
0x20404000,0x20000000,0x00400010,0x20004010,
|
||||
0x00200000,0x04200002,0x04000802,0x00000000,
|
||||
0x00000800,0x04000802,0x00200802,0x04200800,
|
||||
0x04200802,0x00200000,0x00000000,0x04000002,
|
||||
0x00000002,0x04000000,0x04200002,0x00000802,
|
||||
0x04000800,0x00200802,0x00200002,0x04000800,
|
||||
0x04000002,0x04200000,0x04200800,0x00200002,
|
||||
0x04200000,0x00000800,0x00000802,0x04200802,
|
||||
0x00200800,0x00000002,0x04000000,0x00200800,
|
||||
0x04000000,0x00200800,0x00200000,0x04000802,
|
||||
0x04000802,0x04200002,0x04200002,0x00000002,
|
||||
0x00200002,0x04000000,0x04000800,0x00200000,
|
||||
0x04200800,0x00000802,0x00200802,0x04200800,
|
||||
0x00000802,0x04000002,0x04200802,0x04200000,
|
||||
0x00200800,0x00000000,0x00000002,0x04200802,
|
||||
0x00000000,0x00200802,0x04200000,0x00000800,
|
||||
0x04000002,0x04000800,0x00000800,0x00200002,
|
||||
0x10001040,0x00001000,0x00040000,0x10041040,
|
||||
0x10000000,0x10001040,0x00000040,0x10000000,
|
||||
0x00040040,0x10040000,0x10041040,0x00041000,
|
||||
0x10041000,0x00041040,0x00001000,0x00000040,
|
||||
0x10040000,0x10000040,0x10001000,0x00001040,
|
||||
0x00041000,0x00040040,0x10040040,0x10041000,
|
||||
0x00001040,0x00000000,0x00000000,0x10040040,
|
||||
0x10000040,0x10001000,0x00041040,0x00040000,
|
||||
0x00041040,0x00040000,0x10041000,0x00001000,
|
||||
0x00000040,0x10040040,0x00001000,0x00041040,
|
||||
0x10001000,0x00000040,0x10000040,0x10040000,
|
||||
0x10040040,0x10000000,0x00040000,0x10001040,
|
||||
0x00000000,0x10041040,0x00040040,0x10000040,
|
||||
0x10040000,0x10001000,0x10001040,0x00000000,
|
||||
0x10041040,0x00041000,0x00041000,0x00001040,
|
||||
0x00001040,0x00040040,0x10000000,0x10041000,
|
||||
};
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <gnuradio/io_signature.h>
|
||||
#include <boost/scoped_array.hpp>
|
||||
#include "fsk4_demod_ff_impl.h"
|
||||
|
||||
/*
|
||||
|
@ -186,7 +187,7 @@ namespace gr {
|
|||
gr::io_signature::make(1, 1, sizeof(float)),
|
||||
gr::io_signature::make(1, 1, sizeof(float))),
|
||||
d_block_rate(sample_rate_Hz / symbol_rate_Hz),
|
||||
d_history(new float[NTAPS]),
|
||||
my_d_history(new float[NTAPS]),
|
||||
d_history_last(0),
|
||||
d_queue(queue),
|
||||
d_symbol_clock(0.0),
|
||||
|
@ -196,7 +197,7 @@ namespace gr {
|
|||
fine_frequency_correction = 0.0;
|
||||
coarse_frequency_correction = 0.0;
|
||||
|
||||
std::fill(&d_history[0], &d_history[NTAPS], 0.0);
|
||||
std::fill(&my_d_history[0], &my_d_history[NTAPS], 0.0);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -269,7 +270,7 @@ namespace gr {
|
|||
{
|
||||
d_symbol_clock += d_symbol_time;
|
||||
|
||||
d_history[d_history_last++] = input;
|
||||
my_d_history[d_history_last++] = input;
|
||||
d_history_last %= NTAPS;
|
||||
|
||||
if(d_symbol_clock > 1.0) {
|
||||
|
@ -296,8 +297,8 @@ namespace gr {
|
|||
double interp = 0.0;
|
||||
double interp_p1 = 0.0;
|
||||
for(size_t i = 0, j = d_history_last; i < NTAPS; ++i) {
|
||||
interp += TAPS[imu][i] * d_history[j];
|
||||
interp_p1 += TAPS[imu_p1][i] * d_history[j];
|
||||
interp += TAPS[imu][i] * my_d_history[j];
|
||||
interp_p1 += TAPS[imu_p1][i] * my_d_history[j];
|
||||
j = (j + 1) % NTAPS;
|
||||
}
|
||||
#else
|
||||
|
@ -306,8 +307,8 @@ namespace gr {
|
|||
double interp_p1 = 0.0;
|
||||
for(int i=0; i<NTAPS; i++)
|
||||
{
|
||||
interp += TAPS[imu ][i] * d_history[j];
|
||||
interp_p1 += TAPS[imu_p1][i] * d_history[j];
|
||||
interp += TAPS[imu ][i] * my_d_history[j];
|
||||
interp_p1 += TAPS[imu_p1][i] * my_d_history[j];
|
||||
j = (j+1) % NTAPS;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace gr {
|
|||
{
|
||||
private:
|
||||
const float d_block_rate;
|
||||
boost::scoped_array<float> d_history;
|
||||
boost::scoped_array<float> my_d_history;
|
||||
size_t d_history_last;
|
||||
gr::msg_queue::sptr d_queue;
|
||||
double d_symbol_clock;
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <boost/format.hpp>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -65,6 +67,8 @@ hdu::do_correct_errors(bit_vector& frame)
|
|||
{
|
||||
apply_golay_correction(frame);
|
||||
apply_rs_correction(frame);
|
||||
|
||||
if (logging_enabled()) fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -136,33 +140,58 @@ hdu::frame_size_max() const
|
|||
return 792;
|
||||
}
|
||||
|
||||
string
|
||||
hdu::algid_str() const
|
||||
uint8_t
|
||||
hdu::algid() const
|
||||
{
|
||||
const size_t ALGID_BITS[] = {
|
||||
356, 357, 360, 361, 374, 375, 376, 377
|
||||
};
|
||||
const size_t ALGID_BITS_SZ = sizeof(ALGID_BITS) / sizeof(ALGID_BITS[0]);
|
||||
uint8_t algid = extract(frame_body(), ALGID_BITS, ALGID_BITS_SZ);
|
||||
return lookup(algid, ALGIDS, ALGIDS_SZ);
|
||||
return extract(frame_body(), ALGID_BITS, ALGID_BITS_SZ);
|
||||
}
|
||||
|
||||
string
|
||||
hdu::kid_str() const
|
||||
hdu::algid_str() const
|
||||
{
|
||||
uint8_t _algid = algid();
|
||||
return lookup(_algid, ALGIDS, ALGIDS_SZ);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
hdu::kid() const
|
||||
{
|
||||
const size_t KID_BITS[] = {
|
||||
378, 379, 392, 393, 394, 395, 396, 397,
|
||||
410, 411, 412, 413, 414, 415, 428, 429
|
||||
};
|
||||
const size_t KID_BITS_SZ = sizeof(KID_BITS) / sizeof(KID_BITS[0]);
|
||||
uint16_t kid = extract(frame_body(), KID_BITS, KID_BITS_SZ);
|
||||
return extract(frame_body(), KID_BITS, KID_BITS_SZ);
|
||||
}
|
||||
|
||||
string
|
||||
hdu::kid_str() const
|
||||
{
|
||||
uint16_t _kid = kid();
|
||||
ostringstream os;
|
||||
os << hex << showbase << setfill('0') << setw(4) << kid;
|
||||
os << hex << showbase << setfill('0') << setw(4) << _kid;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string
|
||||
hdu::mi_str() const
|
||||
{
|
||||
std::vector<uint8_t> _mi(mi());
|
||||
ostringstream os;
|
||||
os << "0x";
|
||||
for(size_t i = 0; i < _mi.size(); ++i) {
|
||||
uint16_t octet = _mi[i];
|
||||
os << hex << setfill('0') << setw(2) << octet;
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
hdu::mi() const
|
||||
{
|
||||
const size_t MI_BITS[] = {
|
||||
114, 115, 116, 117, 118, 119, 132, 133,
|
||||
|
@ -177,15 +206,9 @@ hdu::mi_str() const
|
|||
};
|
||||
const size_t MI_BITS_SZ = sizeof(MI_BITS) / sizeof(MI_BITS[0]);
|
||||
|
||||
uint8_t mi[9];
|
||||
extract(frame_body(), MI_BITS, MI_BITS_SZ, mi);
|
||||
ostringstream os;
|
||||
os << "0x";
|
||||
for(size_t i = 0; i < (sizeof(mi) / sizeof(mi[0])); ++i) {
|
||||
uint16_t octet = mi[i];
|
||||
os << hex << setfill('0') << setw(2) << octet;
|
||||
}
|
||||
return os.str();
|
||||
std::vector<uint8_t> _mi(((MI_BITS_SZ + 7) / 8));
|
||||
extract(frame_body(), MI_BITS, MI_BITS_SZ, &_mi[0]);
|
||||
return _mi;
|
||||
}
|
||||
|
||||
string
|
||||
|
@ -219,7 +242,21 @@ hdu::tgid_str() const
|
|||
};
|
||||
const size_t TGID_BITS_SZ = sizeof(TGID_BITS) / sizeof(TGID_BITS[0]);
|
||||
const uint16_t tgid = extract(frame_body(), TGID_BITS, TGID_BITS_SZ);
|
||||
ostringstream os;
|
||||
os << hex << showbase << setfill('0') << setw(4) << tgid;
|
||||
return os.str();
|
||||
// Zero fill isn't working properly in original implementation
|
||||
//ostringstream os;
|
||||
//os << hex << showbase << setfill('0') << setw(4) << tgid;
|
||||
//return os.str();
|
||||
return (boost::format("0x%04x") % tgid).str();
|
||||
}
|
||||
|
||||
struct CryptoState
|
||||
hdu::crypto_state() const
|
||||
{
|
||||
struct CryptoState state;
|
||||
|
||||
state.mi = mi();
|
||||
state.kid = kid();
|
||||
state.algid = algid();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -25,11 +25,12 @@
|
|||
#define INCLUDED_HDU_H
|
||||
|
||||
#include "abstract_data_unit.h"
|
||||
#include "crypto.h"
|
||||
|
||||
/**
|
||||
* P25 header data unit (HDU).
|
||||
*/
|
||||
class hdu : public abstract_data_unit
|
||||
class hdu : public abstract_data_unit, public crypto_state_provider
|
||||
{
|
||||
|
||||
public:
|
||||
|
@ -96,7 +97,9 @@ protected:
|
|||
*/
|
||||
virtual uint16_t frame_size_max() const;
|
||||
|
||||
private:
|
||||
public:
|
||||
|
||||
uint8_t algid() const;
|
||||
|
||||
/**
|
||||
* Return a string describing the encryption algorithm ID (ALGID).
|
||||
|
@ -105,6 +108,8 @@ private:
|
|||
*/
|
||||
std::string algid_str() const;
|
||||
|
||||
virtual uint16_t kid() const;
|
||||
|
||||
/**
|
||||
* Returns a string describing the key id (KID).
|
||||
*
|
||||
|
@ -119,6 +124,8 @@ private:
|
|||
*/
|
||||
virtual std::string mfid_str() const;
|
||||
|
||||
virtual std::vector<uint8_t> mi() const;
|
||||
|
||||
/**
|
||||
* Returns a string describing the message indicator (MI).
|
||||
*
|
||||
|
@ -139,6 +146,10 @@ private:
|
|||
* \return A string identifying the TGID.
|
||||
*/
|
||||
virtual std::string tgid_str() const;
|
||||
|
||||
public:
|
||||
|
||||
struct CryptoState crypto_state() const;
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_HDU_H */
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
#include "ldu.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <itpp/base/vec.h>
|
||||
#include <itpp/base/mat.h>
|
||||
#include <itpp/base/binary.h>
|
||||
#include <itpp/base/converters.h>
|
||||
|
||||
const static itpp::Mat<int> ham_10_6_3_6("1 1 1 0 0 1 1 0 0 0; 1 1 0 1 0 1 0 1 0 0; 1 0 1 1 1 0 0 0 1 0; 0 1 1 1 1 0 0 0 0 1");
|
||||
|
||||
typedef std::vector<itpp::Vec<int> > VecArray;
|
||||
|
||||
ldu::ldu(const_bit_queue& frame_body) :
|
||||
voice_data_unit(frame_body),
|
||||
m_hamming_error_count(0)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ldu::do_correct_errors(bit_vector& frame_body)
|
||||
{
|
||||
voice_data_unit::do_correct_errors(frame_body);
|
||||
}
|
||||
|
||||
bool
|
||||
ldu::process_meta_data(bit_vector& frame_body)
|
||||
{
|
||||
m_hamming_error_count = 0;
|
||||
|
||||
//std::vector<uint8_t> lc(30);
|
||||
//std::vector<uint16_t> ham(24);
|
||||
int lc_bit_idx = 0;
|
||||
VecArray arrayVec;
|
||||
itpp::Vec<int> vecRaw(10); // First 6 bits contain data
|
||||
|
||||
for (int i = 400; i < 1360; i += 184)
|
||||
{
|
||||
for (int j = 0; j < 40; j++)
|
||||
{
|
||||
int x = (i + j) + (((i + j) / 70) * 2); // Adjust bit index for status
|
||||
unsigned char ch = frame_body[x];
|
||||
|
||||
//lc[lc_bit_idx / 8] |= (ch << (7 - (lc_bit_idx % 8)));
|
||||
//ham[lc_bit_idx / 10] = ((ham[lc_bit_idx / 10]) << 1) | ch;
|
||||
vecRaw(lc_bit_idx % 10) = ch;
|
||||
|
||||
++lc_bit_idx;
|
||||
|
||||
if ((lc_bit_idx % 10) == 0)
|
||||
arrayVec.push_back(vecRaw);
|
||||
}
|
||||
}
|
||||
|
||||
if (lc_bit_idx != 240) // Not enough bits
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arrayVec.size() != 24) // Not enough vectors
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
itpp::Vec<int> vecZero(4);
|
||||
vecZero.zeros();
|
||||
|
||||
m_raw_meta_data.clear();
|
||||
|
||||
for (int i = 0; i < arrayVec.size(); ++i)
|
||||
{
|
||||
itpp::Vec<int>& vec = arrayVec[i];
|
||||
itpp::bvec vB(itpp::to_bvec(vec));
|
||||
|
||||
itpp::Vec<int> vS = ham_10_6_3_6 * vec;
|
||||
for (int i = 0; i < vS.length(); ++i)
|
||||
vS[i] = vS[i] % 2;
|
||||
itpp::bvec vb(to_bvec(vS));
|
||||
if (itpp::bin2dec(vb) != 0)
|
||||
{
|
||||
++m_hamming_error_count;
|
||||
}
|
||||
|
||||
m_raw_meta_data = concat(m_raw_meta_data, vB.mid(0, 6)); // Includes RS for last 72 bits
|
||||
}
|
||||
|
||||
if (logging_enabled()) fprintf(stderr, "%s: %lu hamming errors, %s\n", duid_str().c_str(), m_hamming_error_count, (meta_data_valid() ? "valid" : "invalid"));
|
||||
|
||||
return meta_data_valid();
|
||||
}
|
||||
|
||||
const itpp::bvec&
|
||||
ldu::raw_meta_data() const
|
||||
{
|
||||
return m_raw_meta_data;
|
||||
}
|
||||
|
||||
bool
|
||||
ldu::meta_data_valid() const
|
||||
{
|
||||
return (m_raw_meta_data.length() == 144); // Not enough bits after Hamming(10,6,3)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef INCLUDED_LDU_H
|
||||
#define INCLUDED_LDU_H
|
||||
|
||||
#include "voice_data_unit.h"
|
||||
|
||||
class ldu : public voice_data_unit
|
||||
{
|
||||
private:
|
||||
|
||||
size_t m_hamming_error_count;
|
||||
itpp::bvec m_raw_meta_data;
|
||||
|
||||
protected:
|
||||
|
||||
virtual void do_correct_errors(bit_vector& frame_body);
|
||||
|
||||
virtual bool process_meta_data(bit_vector& frame_body);
|
||||
|
||||
virtual const itpp::bvec& raw_meta_data() const;
|
||||
|
||||
public:
|
||||
|
||||
ldu(const_bit_queue& frame_body);
|
||||
|
||||
virtual bool meta_data_valid() const;
|
||||
};
|
||||
|
||||
#endif // INCLUDED_LDU_H
|
|
@ -23,10 +23,19 @@
|
|||
|
||||
#include "ldu1.h"
|
||||
|
||||
#include <itpp/base/vec.h>
|
||||
#include <itpp/base/converters.h>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include "pickle.h"
|
||||
#include "value_string.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
ldu1::ldu1(const_bit_queue& frame_body) :
|
||||
voice_data_unit(frame_body)
|
||||
ldu(frame_body)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -34,6 +43,64 @@ ldu1::~ldu1()
|
|||
{
|
||||
}
|
||||
|
||||
void ldu1::do_correct_errors(bit_vector& frame_body)
|
||||
{
|
||||
ldu::do_correct_errors(frame_body);
|
||||
|
||||
if (!process_meta_data(frame_body))
|
||||
return;
|
||||
|
||||
const itpp::bvec& data = raw_meta_data();
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
m_meta_data.m.lcf = bin2dec(data.mid(0, 8));
|
||||
m_meta_data.m.mfid = bin2dec(data.mid(8, 8));
|
||||
ss << (boost::format("%s: LCF: 0x%02x, MFID: 0x%02x") % duid_str() % m_meta_data.m.lcf % m_meta_data.m.mfid);
|
||||
if (m_meta_data.m.lcf == 0x00)
|
||||
{
|
||||
m_meta_data.m0.emergency = data[16];
|
||||
m_meta_data.m0.reserved = bin2dec(data.mid(17, 15));
|
||||
m_meta_data.m0.tgid = bin2dec(data.mid(32, 16));
|
||||
m_meta_data.m0.source = bin2dec(data.mid(48, 24));
|
||||
ss << (boost::format(", Emergency: 0x%02x, Reserved: 0x%04x, TGID: 0x%04x, Source: 0x%06x") % m_meta_data.m0.emergency % m_meta_data.m0.reserved % m_meta_data.m0.tgid % m_meta_data.m0.source);
|
||||
}
|
||||
else if (m_meta_data.m.lcf == 0x03)
|
||||
{
|
||||
m_meta_data.m3.reserved = bin2dec(data.mid(16, 8));
|
||||
m_meta_data.m3.destination = bin2dec(data.mid(24, 24));
|
||||
m_meta_data.m3.source = bin2dec(data.mid(48, 24));
|
||||
ss << (boost::format(", Reserved: 0x%02x, Destination: 0x%06x, Source: 0x%06x") % m_meta_data.m3.reserved % m_meta_data.m3.destination % m_meta_data.m3.source);
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << " (unknown LCF)";
|
||||
}
|
||||
|
||||
if (logging_enabled()) std::cerr << ss.str() << std::endl;
|
||||
}
|
||||
|
||||
std::string
|
||||
ldu1::snapshot() const
|
||||
{
|
||||
pickle p;
|
||||
p.add("duid", duid_str());
|
||||
p.add("mfid", lookup(m_meta_data.m.mfid, MFIDS, MFIDS_SZ));
|
||||
if ((m_meta_data.m.lcf == 0x00) || (m_meta_data.m.lcf == 0x03))
|
||||
p.add("source", (boost::format("0x%06x") % m_meta_data.m0.source).str());
|
||||
if (m_meta_data.m.lcf == 0x00)
|
||||
p.add("tgid", (boost::format("0x%04x") % m_meta_data.m0.tgid).str());
|
||||
if (m_meta_data.m.lcf == 0x03)
|
||||
p.add("dest", (boost::format("0x%06x") % m_meta_data.m3.destination).str());
|
||||
return p.to_string();
|
||||
}
|
||||
|
||||
ldu1::combined_meta_data
|
||||
ldu1::meta_data() const
|
||||
{
|
||||
return m_meta_data;
|
||||
}
|
||||
|
||||
string
|
||||
ldu1::duid_str() const
|
||||
{
|
||||
|
|
|
@ -24,13 +24,45 @@
|
|||
#ifndef INCLUDED_LDU1_H
|
||||
#define INCLUDED_LDU1_H
|
||||
|
||||
#include "voice_data_unit.h"
|
||||
#include "ldu.h"
|
||||
|
||||
/**
|
||||
* P25 Logical Data Unit 1.
|
||||
*/
|
||||
class ldu1 : public voice_data_unit
|
||||
class ldu1 : public ldu
|
||||
{
|
||||
protected:
|
||||
|
||||
void do_correct_errors(bit_vector& frame_body);
|
||||
|
||||
public:
|
||||
|
||||
struct base_meta_data
|
||||
{
|
||||
unsigned char lcf;
|
||||
unsigned char mfid;
|
||||
unsigned int source;
|
||||
unsigned short reserved;
|
||||
};
|
||||
|
||||
struct meta_data_0 : public base_meta_data
|
||||
{
|
||||
bool emergency;
|
||||
unsigned short tgid;
|
||||
};
|
||||
|
||||
struct meta_data_3 : public base_meta_data
|
||||
{
|
||||
unsigned int destination;
|
||||
};
|
||||
|
||||
union combined_meta_data
|
||||
{
|
||||
base_meta_data m;
|
||||
meta_data_0 m0;
|
||||
meta_data_3 m3;
|
||||
} m_meta_data;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
|
@ -50,6 +82,10 @@ public:
|
|||
*/
|
||||
std::string duid_str() const;
|
||||
|
||||
virtual std::string snapshot() const;
|
||||
|
||||
combined_meta_data meta_data() const;
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_LDU1_H */
|
||||
|
|
|
@ -23,10 +23,18 @@
|
|||
|
||||
#include "ldu2.h"
|
||||
|
||||
#include <itpp/base/vec.h>
|
||||
#include <itpp/base/converters.h>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "pickle.h"
|
||||
#include "value_string.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
ldu2::ldu2(const_bit_queue& frame_body) :
|
||||
voice_data_unit(frame_body)
|
||||
ldu(frame_body)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -39,3 +47,43 @@ ldu2::duid_str() const
|
|||
{
|
||||
return string("LDU2");
|
||||
}
|
||||
|
||||
std::string
|
||||
ldu2::snapshot() const
|
||||
{
|
||||
pickle p;
|
||||
p.add("duid", duid_str());
|
||||
std::stringstream ss;
|
||||
ss << "0x";
|
||||
for (size_t n = 0; n < m_crypto_state.mi.size(); ++n)
|
||||
ss << (boost::format("%02x") % (int)m_crypto_state.mi[n]);
|
||||
p.add("mi", ss.str());
|
||||
p.add("algid", lookup(m_crypto_state.algid, ALGIDS, ALGIDS_SZ));
|
||||
p.add("kid", (boost::format("0x%04x") % m_crypto_state.kid).str());
|
||||
return p.to_string();
|
||||
}
|
||||
|
||||
void
|
||||
ldu2::do_correct_errors(bit_vector& frame_body)
|
||||
{
|
||||
ldu::do_correct_errors(frame_body);
|
||||
|
||||
if (!process_meta_data(frame_body))
|
||||
return;
|
||||
|
||||
const itpp::bvec& data = raw_meta_data();
|
||||
|
||||
for (int i = 0; i < 72; i += 8)
|
||||
{
|
||||
m_crypto_state.mi[i/8] = bin2dec(data.mid(i, 8));
|
||||
}
|
||||
|
||||
m_crypto_state.algid = bin2dec(data.mid(72, 8));
|
||||
m_crypto_state.kid = bin2dec(data.mid(80, 16));
|
||||
}
|
||||
|
||||
struct CryptoState
|
||||
ldu2::crypto_state() const
|
||||
{
|
||||
return m_crypto_state;
|
||||
}
|
||||
|
|
|
@ -24,13 +24,22 @@
|
|||
#ifndef INCLUDED_LDU2_H
|
||||
#define INCLUDED_LDU2_H
|
||||
|
||||
#include "voice_data_unit.h"
|
||||
#include "ldu.h"
|
||||
#include "crypto.h"
|
||||
|
||||
/**
|
||||
* P25 Logical Data Unit 2.
|
||||
*/
|
||||
class ldu2 : public voice_data_unit
|
||||
class ldu2 : public ldu, public crypto_state_provider
|
||||
{
|
||||
private:
|
||||
|
||||
struct CryptoState m_crypto_state;
|
||||
|
||||
protected:
|
||||
|
||||
void do_correct_errors(bit_vector& frame_body);
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
|
@ -49,6 +58,10 @@ public:
|
|||
* Returns a string describing the Data Unit ID (DUID).
|
||||
*/
|
||||
std::string duid_str() const;
|
||||
|
||||
virtual std::string snapshot() const;
|
||||
|
||||
struct CryptoState crypto_state() const;
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_LDU2_H */
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/* -*- c++ -*- */
|
||||
/*
|
||||
* Copyright 2005 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gnuradio/message.h>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
namespace gr {
|
||||
namespace op25 {
|
||||
|
||||
static long s_ncurrently_allocated = 0;
|
||||
|
||||
message::sptr message::make(long type, double arg1, double arg2, size_t length)
|
||||
{
|
||||
return message::sptr(new message(type, arg1, arg2, length));
|
||||
}
|
||||
|
||||
message::sptr
|
||||
message::make_from_string(const std::string s, long type, double arg1, double arg2)
|
||||
{
|
||||
message::sptr m = message::make(type, arg1, arg2, s.size());
|
||||
memcpy(m->msg(), s.data(), s.size());
|
||||
return m;
|
||||
}
|
||||
|
||||
message::message(long type, double arg1, double arg2, size_t length)
|
||||
: d_type(type), d_arg1(arg1), d_arg2(arg2), d_buf(length)
|
||||
{
|
||||
if (length == 0)
|
||||
d_msg_start = d_msg_end = nullptr;
|
||||
else {
|
||||
d_msg_start = d_buf.data();
|
||||
d_msg_end = d_msg_start + length;
|
||||
}
|
||||
s_ncurrently_allocated++;
|
||||
}
|
||||
|
||||
message::~message()
|
||||
{
|
||||
assert(d_next == 0);
|
||||
s_ncurrently_allocated--;
|
||||
}
|
||||
|
||||
std::string message::to_string() const
|
||||
{
|
||||
return std::string((char*)d_msg_start, length());
|
||||
}
|
||||
|
||||
long message_ncurrently_allocated() { return s_ncurrently_allocated; }
|
||||
|
||||
} /* namespace op25 */
|
||||
} /* namespace gr */
|
|
@ -0,0 +1,23 @@
|
|||
/* -*- c++ -*- */
|
||||
/*
|
||||
* Copyright 2005,2013 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <op25/msg_handler.h>
|
||||
|
||||
namespace gr {
|
||||
namespace op25 {
|
||||
|
||||
msg_handler::~msg_handler() {}
|
||||
|
||||
} /* namespace op25 */
|
||||
} /* namespace gr */
|
|
@ -0,0 +1,113 @@
|
|||
/* -*- c++ -*- */
|
||||
/*
|
||||
* Copyright 2005,2009,2013 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gnuradio/msg_queue.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace gr {
|
||||
namespace op25 {
|
||||
|
||||
msg_queue::sptr msg_queue::make(unsigned int limit)
|
||||
{
|
||||
return msg_queue::sptr(new msg_queue(limit));
|
||||
}
|
||||
|
||||
msg_queue::msg_queue(unsigned int limit)
|
||||
: d_not_empty(),
|
||||
d_not_full(),
|
||||
/*d_head(0), d_tail(0),*/ d_count(0),
|
||||
d_limit(limit)
|
||||
{
|
||||
}
|
||||
|
||||
msg_queue::~msg_queue() { flush(); }
|
||||
|
||||
void msg_queue::insert_tail(message::sptr msg)
|
||||
{
|
||||
if (msg->d_next)
|
||||
throw std::invalid_argument("gr::msg_queue::insert_tail: msg already in queue");
|
||||
|
||||
gr::thread::scoped_lock guard(d_mutex);
|
||||
|
||||
while (full_p())
|
||||
d_not_full.wait(guard);
|
||||
|
||||
if (d_tail == 0) {
|
||||
d_tail = d_head = msg;
|
||||
// msg->d_next = 0;
|
||||
msg->d_next.reset();
|
||||
} else {
|
||||
d_tail->d_next = msg;
|
||||
d_tail = msg;
|
||||
// msg->d_next = 0;
|
||||
msg->d_next.reset();
|
||||
}
|
||||
d_count++;
|
||||
d_not_empty.notify_one();
|
||||
}
|
||||
|
||||
message::sptr msg_queue::delete_head()
|
||||
{
|
||||
gr::thread::scoped_lock guard(d_mutex);
|
||||
message::sptr m;
|
||||
|
||||
while ((m = d_head) == 0)
|
||||
d_not_empty.wait(guard);
|
||||
|
||||
d_head = m->d_next;
|
||||
if (d_head == 0) {
|
||||
// d_tail = 0;
|
||||
d_tail.reset();
|
||||
}
|
||||
|
||||
d_count--;
|
||||
// m->d_next = 0;
|
||||
m->d_next.reset();
|
||||
d_not_full.notify_one();
|
||||
return m;
|
||||
}
|
||||
|
||||
message::sptr msg_queue::delete_head_nowait()
|
||||
{
|
||||
gr::thread::scoped_lock guard(d_mutex);
|
||||
message::sptr m;
|
||||
|
||||
if ((m = d_head) == 0) {
|
||||
// return 0;
|
||||
return message::sptr();
|
||||
}
|
||||
|
||||
d_head = m->d_next;
|
||||
if (d_head == 0) {
|
||||
// d_tail = 0;
|
||||
d_tail.reset();
|
||||
}
|
||||
|
||||
d_count--;
|
||||
// m->d_next = 0;
|
||||
m->d_next.reset();
|
||||
d_not_full.notify_one();
|
||||
return m;
|
||||
}
|
||||
|
||||
void msg_queue::flush()
|
||||
{
|
||||
message::sptr m;
|
||||
|
||||
while ((m = delete_head_nowait()) != 0)
|
||||
;
|
||||
}
|
||||
|
||||
} /* namespace op25 */
|
||||
} /* namespace gr */
|
|
@ -1,113 +0,0 @@
|
|||
/* -*- C++ -*- */
|
||||
|
||||
%feature("autodoc", "1");
|
||||
|
||||
%{
|
||||
#include <stddef.h>
|
||||
%}
|
||||
|
||||
%include "exception.i"
|
||||
%import "gnuradio.i"
|
||||
|
||||
%{
|
||||
#include "gnuradio/swig/gnuradio_swig_bug_workaround.h"
|
||||
#include "op25_fsk4_demod_ff.h"
|
||||
#include "op25_fsk4_slicer_fb.h"
|
||||
#include "op25_decoder_bf.h"
|
||||
#include "op25_pcap_source_b.h"
|
||||
%}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* This does some behind-the-scenes magic so we can
|
||||
* access fsk4_square_ff from python as fsk4.square_ff
|
||||
*/
|
||||
GR_SWIG_BLOCK_MAGIC(op25, fsk4_demod_ff);
|
||||
|
||||
/*
|
||||
* Publicly-accesible default constuctor function for op25_fsk4_demod_bf.
|
||||
*/
|
||||
op25_fsk4_demod_ff_sptr op25_make_fsk4_demod_ff(gr::msg_queue::sptr queue, float sample_rate, float symbol_rate);
|
||||
|
||||
class op25_fsk4_demod_ff : public gr_block
|
||||
{
|
||||
private:
|
||||
op25_fsk4_demod_ff(gr::msg_queue::sptr queue, float sample_rate, float symbol_rate);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* This does some behind-the-scenes magic so we can invoke
|
||||
* op25_make_slicer_fb from python as op25.slicer_fbf.
|
||||
*/
|
||||
GR_SWIG_BLOCK_MAGIC(op25, fsk4_slicer_fb);
|
||||
|
||||
/*
|
||||
* Publicly-accesible default constuctor function for op25_decoder_bf.
|
||||
*/
|
||||
op25_fsk4_slicer_fb_sptr op25_make_fsk4_slicer_fb(const std::vector<float> &slice_levels);
|
||||
|
||||
/*
|
||||
* The op25_fsk4_slicer block. Takes a series of float samples and
|
||||
* partitions them into dibit symbols according to the slices_levels
|
||||
* provided to the constructor.
|
||||
*/
|
||||
class op25_fsk4_slicer_fb : public gr_sync_block
|
||||
{
|
||||
private:
|
||||
op25_fsk4_slicer_fb (const std::vector<float> &slice_levels);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* This does some behind-the-scenes magic so we can invoke
|
||||
* op25_make_decoder_bsf from python as op25.decoder_bf.
|
||||
*/
|
||||
GR_SWIG_BLOCK_MAGIC(op25, decoder_bf);
|
||||
|
||||
/*
|
||||
* Publicly-accesible default constuctor function for op25_decoder_bf.
|
||||
*/
|
||||
op25_decoder_bf_sptr op25_make_decoder_bf();
|
||||
|
||||
/**
|
||||
* The op25_decoder_bf block. Accepts a stream of dibit symbols and
|
||||
* produces an 8KS/s audio stream.
|
||||
*/
|
||||
class op25_decoder_bf : public gr_block
|
||||
{
|
||||
private:
|
||||
op25_decoder_bf();
|
||||
public:
|
||||
const char *destination() const;
|
||||
gr::msg_queue::sptr get_msgq() const;
|
||||
void set_msgq(gr::msg_queue::sptr msgq);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* This does some behind-the-scenes magic so we can invoke
|
||||
* op25_make_pcap_source_b from python as op25.pcap_source_b.
|
||||
*/
|
||||
GR_SWIG_BLOCK_MAGIC(op25, pcap_source_b);
|
||||
|
||||
/*
|
||||
* Publicly-accesible constuctor function for op25_pcap_source.
|
||||
*/
|
||||
op25_pcap_source_b_sptr op25_make_pcap_source_b(const char *path, float delay);
|
||||
|
||||
/*
|
||||
* The op25_pcap_source block. Reads symbols from a tcpdump-formatted
|
||||
* file and produces a stream of symbols of the appropriate size.
|
||||
*/
|
||||
class op25_pcap_source_b : public gr_sync_block
|
||||
{
|
||||
private:
|
||||
op25_pcap_source_b(const char *path, float delay);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <cstddef>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
* APCO Hamming(15,11,3) ecoder.
|
||||
|
@ -183,4 +184,18 @@ hamming_15_decode(uint16_t& cw)
|
|||
return errs;
|
||||
}
|
||||
|
||||
static const uint32_t hmg1063EncTbl[64] = {
|
||||
0, 12, 3, 15, 7, 11, 4, 8, 11, 7, 8, 4, 12, 0, 15, 3,
|
||||
13, 1, 14, 2, 10, 6, 9, 5, 6, 10, 5, 9, 1, 13, 2, 14,
|
||||
14, 2, 13, 1, 9, 5, 10, 6, 5, 9, 6, 10, 2, 14, 1, 13,
|
||||
3, 15, 0, 12, 4, 8, 7, 11, 8, 4, 11, 7, 15, 3, 12, 0 };
|
||||
|
||||
static const uint32_t hmg1063DecTbl[16] = {
|
||||
0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 8, 1, 16, 32, 0 };
|
||||
|
||||
static inline int hmg1063Dec (uint32_t Dat, uint32_t Par) {
|
||||
assert ((Dat < 64) && (Par < 16));
|
||||
return Dat ^ hmg1063DecTbl[hmg1063EncTbl[Dat] ^ Par];
|
||||
}
|
||||
|
||||
#endif /* INCLUDED_OP25_HAMMING_H */
|
||||
|
|
|
@ -10,9 +10,54 @@
|
|||
#include <vector>
|
||||
|
||||
typedef std::vector<bool> voice_codeword;
|
||||
typedef std::vector<uint8_t> packed_codeword;
|
||||
typedef const std::vector<bool> const_bit_vector;
|
||||
typedef std::vector<bool> bit_vector;
|
||||
|
||||
static const uint16_t hdu_codeword_bits[658] = { // 329 symbols = 324 + 5 pad
|
||||
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
|
||||
130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 144, 145, 146, 147,
|
||||
148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163,
|
||||
164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
|
||||
180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195,
|
||||
196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
|
||||
212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
|
||||
230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
|
||||
246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261,
|
||||
262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277,
|
||||
278, 279, 280, 281, 282, 283, 284, 285, 288, 289, 290, 291, 292, 293, 294, 295,
|
||||
296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311,
|
||||
312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327,
|
||||
328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343,
|
||||
344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 360, 361,
|
||||
362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
|
||||
378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393,
|
||||
394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409,
|
||||
410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425,
|
||||
426, 427, 428, 429, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443,
|
||||
444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459,
|
||||
460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475,
|
||||
476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491,
|
||||
492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 504, 505, 506, 507, 508, 509,
|
||||
510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525,
|
||||
526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541,
|
||||
542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557,
|
||||
558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573,
|
||||
576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591,
|
||||
592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607,
|
||||
608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623,
|
||||
624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639,
|
||||
640, 641, 642, 643, 644, 645, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657,
|
||||
658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673,
|
||||
674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689,
|
||||
690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705,
|
||||
706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 720, 721, 722, 723,
|
||||
724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739,
|
||||
740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755,
|
||||
756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771,
|
||||
772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787,
|
||||
788, 789 };
|
||||
|
||||
static const size_t nof_voice_codewords = 9, voice_codeword_sz = 144;
|
||||
|
||||
static const uint16_t imbe_ldu_NID_bits[] = {
|
||||
|
@ -251,8 +296,8 @@ pngen23(uint32_t& Pr)
|
|||
* \param u0-u7 Result output vectors
|
||||
*/
|
||||
|
||||
static inline void
|
||||
imbe_header_decode(const voice_codeword& cw, uint32_t& u0, uint32_t& u1, uint32_t& u2, uint32_t& u3, uint32_t& u4, uint32_t& u5, uint32_t& u6, uint32_t& u7, uint32_t& E0, uint32_t& ET)
|
||||
static inline size_t
|
||||
imbe_header_decode(const voice_codeword& cw, uint32_t& u0, uint32_t& u1, uint32_t& u2, uint32_t& u3, uint32_t& u4, uint32_t& u5, uint32_t& u6, uint32_t& u7, uint32_t& E0, uint32_t& ET, bool bot_shift = true)
|
||||
{
|
||||
ET = 0;
|
||||
|
||||
|
@ -294,7 +339,30 @@ imbe_header_decode(const voice_codeword& cw, uint32_t& u0, uint32_t& u1, uint32_
|
|||
u6 = v6;
|
||||
|
||||
u7 = extract(cw, 137, 144);
|
||||
u7 <<= 1; /* so that bit0 is free (see note about BOT bit */
|
||||
if (bot_shift)
|
||||
u7 <<= 1; /* so that bit0 is free (see note about BOT bit */
|
||||
|
||||
return errs;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pack 88 bit IMBE parameters into uint8_t vector
|
||||
*/
|
||||
static inline void
|
||||
imbe_pack(packed_codeword& cw, uint32_t u0, uint32_t u1, uint32_t u2, uint32_t u3, uint32_t u4, uint32_t u5, uint32_t u6, uint32_t u7)
|
||||
{
|
||||
cw.empty();
|
||||
cw.push_back(u0 >> 4);
|
||||
cw.push_back(((u0 & 0xf) << 4) + (u1 >> 8));
|
||||
cw.push_back(u1 & 0xff);
|
||||
cw.push_back(u2 >> 4);
|
||||
cw.push_back(((u2 & 0xf) << 4) + (u3 >> 8));
|
||||
cw.push_back(u3 & 0xff);
|
||||
cw.push_back(u4 >> 3);
|
||||
cw.push_back(((u4 & 0x7) << 5) + (u5 >> 6));
|
||||
cw.push_back(((u5 & 0x3f) << 2) + (u6 >> 9));
|
||||
cw.push_back(u6 >> 1);
|
||||
cw.push_back(((u6 & 0x1) << 7) + (u7 >> 1));
|
||||
}
|
||||
|
||||
/* APCO IMBE header encoder.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define INCLUDED_SWAB_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
* Yank in[bits[0]..bits[bits_sz]) to out[where,where+bits_sz).
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
/*
|
||||
* Copyright 2008 Steve Glass
|
||||
* Copyright 2022 Matt Ames
|
||||
*
|
||||
* This file is part of OP25.
|
||||
*
|
||||
|
@ -53,18 +54,40 @@ const value_string ALGIDS[] = {
|
|||
{ 0x02, "FIREFLY Type 1" },
|
||||
{ 0x03, "MAYFLY Type 1" },
|
||||
{ 0x04, "SAVILLE" },
|
||||
{ 0x05, "Motorola Assigned - PADSTONE" },
|
||||
{ 0x41, "BATON (Auto Odd)" },
|
||||
/* Type III */
|
||||
{ 0x80, "Plain" },
|
||||
{ 0x81, "DES-OFB" },
|
||||
{ 0x82, "2 key Triple DES" },
|
||||
{ 0x83, "3 key Triple DES" },
|
||||
{ 0x84, "AES-256" },
|
||||
/* Motorola proprietary */
|
||||
{ 0x9F, "Motorola DES-XL" },
|
||||
{ 0x80, "Unencrypted" },
|
||||
{ 0x81, "DES-OFB, 56 bit key" },
|
||||
{ 0x83, "3 key Triple DES, 168 bit key" },
|
||||
{ 0x84, "AES-256-OFB" },
|
||||
{ 0x85, "AES-128-ECB"},
|
||||
{ 0x88, "AES-CBC"},
|
||||
{ 0x89, "AES-128-OFB"},
|
||||
/* Motorola proprietary - some of these have been observed over the air,
|
||||
some have been taken from firmware dumps on various devices, others
|
||||
have come from the TIA's FTP website while it was still public,
|
||||
from document "ALGID Guide 2015-04-15.pdf", and others have been
|
||||
have been worked out with a little bit of "guesswork" ;) */
|
||||
{ 0x9F, "Motorola DES-XL 56-bit key" },
|
||||
{ 0xA0, "Motorola DVI-XL" },
|
||||
{ 0xA1, "Motorola DVP-XL" },
|
||||
{ 0xAA, "Motorola ADP" },
|
||||
{ 0xA2, "Motorola DVI-XL-SPFL"},
|
||||
{ 0xA3, "Motorola HAYSTACK" },
|
||||
{ 0xA4, "Motorola Assigned - Unknown" },
|
||||
{ 0xA5, "Motorola Assigned - Unknown" },
|
||||
{ 0xA6, "Motorola Assigned - Unknown" },
|
||||
{ 0xA7, "Motorola Assigned - Unknown" },
|
||||
{ 0xA8, "Motorola Assigned - Unknown" },
|
||||
{ 0xA9, "Motorola Assigned - Unknown" },
|
||||
{ 0xAA, "Motorola ADP (40 bit RC4)" },
|
||||
{ 0xAB, "Motorola CFX-256" },
|
||||
{ 0xAC, "Motorola GOST 28147-89 (RFC 5830)" },
|
||||
{ 0xAD, "Motorola Assigned - LOCALIZED" },
|
||||
{ 0xAE, "Motorola Assigned - Unknown" },
|
||||
{ 0xAF, "Motorola AES+" },
|
||||
{ 0xB0, "Motorola DVP"},
|
||||
{ 0xD0, "Motorola LOCAL_BR"}
|
||||
};
|
||||
const size_t ALGIDS_SZ = sizeof(ALGIDS) / sizeof(ALGIDS[0]);
|
||||
|
||||
|
|
|
@ -24,32 +24,325 @@
|
|||
#include "voice_data_unit.h"
|
||||
#include "op25_imbe_frame.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <boost/format.hpp>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <itpp/base/vec.h>
|
||||
#include <itpp/base/mat.h>
|
||||
#include <itpp/base/binary.h>
|
||||
#include <itpp/base/converters.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
voice_data_unit::~voice_data_unit()
|
||||
static void vec_mod(itpp::ivec& vec, int modulus = 2)
|
||||
{
|
||||
for (int i = 0; i < vec.length(); ++i)
|
||||
vec[i] = vec[i] % modulus;
|
||||
}
|
||||
|
||||
class cyclic_16_8_5_syndromes
|
||||
{
|
||||
public:
|
||||
typedef map<unsigned char,unsigned short> SyndromeTableMap;
|
||||
|
||||
const static itpp::imat cyclic_16_8_5;
|
||||
private:
|
||||
SyndromeTableMap m_syndrome_table;
|
||||
public:
|
||||
inline const SyndromeTableMap table() const
|
||||
{
|
||||
return m_syndrome_table;
|
||||
}
|
||||
|
||||
cyclic_16_8_5_syndromes(bool generate_now = false)
|
||||
{
|
||||
if (generate_now)
|
||||
generate();
|
||||
}
|
||||
|
||||
int generate()
|
||||
{
|
||||
if (m_syndrome_table.empty() == false)
|
||||
return -1;
|
||||
|
||||
// n=16, k=8
|
||||
|
||||
// E1
|
||||
itpp::ivec v("1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0");
|
||||
itpp::ivec r(cyclic_16_8_5 * v);
|
||||
vec_mod(r);
|
||||
itpp::bvec b(to_bvec(r));
|
||||
unsigned char ch = (unsigned char)bin2dec(b);
|
||||
itpp::bvec bV(to_bvec(v));
|
||||
unsigned short us = (unsigned short)bin2dec(bV);
|
||||
m_syndrome_table.insert(make_pair(ch, us));
|
||||
|
||||
// E2
|
||||
for (int i = 0; i <= (16 - 2); ++i)
|
||||
{
|
||||
itpp::ivec v2(v);
|
||||
v2[15-i] = 1;
|
||||
r = cyclic_16_8_5 * v2;
|
||||
bV = itpp::to_bvec(v2);
|
||||
|
||||
vec_mod(r);
|
||||
b = itpp::to_bvec(r);
|
||||
unsigned char ch = (unsigned char)itpp::bin2dec(b);
|
||||
unsigned short us = (unsigned short)itpp::bin2dec(bV);
|
||||
m_syndrome_table.insert(make_pair(ch, us));
|
||||
}
|
||||
|
||||
// E3 - disabled: min.d = 5, t=floor(5/2)=2
|
||||
/*for (int i = 0; i <= (16 - 2); ++i)
|
||||
{
|
||||
for (int j = 0; j < i; ++j)
|
||||
{
|
||||
ivec v3(v);
|
||||
v3[15-i] = 1;
|
||||
v3[15-j] = 1;
|
||||
r = cyclic_16_8_5 * v3;
|
||||
bV = to_bvec(v3);
|
||||
|
||||
vec_mod(r);
|
||||
b = to_bvec(r);
|
||||
unsigned char ch = (unsigned char)bin2dec(b);
|
||||
unsigned short us = (unsigned short)bin2dec(bV);
|
||||
m_syndrome_table.insert(make_pair(ch, us));
|
||||
}
|
||||
}*/
|
||||
|
||||
return m_syndrome_table.size();
|
||||
}
|
||||
};
|
||||
|
||||
const itpp::imat cyclic_16_8_5_syndromes::cyclic_16_8_5(
|
||||
"0 0 1 1 1 1 0 0 1 0 0 0 0 0 0 0;"
|
||||
"1 0 0 1 1 1 1 0 0 1 0 0 0 0 0 0;"
|
||||
"0 1 0 0 1 1 1 1 0 0 1 0 0 0 0 0;"
|
||||
"0 0 0 1 1 0 1 1 0 0 0 1 0 0 0 0;"
|
||||
"1 0 1 1 0 0 0 1 0 0 0 0 1 0 0 0;"
|
||||
"1 1 1 0 0 1 0 0 0 0 0 0 0 1 0 0;"
|
||||
"1 1 1 1 0 0 1 0 0 0 0 0 0 0 1 0;"
|
||||
"0 1 1 1 1 0 0 1 0 0 0 0 0 0 0 1"
|
||||
);
|
||||
|
||||
static cyclic_16_8_5_syndromes g_cyclic_16_8_5_syndromes(true);
|
||||
|
||||
static int decode_cyclic_16_8_5(const itpp::ivec& vec, itpp::ivec& out)
|
||||
{
|
||||
itpp::ivec vc(cyclic_16_8_5_syndromes::cyclic_16_8_5 * vec);
|
||||
vec_mod(vc);
|
||||
itpp::bvec vb(to_bvec(vc));
|
||||
|
||||
unsigned char ch = (unsigned char)itpp::bin2dec(vb);
|
||||
if (ch == 0x00)
|
||||
return 0;
|
||||
|
||||
const cyclic_16_8_5_syndromes::SyndromeTableMap& syndrome_table = g_cyclic_16_8_5_syndromes.table();
|
||||
cyclic_16_8_5_syndromes::SyndromeTableMap::const_iterator it = syndrome_table.find(ch);
|
||||
int j = 0;
|
||||
while (it == syndrome_table.end())
|
||||
{
|
||||
++j;
|
||||
vc = itpp::concat(itpp::ivec("0 0 0 0 0 0 0 0"), vc); // Restore to 16 bits
|
||||
vc.shift_left(vc[0]); // Rotate (s * x)
|
||||
vc = cyclic_16_8_5_syndromes::cyclic_16_8_5 * vc;
|
||||
vec_mod(vc);
|
||||
vb = itpp::to_bvec(vc);
|
||||
ch = (unsigned char)itpp::bin2dec(vb);
|
||||
it = syndrome_table.find(ch);
|
||||
|
||||
if (j >= 15)
|
||||
break;
|
||||
}
|
||||
|
||||
if (it == syndrome_table.end())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned short us = it->second;
|
||||
itpp::bvec es(itpp::dec2bin(16, us));
|
||||
if (j > 0)
|
||||
es.shift_right(es.mid(16-j, j)); // e
|
||||
vb = itpp::to_bvec(vec);
|
||||
vb -= es;
|
||||
out = itpp::to_ivec(vb);
|
||||
|
||||
vc = cyclic_16_8_5_syndromes::cyclic_16_8_5 * out;
|
||||
vec_mod(vc);
|
||||
vb = itpp::to_bvec(vc);
|
||||
if (itpp::bin2dec(vb) != 0x00)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int decode_cyclic_16_8_5(itpp::ivec& vec)
|
||||
{
|
||||
return decode_cyclic_16_8_5(vec, vec);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
voice_data_unit::voice_data_unit(const_bit_queue& frame_body) :
|
||||
abstract_data_unit(frame_body)
|
||||
abstract_data_unit(frame_body),
|
||||
d_lsdw(0),
|
||||
d_lsdw_valid(false)
|
||||
{
|
||||
memset(d_lsd_byte_valid, 0x00, sizeof(d_lsd_byte_valid));
|
||||
}
|
||||
|
||||
voice_data_unit::~voice_data_unit()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
voice_data_unit::do_correct_errors(bit_vector& frame_body)
|
||||
{
|
||||
if (logging_enabled()) fprintf(stderr, "\n");
|
||||
|
||||
d_lsd_byte_valid[0] = d_lsd_byte_valid[1] = false;
|
||||
d_lsdw_valid = false;
|
||||
|
||||
itpp::ivec lsd1(16), lsd2(16);
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
int x = 1504 + i;
|
||||
x = x + ((x / 70) * 2); // Adjust bit index for status
|
||||
if (i < 16)
|
||||
lsd1[i] = frame_body[x];
|
||||
else
|
||||
lsd2[i-16] = frame_body[x];
|
||||
}
|
||||
|
||||
int iDecode1 = decode_cyclic_16_8_5(lsd1);
|
||||
if (iDecode1 >= 0)
|
||||
{
|
||||
d_lsd_byte_valid[0] = true;
|
||||
}
|
||||
else if (iDecode1 == -1)
|
||||
{
|
||||
// Error
|
||||
}
|
||||
int iDecode2 = decode_cyclic_16_8_5(lsd2);
|
||||
if (iDecode2 >= 0)
|
||||
{
|
||||
d_lsd_byte_valid[1] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error
|
||||
}
|
||||
|
||||
d_lsdw = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
d_lsdw = d_lsdw | (lsd1[i] << (7 - i)); // Little-endian byte swap
|
||||
for (int i = 0; i < 8; ++i)
|
||||
d_lsdw = d_lsdw | (lsd2[i] << (15 - i)); // Little-endian byte swap
|
||||
|
||||
if (d_lsd_byte_valid[0] && d_lsd_byte_valid[1])
|
||||
d_lsdw_valid = true;
|
||||
}
|
||||
|
||||
uint16_t
|
||||
voice_data_unit::lsdw() const
|
||||
{
|
||||
return d_lsdw;
|
||||
}
|
||||
|
||||
bool
|
||||
voice_data_unit::lsdw_valid() const
|
||||
{
|
||||
return d_lsdw_valid;
|
||||
}
|
||||
|
||||
static void extract(unsigned int u, size_t n, std::vector<bool>& out)
|
||||
{
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
out.push_back(((u & (1 << (n-1-i))) != 0));
|
||||
}
|
||||
|
||||
void
|
||||
voice_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe)
|
||||
voice_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod)
|
||||
{
|
||||
voice_codeword cw(voice_codeword_sz);
|
||||
for(size_t i = 0; i < nof_voice_codewords; ++i) {
|
||||
imbe_deinterleave(frame_body, cw, i);
|
||||
|
||||
unsigned int u0 = 0;
|
||||
unsigned int u1,u2,u3,u4,u5,u6,u7;
|
||||
unsigned int E0 = 0;
|
||||
unsigned int ET = 0;
|
||||
|
||||
// PN/Hamming/Golay - etc.
|
||||
size_t errs = imbe_header_decode(cw, u0, u1, u2, u3, u4, u5, u6, u7, E0, ET, false); // E0 & ET are not used, and are always returned as 0
|
||||
|
||||
crypto_algorithm::sptr algorithm;
|
||||
if (crypto_mod)
|
||||
algorithm = crypto_mod->current_algorithm();
|
||||
|
||||
if (algorithm)
|
||||
{
|
||||
if (i == 8)
|
||||
{
|
||||
d_lsdw ^= algorithm->generate(16); // LSDW
|
||||
}
|
||||
|
||||
u0 ^= (int)algorithm->generate(12);
|
||||
u1 ^= (int)algorithm->generate(12);
|
||||
u2 ^= (int)algorithm->generate(12);
|
||||
u3 ^= (int)algorithm->generate(12);
|
||||
|
||||
u4 ^= (int)algorithm->generate(11);
|
||||
u5 ^= (int)algorithm->generate(11);
|
||||
u6 ^= (int)algorithm->generate(11);
|
||||
|
||||
u7 ^= (int)algorithm->generate(7);
|
||||
|
||||
imbe_header_encode(cw, u0, u1, u2, u3, u4, u5, u6, (u7 << 1));
|
||||
}
|
||||
|
||||
std::vector<bool> cw_raw;
|
||||
extract(u0, 12, cw_raw);
|
||||
extract(u1, 12, cw_raw);
|
||||
extract(u2, 12, cw_raw);
|
||||
extract(u3, 12, cw_raw);
|
||||
extract(u4, 11, cw_raw);
|
||||
extract(u5, 11, cw_raw);
|
||||
extract(u6, 11, cw_raw);
|
||||
extract(u7, 7, cw_raw);
|
||||
|
||||
const int cw_octets = 11;
|
||||
|
||||
std::vector<uint8_t> cw_vector(cw_octets);
|
||||
extract(cw_raw, 0, (cw_octets * 8), &cw_vector[0]);
|
||||
|
||||
if (logging_enabled())
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (size_t n = 0; n < cw_vector.size(); ++n)
|
||||
{
|
||||
ss << (boost::format("%02x") % (int)cw_vector[n]);
|
||||
if (n < (cw_vector.size() - 1))
|
||||
ss << " ";
|
||||
}
|
||||
|
||||
if (errs > 0)
|
||||
ss << (boost::format(" (%llu errors)") % errs);
|
||||
|
||||
std:cerr << (boost::format("%s:\t%s") % duid_str() % ss.str()) << std::endl;
|
||||
}
|
||||
|
||||
imbe.decode(cw);
|
||||
}
|
||||
|
||||
if (logging_enabled()) fprintf(stderr, "%s: LSDW: 0x%04x, %s\n", duid_str().c_str(), d_lsdw, (d_lsdw_valid ? "valid" : "invalid"));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
|
|
|
@ -38,6 +38,17 @@ public:
|
|||
*/
|
||||
virtual ~voice_data_unit();
|
||||
|
||||
const static int LSD_BYTE_COUNT=2;
|
||||
|
||||
private:
|
||||
|
||||
union {
|
||||
uint8_t d_lsd_byte[LSD_BYTE_COUNT];
|
||||
uint16_t d_lsdw;
|
||||
};
|
||||
bool d_lsdw_valid;
|
||||
bool d_lsd_byte_valid[LSD_BYTE_COUNT];
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
|
@ -63,7 +74,7 @@ protected:
|
|||
* \param frame_body The const_bit_vector to decode.
|
||||
* \param imbe The imbe_decoder to use to generate the audio.
|
||||
*/
|
||||
virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe);
|
||||
virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod);
|
||||
|
||||
/**
|
||||
* Returns the expected size (in bits) of this data_unit. For
|
||||
|
@ -74,6 +85,10 @@ protected:
|
|||
*/
|
||||
virtual uint16_t frame_size_max() const;
|
||||
|
||||
virtual uint16_t lsdw() const;
|
||||
|
||||
virtual bool lsdw_valid() const;
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_VOICE_DATA_UNIT_H */
|
||||
|
|
|
@ -28,9 +28,10 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
voice_du_handler::voice_du_handler(data_unit_handler_sptr next, imbe_decoder_sptr decoder) :
|
||||
voice_du_handler::voice_du_handler(data_unit_handler_sptr next, imbe_decoder_sptr decoder, crypto_module::sptr crypto_mod) :
|
||||
data_unit_handler(next),
|
||||
d_decoder(decoder)
|
||||
d_decoder(decoder),
|
||||
d_crypto_mod(crypto_mod)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -42,6 +43,6 @@ voice_du_handler::~voice_du_handler()
|
|||
void
|
||||
voice_du_handler::handle(data_unit_sptr du)
|
||||
{
|
||||
du->decode_audio(*d_decoder);
|
||||
du->decode_audio(*d_decoder, d_crypto_mod);
|
||||
data_unit_handler::handle(du);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "data_unit_handler.h"
|
||||
#include "imbe_decoder.h"
|
||||
#include "crypto.h"
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
|
@ -43,7 +44,7 @@ public:
|
|||
* \param next The next data_unit_handler in the chain.
|
||||
* \param decoder An imbe_decoder_sptr to the IMBE decoder to use.
|
||||
*/
|
||||
voice_du_handler(data_unit_handler_sptr next, imbe_decoder_sptr decoder);
|
||||
voice_du_handler(data_unit_handler_sptr next, imbe_decoder_sptr decoder, crypto_module::sptr crypto_mod = crypto_module::sptr()); // TODO: Add capability to decoder_ff (remove default argument)
|
||||
|
||||
/**
|
||||
* voice_du_handler virtual destructor.
|
||||
|
@ -64,6 +65,8 @@ private:
|
|||
*/
|
||||
imbe_decoder_sptr d_decoder;
|
||||
|
||||
crypto_module::sptr d_crypto_mod;
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_VOICE_DU_HANDLER_H */
|
||||
|
|
|
@ -31,7 +31,7 @@ endif()
|
|||
GR_PYTHON_INSTALL(
|
||||
FILES
|
||||
__init__.py
|
||||
DESTINATION ${GR_PYTHON_DIR}/op25
|
||||
DESTINATION ${OP25_PYTHON_DIR}/op25
|
||||
)
|
||||
|
||||
########################################################################
|
||||
|
|
|
@ -31,9 +31,9 @@ try:
|
|||
from dl import RTLD_GLOBAL as _RTLD_GLOBAL
|
||||
except ImportError:
|
||||
try:
|
||||
from DLFCN import RTLD_GLOBAL as _RTLD_GLOBAL
|
||||
from DLFCN import RTLD_GLOBAL as _RTLD_GLOBAL
|
||||
except ImportError:
|
||||
pass
|
||||
pass
|
||||
|
||||
if _RTLD_GLOBAL != 0:
|
||||
_dlopenflags = sys.getdlopenflags()
|
||||
|
@ -42,7 +42,7 @@ if _RTLD_GLOBAL != 0:
|
|||
|
||||
|
||||
# import swig generated symbols into the op25 namespace
|
||||
from op25_swig import *
|
||||
from .op25_swig import *
|
||||
|
||||
# import any pure python here
|
||||
#
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright 2022 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
/***********************************************************************************/
|
||||
/* This file is automatically generated using bindtool and can be manually edited */
|
||||
/* The following lines can be configured to regenerate this file during cmake */
|
||||
/* If manual edits are made, the following tags should be modified accordingly. */
|
||||
/* BINDTOOL_GEN_AUTOMATIC(0) */
|
||||
/* BINDTOOL_USE_PYGCCXML(0) */
|
||||
/* BINDTOOL_HEADER_FILE(message.h) */
|
||||
/* BINDTOOL_HEADER_FILE_HASH(e324acfee988515a91a4759680dbabbf) */
|
||||
/***********************************************************************************/
|
||||
|
||||
#include <pybind11/complex.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
#include <gnuradio/op25/message.h>
|
||||
// pydoc.h is automatically generated in the build directory
|
||||
#include <message_pydoc.h>
|
||||
|
||||
void bind_message(py::module& m)
|
||||
{
|
||||
|
||||
using message = ::gr::op25::message;
|
||||
|
||||
|
||||
py::class_<message,
|
||||
std::shared_ptr<message>>(m, "message", D(message))
|
||||
|
||||
.def(py::init(&message::make),
|
||||
py::arg("type") = 0,
|
||||
py::arg("arg1") = 0,
|
||||
py::arg("arg2") = 0,
|
||||
py::arg("length") = 0,
|
||||
D(message,make)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.def_static("make_from_string",&message::make_from_string,
|
||||
py::arg("s"),
|
||||
py::arg("type") = 0,
|
||||
py::arg("arg1") = 0,
|
||||
py::arg("arg2") = 0,
|
||||
D(message,make_from_string)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("type",&message::type,
|
||||
D(message,type)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("arg1",&message::arg1,
|
||||
D(message,arg1)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("arg2",&message::arg2,
|
||||
D(message,arg2)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("set_type",&message::set_type,
|
||||
py::arg("type"),
|
||||
D(message,set_type)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("set_arg1",&message::set_arg1,
|
||||
py::arg("arg1"),
|
||||
D(message,set_arg1)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("set_arg2",&message::set_arg2,
|
||||
py::arg("arg2"),
|
||||
D(message,set_arg2)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("msg",&message::msg,
|
||||
D(message,msg)
|
||||
)
|
||||
|
||||
|
||||
|
||||
//.def("to_string",&message::to_string,
|
||||
// D(message,to_string)
|
||||
//)
|
||||
.def("to_string",
|
||||
[](std::shared_ptr<message> msg) {
|
||||
std::string s = msg->to_string();
|
||||
return py::bytes(s); // Return the data without transcoding
|
||||
})
|
||||
|
||||
;
|
||||
|
||||
|
||||
|
||||
m.def("message_ncurrently_allocated",&::gr::op25::message_ncurrently_allocated,
|
||||
D(message_ncurrently_allocated)
|
||||
);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2022 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
/***********************************************************************************/
|
||||
/* This file is automatically generated using bindtool and can be manually edited */
|
||||
/* The following lines can be configured to regenerate this file during cmake */
|
||||
/* If manual edits are made, the following tags should be modified accordingly. */
|
||||
/* BINDTOOL_GEN_AUTOMATIC(0) */
|
||||
/* BINDTOOL_USE_PYGCCXML(0) */
|
||||
/* BINDTOOL_HEADER_FILE(msg_handler.h) */
|
||||
/* BINDTOOL_HEADER_FILE_HASH(668ae41ad9b9d463886fcf60a87e9ede) */
|
||||
/***********************************************************************************/
|
||||
|
||||
#include <pybind11/complex.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
#include <gnuradio/op25/msg_handler.h>
|
||||
// pydoc.h is automatically generated in the build directory
|
||||
#include <msg_handler_pydoc.h>
|
||||
|
||||
void bind_msg_handler(py::module& m)
|
||||
{
|
||||
|
||||
using msg_handler = ::gr::op25::msg_handler;
|
||||
|
||||
|
||||
py::class_<msg_handler,
|
||||
std::shared_ptr<msg_handler>>(m, "msg_handler", D(msg_handler))
|
||||
|
||||
// .def(py::init<>(),D(msg_handler,msg_handler,0))
|
||||
// .def(py::init<gr::op25::msg_handler const &>(), py::arg("arg0"),
|
||||
// D(msg_handler,msg_handler,1)
|
||||
// )
|
||||
|
||||
|
||||
|
||||
.def("handle",&msg_handler::handle,
|
||||
py::arg("msg"),
|
||||
D(msg_handler,handle)
|
||||
)
|
||||
|
||||
;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright 2022 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
/***********************************************************************************/
|
||||
/* This file is automatically generated using bindtool and can be manually edited */
|
||||
/* The following lines can be configured to regenerate this file during cmake */
|
||||
/* If manual edits are made, the following tags should be modified accordingly. */
|
||||
/* BINDTOOL_GEN_AUTOMATIC(0) */
|
||||
/* BINDTOOL_USE_PYGCCXML(0) */
|
||||
/* BINDTOOL_HEADER_FILE(msg_queue.h) */
|
||||
/* BINDTOOL_HEADER_FILE_HASH(3f70adbde5e636fca8dc78e0505b06fd) */
|
||||
/***********************************************************************************/
|
||||
|
||||
#include <pybind11/complex.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
#include <gnuradio/op25/msg_queue.h>
|
||||
// pydoc.h is automatically generated in the build directory
|
||||
#include <msg_queue_pydoc.h>
|
||||
|
||||
void bind_msg_queue(py::module& m)
|
||||
{
|
||||
|
||||
using msg_queue = ::gr::op25::msg_queue;
|
||||
|
||||
|
||||
py::class_<msg_queue,
|
||||
std::shared_ptr<msg_queue>>(m, "msg_queue", D(msg_queue))
|
||||
|
||||
.def(py::init(&msg_queue::make),
|
||||
py::arg("limit") = 0,
|
||||
D(msg_queue,make)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.def("handle",&msg_queue::handle,
|
||||
py::arg("msg"),
|
||||
D(msg_queue,handle)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("insert_tail",&msg_queue::insert_tail,
|
||||
py::arg("msg"),
|
||||
D(msg_queue,insert_tail)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("delete_head",&msg_queue::delete_head, py::call_guard<py::gil_scoped_release>(),
|
||||
D(msg_queue,delete_head)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("delete_head_nowait",&msg_queue::delete_head_nowait,
|
||||
D(msg_queue,delete_head_nowait)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("flush",&msg_queue::flush,
|
||||
D(msg_queue,flush)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("empty_p",&msg_queue::empty_p,
|
||||
D(msg_queue,empty_p)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("full_p",&msg_queue::full_p,
|
||||
D(msg_queue,full_p)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("count",&msg_queue::count,
|
||||
D(msg_queue,count)
|
||||
)
|
||||
|
||||
|
||||
|
||||
.def("limit",&msg_queue::limit,
|
||||
D(msg_queue,limit)
|
||||
)
|
||||
|
||||
;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
# Include swig generation macros
|
||||
########################################################################
|
||||
find_package(SWIG)
|
||||
find_package(PythonLibs 2)
|
||||
find_package(PythonLibs 3)
|
||||
if(NOT SWIG_FOUND OR NOT PYTHONLIBS_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
@ -31,9 +31,7 @@ include(GrPython)
|
|||
########################################################################
|
||||
# Setup swig generation
|
||||
########################################################################
|
||||
foreach(incdir ${GNURADIO_RUNTIME_INCLUDE_DIRS})
|
||||
list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/gnuradio/swig)
|
||||
endforeach(incdir)
|
||||
set(GR_SWIG_INCLUDE_DIRS $<TARGET_PROPERTY:gnuradio::runtime_swig,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
set(GR_SWIG_LIBRARIES gnuradio-op25)
|
||||
set(GR_SWIG_DOC_FILE ${CMAKE_CURRENT_BINARY_DIR}/op25_swig_doc.i)
|
||||
|
@ -44,7 +42,7 @@ GR_SWIG_MAKE(op25_swig op25_swig.i)
|
|||
########################################################################
|
||||
# Install the build swig module
|
||||
########################################################################
|
||||
GR_SWIG_INSTALL(TARGETS op25_swig DESTINATION ${GR_PYTHON_DIR}/op25)
|
||||
GR_SWIG_INSTALL(TARGETS op25_swig DESTINATION ${OP25_PYTHON_DIR}/op25)
|
||||
|
||||
########################################################################
|
||||
# Install swig .i files for development
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* -*- c++ -*- */
|
||||
|
||||
%include "pycontainer.swg"
|
||||
|
||||
#define OP25_API
|
||||
|
||||
%include "gnuradio.i" // the common stuff
|
||||
|
@ -15,6 +17,11 @@
|
|||
#include "op25/pcap_source_b.h"
|
||||
%}
|
||||
|
||||
%template(key_type) std::vector<unsigned char>;
|
||||
// This causes SWIG to segfault
|
||||
//%template(key_map_type) std::map<uint16_t,key_type >;
|
||||
%template(key_map_type) std::map<uint16_t,std::vector<unsigned char> >;
|
||||
|
||||
%include "op25/fsk4_demod_ff.h"
|
||||
GR_SWIG_BLOCK_MAGIC2(op25, fsk4_demod_ff);
|
||||
%include "op25/fsk4_slicer_fb.h"
|
||||
|
|
|
@ -63,6 +63,31 @@ if(NOT Boost_FOUND)
|
|||
message(FATAL_ERROR "Boost required to compile op25_repeater")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Find gnuradio build dependencies
|
||||
########################################################################
|
||||
find_package(CppUnit)
|
||||
|
||||
set(ENABLE_PYTHON "TRUE" CACHE BOOL "enable python")
|
||||
cmake_policy(SET CMP0012 NEW)
|
||||
|
||||
# To run a more advanced search for GNU Radio and it's components and
|
||||
# versions, use the following. Add any components required to the list
|
||||
# of GR_REQUIRED_COMPONENTS (in all caps) and change "version" to the
|
||||
# minimum API compatible version required.
|
||||
#
|
||||
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
||||
# find_package(Gnuradio "version")
|
||||
set(MIN_GR_VERSION "3.8.0")
|
||||
find_package(Gnuradio REQUIRED)
|
||||
if("${Gnuradio_VERSION}" VERSION_LESS MIN_GR_VERSION)
|
||||
MESSAGE(FATAL_ERROR "GnuRadio version required: >=\"" ${MIN_GR_VERSION} "\" found: \"" ${Gnuradio_VERSION} "\"")
|
||||
endif()
|
||||
|
||||
if(NOT CPPUNIT_FOUND)
|
||||
message(FATAL_ERROR "CppUnit required to compile op25_repeater")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Install directories
|
||||
########################################################################
|
||||
|
@ -80,31 +105,6 @@ set(GR_LIBEXEC_DIR libexec)
|
|||
set(GR_PKG_LIBEXEC_DIR ${GR_LIBEXEC_DIR}/${CMAKE_PROJECT_NAME})
|
||||
set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
|
||||
|
||||
########################################################################
|
||||
# Find gnuradio build dependencies
|
||||
########################################################################
|
||||
find_package(GnuradioRuntime)
|
||||
find_package(CppUnit)
|
||||
|
||||
# To run a more advanced search for GNU Radio and it's components and
|
||||
# versions, use the following. Add any components required to the list
|
||||
# of GR_REQUIRED_COMPONENTS (in all caps) and change "version" to the
|
||||
# minimum API compatible version required.
|
||||
#
|
||||
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
|
||||
# find_package(Gnuradio "version")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
||||
find_package(Gnuradio)
|
||||
endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
|
||||
if(NOT GNURADIO_RUNTIME_FOUND)
|
||||
message(FATAL_ERROR "GnuRadio Runtime required to compile op25_repeater")
|
||||
endif()
|
||||
if(NOT CPPUNIT_FOUND)
|
||||
message(FATAL_ERROR "CppUnit required to compile op25_repeater")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Setup the include and linker paths
|
||||
########################################################################
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
"Sysname" "Control Channel List" "Offset" "NAC" "Modulation" "TGID Tags File" "Whitelist" "Blacklist" "Center Frequency"
|
||||
"Fake" "924.975" "0" "0x293" "FSK4" "" "" "" "924.95"
|
|
|
@ -0,0 +1,243 @@
|
|||
|
||||
April, 2017
|
||||
===========
|
||||
|
||||
This file contains notes on the new version OP25 receiver (rx.py) which
|
||||
replaces the prior version scope.py. The primary differences are:
|
||||
* The dependency on WX is completely removed. By default OP25 runs in
|
||||
a console window in text-only mode.
|
||||
* An optional real-time plot can be selected when launching rx.py:
|
||||
contellation, datascope (eye pattern), or symbol trace.
|
||||
* cqpsk versus fsk4 mode is now selected as a command line parameter.
|
||||
* most rx.py command-line parameters are compatible with scope.py.
|
||||
* reduced CPU consumption, as the frame assembler block now runs as a
|
||||
sink-only GR block.
|
||||
* dependency on 14.04 is completely removed. Should now run in later
|
||||
ubuntu and fedora versions with only minor changes (not yet tested).
|
||||
|
||||
ADDITIONAL REQUIRED PACKAGES
|
||||
============================
|
||||
sudo apt-get install gnuplot-x11
|
||||
|
||||
EXAMPLE COMMAND LINE
|
||||
====================
|
||||
./rx.py --args 'rtl' --gains 'lna:49' -f 456.7e6 -T tsys.tsv -q -1 -S 1000000 -P symbol -o 50000 -w 2> stderr.2
|
||||
|
||||
Running stderr to a file (e.g., "2> stderr.2") is recommended to avoid
|
||||
screen misprinting.
|
||||
|
||||
NOTE: For phase1 voice the "-V" option is not used. Instead the
|
||||
"-w" option is used (see AUDIO SERVER section, below). For P25 phase 2/TDMA,
|
||||
the "-2" option is required in addition to the "-w" option.
|
||||
|
||||
TERMINAL OPERATION
|
||||
==================
|
||||
After starting rx.py if plotting is in use a separate gnuplot window
|
||||
should open. You must click on the terminal window to restore it to
|
||||
focus, otherwise all keystrokes are consumed by gnuplot. Once in the
|
||||
terminal window there are several keyboard commands:
|
||||
h - hold
|
||||
H - hold/goto the specified tgid
|
||||
l - lockout
|
||||
s - skip
|
||||
q - quit program
|
||||
There are also two experimental commands (should not be used in -T mode)
|
||||
f - manually change frequencies
|
||||
t - if currently tuned to a CC, autostart scanning talkgroups
|
||||
The "t" command allows trunk tracking without setting up a trunking TSV
|
||||
file. However running with the -T <filename> command line option is
|
||||
preferred as that allows use of white/black lists and talkgroup tags files.
|
||||
|
||||
If the terminal window freezes there may have been a crash. Press Ctrl-Z
|
||||
to suspend the program and examine stderr.2 for error messages. If there
|
||||
is a traceback please report the full traceback (and the command line) to
|
||||
the mail list.
|
||||
|
||||
REMOTE TERMINAL
|
||||
===============
|
||||
Adding (for example) "-l 56111" to the rx.py command starts rx.py but does
|
||||
not attach a curses terminal. Instead the program runs as normal in the
|
||||
foreground (hit CTRL-C to end rx.py as desired). To connect to a running
|
||||
instance of rx.py, (in this example)
|
||||
./terminal.py 127.0.0.1 56111
|
||||
NOTE: rx.py and terminal.py need not run on the same machine. The machine
|
||||
where terminal.py is running need not have an SDR device directly attached;
|
||||
but GNU Radio (and OP25) must be available.
|
||||
|
||||
WARNING: there is no security or encryption on the UDP port.
|
||||
|
||||
EXTERNAL UDP AUDIO SERVER
|
||||
=========================
|
||||
Starting rx.py with the "-w -W host" options directs udp audio data to
|
||||
be sent over the network to the specified remote host. It can then be
|
||||
received and played back with either of the following methods:
|
||||
1. Execute ./audio.sh on a remote machine equipped with python2.7,
|
||||
libasound.so.2 and the sockaudio.py file.
|
||||
-or-
|
||||
2. Execute the command:
|
||||
nc -kluvw 1 127.0.0.1 23456 | aplay -c1 -f S16_LE -r 8000
|
||||
|
||||
NOTE: audio underruns are to be expected when using nc | aplay as the
|
||||
pcm stream is interrupted every time a radio transmission ends. The
|
||||
sockaudio player is designed to handle this more gracefully, and generally
|
||||
only underruns due to high cpu utilization or reception/decoding errors.
|
||||
|
||||
INTERNAL AUDIO SERVER
|
||||
=====================
|
||||
Starting rx.py with the "-U" command line option enables an internal udp
|
||||
audio server which will play received audio through the default ALSA
|
||||
device. Optionally you may specify which ALSA device to use by setting
|
||||
the "-O audio_out" option along with "-U".
|
||||
|
||||
As of this writing (Aug 2017) it is still necessary to specify the "-w"
|
||||
(wireshark) option if using either the internal or external audio server.
|
||||
|
||||
PLOT MODES
|
||||
==========
|
||||
Three types of plotting are currently implemented, via the -P parameter:
|
||||
* constellation
|
||||
* datascope
|
||||
* symbol
|
||||
The symbol mode is allowed both in fsk4 and cqpsk modes. The datascope
|
||||
mode works only with fsk4 demod mode (-D fsk4). The constellation mode
|
||||
only works when the cqpsk demod mode is selected (or defaulted).
|
||||
|
||||
A couple of notes specific to plot mode:
|
||||
|
||||
1. At program startup time the gnuplot window is given the focus after
|
||||
it opens. Before you can enter terminal commands you need to click on
|
||||
the terminal window once to make it the active window.
|
||||
|
||||
2. In some cases the gnuplot window is displayed on top of the terminal
|
||||
window used by OP25. If so it may be necessary to move one or the other
|
||||
of the two windows.
|
||||
|
||||
COMMAND LINE OPTIONS
|
||||
====================
|
||||
Usage: rx.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
--args=ARGS device args
|
||||
--antenna=ANTENNA select antenna
|
||||
-a, --audio use direct audio input
|
||||
-A, --audio-if soundcard IF mode (use --calibration to set IF freq)
|
||||
-I AUDIO_INPUT, --audio-input=AUDIO_INPUT
|
||||
pcm input device name. E.g., hw:0,0 or /dev/dsp
|
||||
-i INPUT, --input=INPUT
|
||||
input file name
|
||||
-b Hz, --excess-bw=Hz
|
||||
for RRC filter
|
||||
-c Hz, --calibration=Hz
|
||||
USRP offset or audio IF frequency
|
||||
-C Hz, --costas-alpha=Hz
|
||||
value of alpha for Costas loop
|
||||
-D DEMOD_TYPE, --demod-type=DEMOD_TYPE
|
||||
cqpsk | fsk4
|
||||
-P PLOT_MODE, --plot-mode=PLOT_MODE
|
||||
constellation | symbol | datascope
|
||||
-f Hz, --frequency=Hz
|
||||
USRP center frequency
|
||||
-F IFILE, --ifile=IFILE
|
||||
read input from complex capture file
|
||||
-H HAMLIB_MODEL, --hamlib-model=HAMLIB_MODEL
|
||||
specify model for hamlib
|
||||
-s SEEK, --seek=SEEK ifile seek in K
|
||||
-l TERMINAL_TYPE, --terminal-type=TERMINAL_TYPE
|
||||
'curses' or udp port or 'http:host:port'
|
||||
-L LOGFILE_WORKERS, --logfile-workers=LOGFILE_WORKERS
|
||||
number of demodulators to instantiate
|
||||
-S SAMPLE_RATE, --sample-rate=SAMPLE_RATE
|
||||
source samp rate
|
||||
-t, --tone-detect use experimental tone detect algorithm
|
||||
-T TRUNK_CONF_FILE, --trunk-conf-file=TRUNK_CONF_FILE
|
||||
trunking config file name
|
||||
-v VERBOSITY, --verbosity=VERBOSITY
|
||||
message debug level
|
||||
-V, --vocoder voice codec
|
||||
-o Hz, --offset=Hz tuning offset frequency [to circumvent DC offset]
|
||||
-p, --pause block on startup
|
||||
-w, --wireshark output data to Wireshark
|
||||
-W WIRESHARK_HOST, --wireshark-host=WIRESHARK_HOST
|
||||
Wireshark host
|
||||
-r RAW_SYMBOLS, --raw-symbols=RAW_SYMBOLS
|
||||
dump decoded symbols to file
|
||||
-R RX_SUBDEV_SPEC, --rx-subdev-spec=RX_SUBDEV_SPEC
|
||||
select USRP Rx side A or B (default=A)
|
||||
-g GAIN, --gain=GAIN set USRP gain in dB (default is midpoint) or set audio
|
||||
gain
|
||||
-G GAIN_MU, --gain-mu=GAIN_MU
|
||||
gardner gain
|
||||
-N GAINS, --gains=GAINS
|
||||
gain settings
|
||||
-O AUDIO_OUTPUT, --audio-output=AUDIO_OUTPUT
|
||||
audio output device name
|
||||
-q FREQ_CORR, --freq-corr=FREQ_CORR
|
||||
frequency correction
|
||||
-2, --phase2-tdma enable phase2 tdma decode
|
||||
-Z DECIM_AMT, --decim-amt=DECIM_AMT
|
||||
spectrum decimation
|
||||
|
||||
HTTP CONSOLE
|
||||
============
|
||||
New as of Jan. 2018, the OP25 dashboard is accessible to any Web browser over
|
||||
HTTP. Include the option "-l http:<host>:<port>" when starting the rx.py app,
|
||||
where <host> is either "127.0.0.1" to limit access from only this host, or
|
||||
"0.0.0.0" if HTTP access from anywhere is to be allowed*. After rx.py has
|
||||
started it begins listening on the specified port for incoming connections.
|
||||
|
||||
Once connected the status page should automatically update to show trunking
|
||||
system status, frequency list, adjacent sites, and other data.
|
||||
|
||||
Example: you have started rx.py with the option "-l http:127.0.0.1:8080".
|
||||
To connect, set your web browser URL to "http://127.0.0.1:8080".
|
||||
|
||||
If one or more plot modes has been selected using the "-P" option you may
|
||||
view them by clicking the "PLOT" button. The plots are updated approx.
|
||||
every five seconds. Click "STATUS" to return to the main status page.
|
||||
|
||||
*WARNING*: there is no security or encryption. Be careful when using "0.0.0.0"
|
||||
as the listening address since anyone with access to the network can connect.
|
||||
|
||||
NOTE: the python-pyramid package is required when using this option. It can
|
||||
be installed by running
|
||||
sudo apt-get install python-pyramid
|
||||
|
||||
MULTI-RECEIVER
|
||||
==============
|
||||
The multi_rx.py app allows an arbitrary number of SDR devices and channels
|
||||
to be defined. Each channel may have one or more plot windows attached.
|
||||
|
||||
Configuration is achieved via a json file (see cfg.json for an example).
|
||||
In this version, channels are automatically assigned to the first device
|
||||
found whose frequency span includes the selected frequency.
|
||||
|
||||
As of this writing (winter, 2017), neither trunking nor P25 P2/TDMA are
|
||||
supported in multi_rx.py. The rx.py app should be used for P25 trunking,
|
||||
for either P1/FDMA or P2/TDMA.
|
||||
|
||||
Below is a summary of the major config file keys:
|
||||
demod_type: 'cqpsk' for qpsk p25 only; 'fsk4' for ysf/dstar/dmr/fsk4 p25
|
||||
filter_type: 'rc' for p25; 'rrc' for dmr and ysf; 'gmsk' for d-star
|
||||
plot: 'fft', 'constellation', 'datascope', 'symbol'
|
||||
[if more than one plot desired, provide a comma-separated list]
|
||||
destination: 'udp://host:port' or 'file://<filename>'
|
||||
name: arbitrary string used to identify channels and devices
|
||||
|
||||
bug: 'fft' and 'constellation' currently mutually exclusive
|
||||
bug: 'gmsk' needs work
|
||||
|
||||
Note: DMR audio for the second time slot is sent on the specified port number
|
||||
plus two. In the example 'udp://127.0.0.1:56122', audio for the first slot
|
||||
would use 56122; and 56124 for the second.
|
||||
|
||||
The command line options for multi_rx:
|
||||
Usage: multi_rx.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG_FILE, --config-file=CONFIG_FILE
|
||||
specify config file name
|
||||
-v VERBOSITY, --verbosity=VERBOSITY
|
||||
message debug level
|
||||
-p, --pause block on startup
|
|
@ -0,0 +1,291 @@
|
|||
|
||||
New features in this release (June, 2021)
|
||||
=========================================
|
||||
|
||||
1. With thanks to OP25 user Triptolemus, the web client is enhanced to
|
||||
include comprehensive logs of recent control channel signalling and
|
||||
call activity. Many other features are also added:
|
||||
* unit ID (subscriber ID) tagging - similar to the existing TGID
|
||||
tags setup.
|
||||
* tag color coding (for both TGID and SUID tags).
|
||||
* tag ranges and wildcarding - for both the TGID and SUID tag maps,
|
||||
a single definition line may be used to create tags for a range of
|
||||
IDs.
|
||||
* real time system frequency status table
|
||||
* smart colors
|
||||
* user settings (colors, preferences) may be edited and saved via a
|
||||
convenient set of web forms and applications
|
||||
|
||||
2. The multi_rx app adds extensions to include trunked P25 call following
|
||||
concurrent with full-time tracking of one or more P25 control channels.
|
||||
If necessary, additional SDR devices may be configured to allow full
|
||||
coverage of all control channels without loss of CC data even during voice
|
||||
call reception. Several new command line options to multi_rx have been
|
||||
added - -T (trunking TSV file) -l (terminal type) as well as -X and -U,
|
||||
all having the same meaning as in rx.py.
|
||||
|
||||
3. Control channel logging to SQL database is added. For details see the
|
||||
section on the Flask Datatables App, below.
|
||||
|
||||
4. Experimental TDMA Control Channel support
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
First locate and change to your current OP25 install build/ directory and
|
||||
run the command
|
||||
sude make uninstall
|
||||
|
||||
Since this version includes library C++ code updates it requires a full
|
||||
source rebuild via the standard install script (install.sh).
|
||||
|
||||
The installation will include one or more SDR receivers, depending
|
||||
on the the amount of spectrum utilized by the target trunking system, how
|
||||
many control channels are to be monitored concurrently, and whether voice
|
||||
call following is desired.
|
||||
|
||||
* When SQL logging is used, it is most desirable to keep the control channel
|
||||
tuned in 100% of the time. With a single SDR this is not possible when the
|
||||
range of control channel and voice channel frequencies exceed the tuning band
|
||||
of the SDR.
|
||||
* When voice call following is to be used, a separate voice channel must be
|
||||
defined for each device over which voice reception is desired. It is
|
||||
redundant to have more than one voice channel assigned to a given device.
|
||||
* A separate SDR can be dedicated to voice call following if needed. If there
|
||||
is already a frequency-locked ("tunable"=false) device whose tuning band
|
||||
includes all desired voice frequencies, a separate voice SDR is not needed.
|
||||
* This version of OP25 follows the same voice call system as in rx.py.
|
||||
That is, a single call at a time is monitored and a 3-second (nominal)
|
||||
time delay is applied at the end of each call to catch possible replies.
|
||||
* A single device may be shared by multiple channels. When more than one channel
|
||||
is assigned to a device, the device should be tuned to a fixed frequency and
|
||||
"tunable" should be set to "false".
|
||||
|
||||
Simplified example: Of all frequencies (control and voice) in the system,
|
||||
the lowest frequency is 464.05 and the highest is 464.6. An RTL-SDR having
|
||||
a maximum sample rate of 2.56 MHz is to be used. Since the band required is
|
||||
0.55 MHz, a single SDR configuration can be used. The sample rate for
|
||||
this example, 2.56 MHz, could be reduced to 1.0 MHz to conserve CPU.
|
||||
|
||||
NOTE: Proper logging of CC activity requires two things:
|
||||
1) Device and/or channel resources must be allocated so that there
|
||||
is 100% time coverage of the control channel. Voice channel
|
||||
operation on the same SDR can only occur when the entire system
|
||||
fits within the SDR tuning band.
|
||||
2) Control channel reception and demodulation must be 100% error-free.
|
||||
Occasional errors are potentially corrected by the FEC but a better
|
||||
course is to increase the receive SNR and/or decrease the system BER.
|
||||
|
||||
Notes on JSON Configuration/Parameters
|
||||
======================================
|
||||
Example json config files are included in the apps/ directory. You
|
||||
should choose one of these files (as described above) and make edits
|
||||
to a working copy of the file. The name of the resulting JSON config
|
||||
file must be passed to multi_rx.py via the "-c" parameter.
|
||||
cfg-trunk.json - When all system frequencies (CC and VC) will fit
|
||||
within the SDR tuning band (without retuning the SDR),
|
||||
or voice decode is not needed.
|
||||
cfg-trunk2.json - When two SDRs are needed to cover both CC and all VCs.
|
||||
cfg-trunkx.json - Large system example with voice following and several CCs.
|
||||
|
||||
There are several key values to note:
|
||||
"tunable" In the single-SDR configuration where all system frequencies
|
||||
(primary/secondary CCs and VCs) are within the SDR band,
|
||||
you should set this to "false". In this case the SDR is
|
||||
fixed-tuned and remains on a single frequency, the center
|
||||
frequency. You must set the center frequency to a value
|
||||
halfway between the lowest and highest frequencies in the
|
||||
system, via the device "frequency" setting.
|
||||
"frequency" See above. When "tunable" is set to "true" this value must
|
||||
be filled in. Otherwise the value is used to set the device
|
||||
frequency at startup time (must be a valid frequency for the
|
||||
device). The device will most likely be retuned one or more
|
||||
times during execution.
|
||||
"decode" Assists multi_rx in assigning channels to the proper device(s).
|
||||
If the value of "decode" starts with the string "p25_decoder",
|
||||
multi_rx uses the p25 decoder instead of its standard decoder.
|
||||
|
||||
Note that "tunable" is a device-specific parameter, and that "decode" is a
|
||||
channel-specific parameter. Also, while both the device and channel define
|
||||
the "frequency" parameter, the description above is for device entries. A
|
||||
channel entry may also define a frequency, but the channel "frequency" parameter
|
||||
is ignored (in this version).
|
||||
|
||||
When the p25_decoder is used, there is a parameter string consisting of a
|
||||
colon-separated list of parameters with each parameter in the form "key=value",
|
||||
with the parameter string defined as the value of the "decode" parameter.
|
||||
|
||||
Here are two examples:
|
||||
"decode": "p25_decoder:role=cc:dev=rtl11:nac=0x4e1", [control]
|
||||
"decode": "p25_decoder:role=vc:dev=rtl12_vc", [voice]
|
||||
The valid parameter keywords are:
|
||||
"p25_decoder" Required for trunked P25. This keyword introduces the
|
||||
parameter list. There is no value.
|
||||
"role" Must be set to "vc" or "cc".
|
||||
"dev" Must be set to the name of the device. Each channel is
|
||||
assigned to exactly one device.
|
||||
"nac" Comma-separated list of NACs for the channel. Only trunked
|
||||
systems having a NAC in the list can be assigned to this
|
||||
channel.
|
||||
"sysid" Comma-separated list of SYSIDs for the channel. Only trunked
|
||||
systems having a SYSID in the list can be assigned to this
|
||||
channel.
|
||||
|
||||
The "nac" and "sysid" options are only checked for control channels ("role=cc").
|
||||
Values starting with "0x" are hexadecimal; otherwise decimal values are assumed .
|
||||
A blank/default value for "sysid" and/or "nac" indicates that parameter is not
|
||||
checked.
|
||||
|
||||
The following startup messages in the stderr log are typical in a 2-SDR config:
|
||||
assigning channel "p25 control channel" (channel id 1) to device "rtl11_cc"
|
||||
assigning channel "p25 voice channel" (channel id 2) to device "rtl12_vc"
|
||||
Note that the channel ID displayed in the "tuning error +/-1200" messages can be
|
||||
linked to the specific device(s) encountering the error using this ID.
|
||||
|
||||
Experimental TDMA Control Channel Support
|
||||
=========================================
|
||||
|
||||
The following specifics detail the JSON configuration file channel parameters
|
||||
needed to define a TDMA control channel:
|
||||
"demod_type": "cqpsk",
|
||||
"if_rate": 24000,
|
||||
"symbol_rate": 6000,
|
||||
"decode": "p25_decoder:role=cc:dev=<device-name>:nac=0x4e1",
|
||||
The NAC should be changed to match that of the system being received, and
|
||||
<device-name> should refer to the assigned device.
|
||||
|
||||
Colors and Tags for Talkgroup and Radio IDs
|
||||
===========================================
|
||||
Tags and colors are defined in two TSV files, one for TGIDs and one for SUIDs.
|
||||
The TSV file format, compatible with earlier versions of OP25 has the TAB
|
||||
separated columns defined as:
|
||||
column one: decimal TG or SU ID. May contain wildcards (see below)
|
||||
column two: tag text (string)
|
||||
column three(optional): encoded priority/color value, decimal (see below)
|
||||
The color code is directly mapped by client JS into style sheet (CSS) colors.
|
||||
If only two columns are present the third column is defaulted to zero.
|
||||
|
||||
The file names of the two files are specified (comma-separated) in the
|
||||
trunking TSV "TGID Tags File" column (the trunking TSV in turn is the
|
||||
file referred to by the "-T" command option of rx.py or multi_rx.py).
|
||||
The talkgroup tags file name is specified first, followed by a comma,
|
||||
then the SUID tags file. The SUID tags file can't be specified alone.
|
||||
|
||||
Wildcard IDs (column one) may be (for example)
|
||||
* 123-678 [all IDs in range, inclusive, are set to same tag/color]
|
||||
* 444.... [all IDs from 4440000 to 4449999]
|
||||
* 456* [all IDs starting with 456]
|
||||
* 54321 [defines that one ID]
|
||||
|
||||
Column three contains a color value from 0-99 (decimal).
|
||||
In the TGID file (only), the column value also contains a talkgroup
|
||||
priority, encoded as follows:
|
||||
- the low-order two decimal digits (tens and units digits) are the
|
||||
color code
|
||||
- the remaining upper-order decimal digits (hundreds digit and above) are
|
||||
the priority value for talkgroup pre-emption purposes.
|
||||
|
||||
Setup SQL Log Database (Optional)
|
||||
=================================
|
||||
|
||||
This addition provides a permanent server-side log of control channel
|
||||
activity via logging to an SQL database. See the next section for details
|
||||
on installing and using the log viewer.
|
||||
|
||||
1. Make sure that sqlite3 is installed in python
|
||||
|
||||
WARNING: OP25 MUST NOT BE RUNNING DURING THIS STEP
|
||||
2. Initialize DB (any existing DB data will be destroyed)
|
||||
op25/.../apps$ python sql_dbi.py reset_db
|
||||
WARNING: OP25 MUST NOT BE RUNNING DURING THIS STEP
|
||||
|
||||
3. Import talkgroups tags file
|
||||
op25/.../apps$ python sql_dbi.py import_tgid tags.tsv <sysid>
|
||||
also, import the radio ID tags file (optional)
|
||||
op25/.../apps$ python sql_dbi.py import_unit radio-tags.tsv <sysid>
|
||||
import the System ID tags file (see below)
|
||||
op25/.../apps$ python sql_dbi.py import_sysid sysid-tags.tsv 0
|
||||
|
||||
The sysid tags must be a TSV file containing two columns
|
||||
column 1 is the P25 trunked sysid (int, decimal)
|
||||
colunn 2 is the System Name (text)
|
||||
(Note: there is no header row line in this TSV file).
|
||||
|
||||
NOTE: in the various import commands above, the sysid (decimal) must follow
|
||||
as the next argument after the TSV file name. For the sysid tags file, the
|
||||
sysid should be set to zero.
|
||||
|
||||
4. Run op25 as usual. Logfile data should be inserted into DB in real time
|
||||
and you should be able to view activity via the OP25 http console (once
|
||||
the flask/datatables app has been set up; see next section).
|
||||
|
||||
Setup Flask Datatables App
|
||||
==========================
|
||||
|
||||
0. The DB must first be established (see previous section)
|
||||
|
||||
1. Install the necessary libs. If you are running the install in Ubuntu
|
||||
16.04 there are two lines in the script that must be un-commented prior
|
||||
to running; then, in any case do:
|
||||
op25/.../apps$ sh install-sql.sh
|
||||
|
||||
Note: you may need to 'sudo apt install git' prior to running this script.
|
||||
|
||||
2. Update your .bashrc file as instructed, then re-login to pick up the
|
||||
update to PATH. Verify that the updated PATH is correct. You can run
|
||||
the command "echo $PATH" to display its current value. Here is an example
|
||||
response: /home/op25/.local/bin:/usr/local/sbin:/usr/local/bin.....
|
||||
You should confirm that the file "flask" exists and is executable (see
|
||||
warning below).
|
||||
|
||||
$ ls -l ~/.local/bin/flask
|
||||
-rwxr-xr-x 1 op25 op25 212 Apr 29 21:43 /home/op25/.local/bin/flask
|
||||
|
||||
3. First change to the "..../apps/oplog" directory, then run the following
|
||||
commands to start the flask http process (listens on port 5000)
|
||||
|
||||
op25/.../apps/oplog$ export FLASK_APP=op25
|
||||
op25/.../apps/oplog$ FLASK_DEBUG=1 flask run
|
||||
|
||||
WARNING: if you receive the following messages when attempting the "flask run"
|
||||
command
|
||||
-------------------------------------------------------------
|
||||
Command 'flask' not found, but can be installed with:
|
||||
sudo apt install python3-flask
|
||||
-------------------------------------------------------------
|
||||
most likely this indicates the PATH is not properly set up (see step 2).
|
||||
In this case we do NOT recommend that you attempt to install the apt version
|
||||
of flask. The install-sql.sh script (step 1, above) should have installed a
|
||||
version of flask in a directory such as:
|
||||
|
||||
$ ls -l ~/.local/bin/flask
|
||||
-rwxr-xr-x 1 op25 op25 212 Apr 29 21:43 /home/op25/.local/bin/flask
|
||||
|
||||
If install of the apt version of flask is attempted, it may result in an
|
||||
obsolete and/or incompatible flask version being installed.
|
||||
|
||||
NOTE: The following command example can be used when starting the oplog
|
||||
process as a system service:
|
||||
/home/<user>/op25/op25/gr-op25_repeater/apps/oplog/oplog.sh
|
||||
* Change <user> to match the username
|
||||
* Make appropriate corrections if the git repo is cloned into a different
|
||||
directory than in the command shown
|
||||
* Verify the resulting path and filename is correct.
|
||||
============ oplog.sh ============
|
||||
#! /bin/sh
|
||||
|
||||
export FLASK_APP=op25
|
||||
FLASK_DEBUG=1 flask run --host=0.0.0.0
|
||||
==================================
|
||||
|
||||
In lieu of setting the flask PATH (as per step 2, above) you could also
|
||||
specify it explicitly. In that case, replace the last line of oplog.sh as
|
||||
follows:
|
||||
FLASK_DEBUG=1 /home/<user>/.local/bin/flask run --host=0.0.0.0
|
||||
* Change <user> to match the username
|
||||
* Confirm the "flask" file is present in ..../bin/ and is executable
|
||||
* Set "host" to "127.0.0.1" to restrict HTTP connections to the local
|
||||
machine.
|
||||
* WARNING The web server is not security-hardened. It is not
|
||||
designed for exposure to public web-facing applications.
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
Linking OP25 TX and RX December 2020
|
||||
===================================================================
|
||||
|
||||
The OP25 TX application can be configured to transmit one or more
|
||||
channels directly to one of the RX apps (rx.py or multi_rx.py) in
|
||||
each case by specifying a device args string of the form
|
||||
udp:host:port
|
||||
where host and port are the IP address and UDP port number,
|
||||
respectively. Typically, the TX is started first followed by
|
||||
the RX, but they can be started in any order. UDP port 25252 is
|
||||
used in the examples.
|
||||
|
||||
The sample RX script is in runudp.sh, and the sample TX json config
|
||||
is in cfg-900.json .
|
||||
|
||||
To start the TX
|
||||
cd .../op25/op25-gr_repeater/apps/tx
|
||||
./multi_tx.py -c ../cfg-900.json
|
||||
|
||||
The RX is started with
|
||||
cd .../op25/op25-gr_repeater/apps
|
||||
./runudp.sh
|
||||
|
||||
There is a 120KHz sized block of spectrum transmitted (SR=120000)
|
||||
which is treated as complex baseband with each side pretending that
|
||||
the spectrum is converted to/from the 900 MHz band. On the RX side
|
||||
any frequency-change requests are ignored; instead, a "center
|
||||
frequency" is defined. There are two channels (P25 trunk control
|
||||
channel and P25 voice channel) spaced at 50 KHz separation.
|
||||
|
||||
The TX reads two data files that must be created beforehand. To
|
||||
create the trunk control channel data symbol file, refer to
|
||||
tx/README-fakecc.txt
|
||||
An audio file (PCM/ rate 8000 / mono / signed int16) must also be
|
||||
created - this file will be real-time encoded with the voice codec
|
||||
and sent on the P25 voice channel. These two files must be defined
|
||||
in the cfg-900.json file (channel "source" keyword).
|
|
@ -0,0 +1,268 @@
|
|||
|
||||
OP25 HTTP live streaming December 2020
|
||||
=====================================================================
|
||||
|
||||
These hacks ("OP25-hls hacks") add a new option for audio reception
|
||||
and playback in OP25; namely, via an HTTP live stream to any remote
|
||||
client using a standard Web browser*. The web server software used
|
||||
(nginx) is industrial-strength and immediately scalable to dozens or
|
||||
hundreds of simultaneous remote users with zero added effort. More
|
||||
than one upstream source (in parallel) can be served simultaneously.
|
||||
|
||||
OP25's liquidsoap script is hacked to pipe output PCM audio data
|
||||
to ffmpeg, which also reads the www/images/status.png image file
|
||||
that makes up the video portion of the encoded live stream. The
|
||||
image png file is kept updated by rx.py.
|
||||
|
||||
The selection of ffmpeg codecs ("libx264" for video and "aac" for
|
||||
audio) allows us directly to send the encoded data stream from
|
||||
ffmpeg to the web server (nginx) utilizing RTMP. Individual
|
||||
MPEG-TS segments are stored as files in nginx web server URL-space,
|
||||
and served to web clients via standard HTTP GET requests. The
|
||||
hls.js package is used at the client.
|
||||
|
||||
The entire effort mostly involved assembling existing off-the-shelf
|
||||
building blocks. The ffmpeg package was built manually from source
|
||||
to enable the "libx264" codec, and a modified nginx config was
|
||||
used.
|
||||
|
||||
*the web browser must support the "MediaSource Extensions" API.
|
||||
All recent broswer versions should qualify.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. nginx installation
|
||||
|
||||
The libnginx-mod-rtmp package must be installed (in addition to
|
||||
nginx itself). You can copy the sample nginx configuration at the
|
||||
end of this README file to /etc/nginx/nginx.conf, followed by
|
||||
restarting the web server
|
||||
sudo systemctl stop nginx
|
||||
sudo systemctl start nginx
|
||||
With this configuration the web server should listen on HTTP port
|
||||
8081 and RTMP port 1935.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
2. ffmpeg installation
|
||||
git clone https://code.videolan.org/videolan/x264.git
|
||||
git clone https://git.ffmpeg.org/ffmpeg.git
|
||||
cd x264
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
cd ../ffmpeg
|
||||
./configure --enable-shared --enable-libx264 --enable-gpl
|
||||
make
|
||||
sudo make install
|
||||
|
||||
To confirm the installation run the "ffmpeg" command and
|
||||
verify the presence of "--enable-shared" and "--enable-libx264"
|
||||
in the "configuration:" section of the output.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
3. liquidsoap installation
|
||||
Both packages "liquidsoap" and "liquidsoap-plugin-all" were
|
||||
installed, but not tested whether the plugins are required for
|
||||
this application.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
4. nginx setup
|
||||
with the custom config installed as per step 1, copy the files
|
||||
from op25/gr_op25_repeater/www to /var/www/html as follows:
|
||||
|
||||
live.html
|
||||
live.m3u8
|
||||
hls.js
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
5. liquidsoap setup
|
||||
in the op25/gr_op25_repeater/apps directory, note the ffmpeg.liq
|
||||
script. Overall the filtering and buffering options should be
|
||||
similar to those in op25.liq. The default version of ffmpeg.liq
|
||||
should be OK for most uses.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
6. operation
|
||||
With OP25 rx.py started using the options -V -w (and -2 if using
|
||||
TDMA) and with ffmpeg.liq started (both from the apps directory),
|
||||
you should be able to connect to http://hostip:8081/live.html
|
||||
and click the Play button to begin. NOTE: See note E for more
|
||||
details about how to specify the value for 'hostip' in the above
|
||||
link.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
7. troubleshooting
|
||||
A. with the ffmpeg.liq script running ffmpeg should start sending
|
||||
rtmp data over port 1935 to nginx. You should see files
|
||||
start being populated in /var/www/html/hls/ .
|
||||
B. If /var/www/html/hls is empty, check ffmpeg message output
|
||||
for possible errors, and also check the nginx access and
|
||||
error logs. Note that the /var/www/html/hls directory should
|
||||
start receiving files a few seconds after ffmpeg.liq is started
|
||||
(regardless of whether OP25 is actively receiving a station,
|
||||
or is not receiving).
|
||||
C. js debug can be enabled for hls.js by editing that file as
|
||||
follows; locate the lines of code and change the "debug"
|
||||
setting to "true"
|
||||
|
||||
var hlsDefaultConfig = _objectSpread(_objectSpread({
|
||||
autoStartLoad: true,
|
||||
// used by stream-controller
|
||||
startPosition: -1,
|
||||
// used by stream-controller
|
||||
defaultAudioCodec: void 0,
|
||||
// used by stream-controller
|
||||
debug: true, ///// <<<=== change this line from
|
||||
///// "false" to "true"
|
||||
|
||||
D. after reloading the page and with the web browser js console
|
||||
opened (and with all message types enabled), debug messages
|
||||
should now start appearing in the console. As before another
|
||||
place to look for messages is in the nginx access and error
|
||||
logs.
|
||||
E. if you are doing heavy client-side debugging it may be helpful
|
||||
to obtain a copy of the hls.js distribution and to populate
|
||||
the hls.js.map file (with updated hls.js) in /var/www/html.
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
8. notes
|
||||
A. due to the propagation delay inherent in the streaming
|
||||
process, there is a latency of several seconds from when
|
||||
the transmissions are receieved before they are played in
|
||||
the remote web browser. OP25 attempts to keep the video
|
||||
and audio synchronized but the usual user controls (hold,
|
||||
lockout, etc). are not available (in this release) because
|
||||
the several-second delay could cause the commands to operate
|
||||
on stale talkgroup data (without additional work).
|
||||
B. in keeping with the current OP25 liquidsoap setup, the audio
|
||||
stream is converted to mono prior to streaming. It might be
|
||||
possible to retain the stereo data (in cases where the L and
|
||||
R channels contain separate information), but this has not
|
||||
been tested. The ffmpeg.liq script would need to be changed to
|
||||
use "output" instead of "mean(output)" and the ffmpeg script
|
||||
would need to change "-ac 1" to "-ac 2". In addition the
|
||||
options stereo=true and channels=2 would need to be set in the
|
||||
%wav specification parameters.
|
||||
C. multiple independent streams can be served simultaneously by
|
||||
invoking a separate ffmpeg.sh script for each stream and by
|
||||
changing the last component of the rtmp URL to a unique
|
||||
value; for example:
|
||||
rtmp://localhost/live/stream2
|
||||
A unified (parameterized) version of ffmpeg.sh could also be
|
||||
used.
|
||||
Also, new versions of live.html and live.m3u8 in /var/www/html
|
||||
(reflecting the above modification) would need to be added.
|
||||
D. note that pausing and seeking etc. in the media feed isn't
|
||||
possible when doing live streaming.
|
||||
E. when connecting from the remote client to the nginx server as
|
||||
detailed in step 6 (above) you should specify the value for
|
||||
'hostip' as follows (omitting the quotes):
|
||||
|
||||
'localhost' (default) - use this when the client is on the same
|
||||
machine as the server
|
||||
'host.ip.address' - specify the IP address of the server when
|
||||
the client and server machines are different, and the server
|
||||
does not have a DNS hostname.
|
||||
'hostname' - if the server has a DNS hostname, this name should
|
||||
be used in place of 'hostip'.
|
||||
|
||||
Note that in the second and third cases above you must also
|
||||
change the hostname from 'localhost' to the IP address or DNS
|
||||
hostname, respectively, in the following files
|
||||
live.html
|
||||
live.m3u8
|
||||
in /var/www/html (total three occurrences).
|
||||
|
||||
Similarly, if an IP port other than 8081 is to be used, the same
|
||||
updates as above must be made, and also the nginx conf file must
|
||||
be updated to reflect the changed port number.
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
########################################################################
|
||||
####### tested on ubuntu 18.04 #######
|
||||
####### sample nginx conf file - copy everything below this line #######
|
||||
|
||||
user www-data;
|
||||
worker_processes auto;
|
||||
pid /run/nginx.pid;
|
||||
include /etc/nginx/modules-enabled/*.conf;
|
||||
|
||||
events {
|
||||
worker_connections 768;
|
||||
# multi_accept on;
|
||||
}
|
||||
|
||||
# RTMP configuration
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935; # Listen on standard RTMP port
|
||||
chunk_size 4000;
|
||||
|
||||
application live {
|
||||
live on;
|
||||
# Turn on HLS
|
||||
hls on;
|
||||
hls_path /var/www/html/hls/;
|
||||
hls_fragment 3;
|
||||
hls_playlist_length 60;
|
||||
# disable consuming the stream from nginx as rtmp
|
||||
deny play all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile off;
|
||||
tcp_nopush on;
|
||||
#aio on;
|
||||
directio 512;
|
||||
default_type application/octet-stream;
|
||||
|
||||
server {
|
||||
listen 8081;
|
||||
|
||||
location / {
|
||||
# Disable cache
|
||||
add_header 'Cache-Control' 'no-cache';
|
||||
|
||||
# CORS setup
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Expose-Headers' 'Content-Length';
|
||||
|
||||
# allow CORS preflight requests
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
# include /etc/nginx/mime.types;
|
||||
types {
|
||||
text/html html;
|
||||
text/css css;
|
||||
application/javascript js;
|
||||
application/dash+xml mpd;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
video/mp2t ts;
|
||||
}
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
root /var/www/html;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"label_color": "#000000",
|
||||
"tic_color": "#000000",
|
||||
"border_color": "#000000",
|
||||
"plot_color": "#c000ff",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017, 2018 Graham Norbury
|
||||
#
|
||||
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
|
||||
#
|
||||
# This file is part of OP25 and part of GNU Radio
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
from optparse import OptionParser
|
||||
from sockaudio import socket_audio
|
||||
|
||||
def signal_handler(signal, frame):
|
||||
sys.stderr.write("audio.py shutting down\n")
|
||||
audio_handler.stop()
|
||||
sys.exit(0)
|
||||
|
||||
parser = OptionParser()
|
||||
parser.add_option("-O", "--audio-output", type="string", default="default", help="audio output device name")
|
||||
parser.add_option("-H", "--host-ip", type="string", default="0.0.0.0", help="IP address to bind to")
|
||||
parser.add_option("-u", "--wireshark-port", type="int", default=23456, help="Wireshark port")
|
||||
parser.add_option("-2", "--two-channel", action="store_true", default=False, help="single or two channel audio")
|
||||
parser.add_option("-x", "--audio-gain", type="float", default="1.0", help="audio gain (default = 1.0)")
|
||||
parser.add_option("-s", "--stdout", action="store_true", default=False, help="write to stdout instead of audio device")
|
||||
parser.add_option("-S", "--silence", action="store_true", default=False, help="suppress output of zeros after timeout")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
if len(args) != 0:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
audio_handler = socket_audio(options.host_ip, options.wireshark_port, options.audio_output, options.two_channel, options.audio_gain, options.stdout, silent_flag=options.silence)
|
||||
|
||||
if __name__ == "__main__":
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
audio_handler.run()
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"channels": [
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "udp://127.0.0.1:56120",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 924975000,
|
||||
"if_rate": 24000,
|
||||
"name": "p25 control channel",
|
||||
"plot": "datascope",
|
||||
"source": "symbols:sym-cc925.dat",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "udp://127.0.0.1:56124",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 924925000,
|
||||
"if_rate": 24000,
|
||||
"name": "p25 voice channel",
|
||||
"plot": "datascope",
|
||||
"source": "/home/mhp/rand4.raw",
|
||||
"symbol_rate": 4800
|
||||
}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"args": "udp:127.0.0.1:25252",
|
||||
"frequency": 924950000,
|
||||
"gains": "",
|
||||
"name": "udp",
|
||||
"offset": 0,
|
||||
"ppm": 0,
|
||||
"rate": 120000,
|
||||
"tunable": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"channels": [
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:56124",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "p25 control channel",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:cc",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:23456",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "p25 voice channel",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:vc",
|
||||
"symbol_rate": 4800
|
||||
}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"args": "rtl=1",
|
||||
"frequency": 454300000,
|
||||
"gains": "lna:49",
|
||||
"name": "rtl0",
|
||||
"offset": 0,
|
||||
"ppm": 54,
|
||||
"rate": 1000000,
|
||||
"tunable": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"channels": [
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:56124",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "p25 control channel",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:cc",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:23456",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "p25 voice channel",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:vc",
|
||||
"symbol_rate": 4800
|
||||
}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"args": "rtl=00000011",
|
||||
"frequency": 460300000,
|
||||
"gains": "lna:49",
|
||||
"name": "rtl11_cc",
|
||||
"offset": 0,
|
||||
"ppm": 54,
|
||||
"rate": 1000000,
|
||||
"tunable": false
|
||||
},
|
||||
{
|
||||
"args": "rtl=00000012",
|
||||
"frequency": 453000000,
|
||||
"gains": "lna:49",
|
||||
"name": "rtl12_vc",
|
||||
"offset": 0,
|
||||
"ppm": 54,
|
||||
"rate": 1000000,
|
||||
"tunable": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"channels": [
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:23456",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "Oswego CC",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:role=cc:dev=rtl12:nac=0x2a4",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:23456",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "Cayuga CC",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:role=cc:dev=rtl12:nac=0x2a8",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:23456",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "460 MHz VC",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:role=vc:dev=rtl12",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:23456",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "453-454 MHz VC",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:role=vc:dev=rtl11",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:56124",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "Onondaga CC",
|
||||
"plot": "symbol",
|
||||
"decode": "p25_decoder:role=cc:dev=rtl12:nac=0x2a0",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:56124",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 0,
|
||||
"if_rate": 24000,
|
||||
"name": "Cortland CC",
|
||||
"plot": "constellation",
|
||||
"decode": "p25_decoder:role=cc:dev=rtl11:nac=0x4e1",
|
||||
"symbol_rate": 4800
|
||||
}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"args": "rtl=00000012",
|
||||
"frequency": 460500000,
|
||||
"gains": "lna:49",
|
||||
"name": "rtl12",
|
||||
"offset": 0,
|
||||
"ppm": 54,
|
||||
"rate": 1000000,
|
||||
"tunable": false
|
||||
},
|
||||
{
|
||||
"args": "rtl=00000011",
|
||||
"frequency": 453850000,
|
||||
"gains": "lna:49",
|
||||
"name": "rtl11",
|
||||
"offset": 0,
|
||||
"ppm": 55,
|
||||
"rate": 2048000,
|
||||
"tunable": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"channels": [
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "udp://127.0.0.1:56124",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "nxdn",
|
||||
"frequency": 442112500,
|
||||
"if_rate": 24000,
|
||||
"name": "nxdn48",
|
||||
"plot": "datascope",
|
||||
"source": "/home/mhp/rand0.raw",
|
||||
"symbol_rate": 2400
|
||||
},
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "udp://127.0.0.1:56128",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rrc",
|
||||
"frequency": 442187500,
|
||||
"if_rate": 24000,
|
||||
"name": "dmr",
|
||||
"plot": "datascope",
|
||||
"source": "/home/mhp/rand1.raw",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "udp://127.0.0.1:56132",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "gmsk",
|
||||
"frequency": 442262500,
|
||||
"if_rate": 24000,
|
||||
"name": "dstar",
|
||||
"plot": "datascope",
|
||||
"source": "/home/mhp/rand2.raw",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "udp://127.0.0.1:56136",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rrc",
|
||||
"frequency": 442337500,
|
||||
"if_rate": 24000,
|
||||
"name": "ysf",
|
||||
"plot": "datascope",
|
||||
"source": "/home/mhp/rand3.raw",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "udp://127.0.0.1:56120",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 442412500,
|
||||
"if_rate": 24000,
|
||||
"name": "p25",
|
||||
"plot": "datascope",
|
||||
"source": "/home/mhp/rand4.raw",
|
||||
"symbol_rate": 4800
|
||||
}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"args": "udp:127.0.0.1:25252",
|
||||
"frequency": 442262500,
|
||||
"gains": "",
|
||||
"name": "udp",
|
||||
"offset": 0,
|
||||
"ppm": 0,
|
||||
"rate": 480000,
|
||||
"tunable": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"channels": [
|
||||
{
|
||||
"demod_type": "cqpsk",
|
||||
"destination": "udp://127.0.0.1:56120",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rc",
|
||||
"frequency": 460412500,
|
||||
"if_rate": 24000,
|
||||
"name": "p25",
|
||||
"plot": "symbol",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "file:///tmp/out1.raw",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rrc",
|
||||
"frequency": 460500000,
|
||||
"if_rate": 24000,
|
||||
"name": "ysf",
|
||||
"plot": "datascope",
|
||||
"symbol_rate": 4800
|
||||
},
|
||||
{
|
||||
"demod_type": "fsk4",
|
||||
"destination": "udp://127.0.0.1:56122",
|
||||
"excess_bw": 0.2,
|
||||
"filter_type": "rrc",
|
||||
"frequency": 460050000,
|
||||
"if_rate": 24000,
|
||||
"name": "dmr",
|
||||
"plot": "symbol",
|
||||
"symbol_rate": 4800
|
||||
}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"args": "rtl=0",
|
||||
"frequency": 460100000,
|
||||
"gains": "lna:49",
|
||||
"name": "rtl0",
|
||||
"offset": 0,
|
||||
"ppm": 38,
|
||||
"rate": 1000000,
|
||||
"tunable": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,602 @@
|
|||
[
|
||||
[
|
||||
500,
|
||||
"placeholder",
|
||||
"do-not-use",
|
||||
false
|
||||
],
|
||||
[
|
||||
1,
|
||||
"#0066ff",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
2,
|
||||
"#ff0000",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
3,
|
||||
"#ff9900",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
4,
|
||||
"#eeeeee",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
5,
|
||||
"#9966ff",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
6,
|
||||
"#00ff00",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
7,
|
||||
"#009933",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
8,
|
||||
"#ffff00",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
9,
|
||||
"#eee",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
10,
|
||||
"#ff6666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
11,
|
||||
"#0080C0",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
12,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
13,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
14,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
15,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
16,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
17,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
18,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
19,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
20,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
21,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
22,
|
||||
"#ff0000",
|
||||
"",
|
||||
true
|
||||
],
|
||||
[
|
||||
23,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
24,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
25,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
26,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
27,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
28,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
29,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
30,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
31,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
32,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
33,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
34,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
35,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
36,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
37,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
38,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
39,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
40,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
41,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
42,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
43,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
44,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
45,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
46,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
47,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
48,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
49,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
50,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
51,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
52,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
53,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
54,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
55,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
56,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
57,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
58,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
59,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
60,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
61,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
62,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
63,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
64,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
65,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
66,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
67,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
68,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
69,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
70,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
71,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
72,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
73,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
74,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
75,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
76,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
77,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
78,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
79,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
80,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
81,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
82,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
83,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
84,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
85,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
86,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
87,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
88,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
89,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
90,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
91,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
92,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
93,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
94,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
95,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
96,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
97,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
98,
|
||||
"#666666",
|
||||
"",
|
||||
false
|
||||
],
|
||||
[
|
||||
99,
|
||||
"#00ff00",
|
||||
"#000000",
|
||||
false
|
||||
]
|
||||
]
|
|
@ -0,0 +1,27 @@
|
|||
[
|
||||
{
|
||||
"fs":[ 1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1 ],
|
||||
"length":864,
|
||||
"name":"P25"
|
||||
},
|
||||
{
|
||||
"fs":[ -3, -3, 1, 1, -3, -3, 3, 3, -3, -3, -3, -3, 3, 3, 3, 3, -1, -1, 3, 3 ],
|
||||
"length":384,
|
||||
"name":"NXDN48"
|
||||
},
|
||||
{
|
||||
"fs":[ -3, 1, -3, 3, -3, -3, 3, 3, -1, 3 ],
|
||||
"length":192,
|
||||
"name":"NXDN96"
|
||||
},
|
||||
{
|
||||
"fs":[ 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1 ],
|
||||
"length":576,
|
||||
"name":"DMR"
|
||||
},
|
||||
{
|
||||
"fs":[ -3, 3, 3, 1, 3, -3, 1, 3, -3, 1, -1, 3, 3, -1, 1, -3, 3, 1, -3, 3 ],
|
||||
"length":480,
|
||||
"name":"YSF"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# (c) Copyright 2020, OP25
|
||||
#
|
||||
# This file is part of OP25 and part of GNU Radio
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
""" generate named image file consisting of multi-line text """
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import os
|
||||
|
||||
_TTF_FILE = '/usr/share/fonts/truetype/freefont/FreeSerif.ttf'
|
||||
|
||||
def create_image(textlist=["Blank"], imgfile="test.png", bgcolor='red', fgcolor='black', windowsize=(400,300)):
|
||||
global _TTF_FILE
|
||||
width=windowsize[0]
|
||||
height=windowsize[1]
|
||||
|
||||
margin = 4
|
||||
if not os.access(_TTF_FILE, os.R_OK):
|
||||
font = ImageFont.load_default()
|
||||
else:
|
||||
font = ImageFont.truetype(_TTF_FILE, 16)
|
||||
img = Image.new('RGB', (width, height), bgcolor)
|
||||
draw = ImageDraw.Draw(img)
|
||||
cursor = 0
|
||||
for line in textlist:
|
||||
w,h = draw.textsize(line, font)
|
||||
# TODO: overwidth check needed?
|
||||
if cursor+h >= height:
|
||||
break
|
||||
draw.text((margin, cursor), line,'black',font)
|
||||
cursor += h + margin // 2
|
||||
|
||||
img.save(imgfile)
|
||||
|
||||
if __name__ == '__main__':
|
||||
s = []
|
||||
s.append('Starting...')
|
||||
|
||||
create_image(textlist=s, bgcolor='#c0c0c0')
|
|
@ -0,0 +1,383 @@
|
|||
#sql_dbi events map
|
||||
|
||||
events_map = {
|
||||
"grp_v_ch_grant_mbt": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'options'],
|
||||
['frequency', 'frequency'],
|
||||
['tgid', 'group'],
|
||||
['suid', 'srcaddr'],
|
||||
],
|
||||
"grg_exenc_cmd": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'mfrid'],
|
||||
['tgid', 'sg'],
|
||||
['p', 'keyid'],
|
||||
],
|
||||
"grp_v_ch_grant": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'mfrid'],
|
||||
['p', 'options'],
|
||||
['frequency', 'frequency'],
|
||||
['tgid', 'group'],
|
||||
['suid', 'srcaddr'],
|
||||
],
|
||||
"mot_grg_cn_grant": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'mfrid'],
|
||||
['frequency', 'frequency'],
|
||||
['tgid', 'sg'],
|
||||
['suid', 'sa'],
|
||||
],
|
||||
"grp_v_ch_grant_updt": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'mfrid'],
|
||||
['frequency', 'frequency1'],
|
||||
['tgid', 'group1'],
|
||||
],
|
||||
"grp_v_ch_grant_updt_exp": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'mfrid'],
|
||||
['p', 'options'],
|
||||
['frequency', 'frequency'],
|
||||
['tgid', 'group'],
|
||||
],
|
||||
"ack_resp_fne": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'aiv'],
|
||||
['p2', 'ex'],
|
||||
['p3', 'addl'],
|
||||
['wacn', 'wacn'],
|
||||
['suid', 'source'],
|
||||
['suid2', 'target'],
|
||||
],
|
||||
"deny_resp": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'aiv'],
|
||||
['p2', 'reason'],
|
||||
['p3', 'additional'],
|
||||
['suid', 'target'],
|
||||
],
|
||||
"grp_aff_resp": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'affiliation'],
|
||||
['p2', 'group_aff_value'],
|
||||
['tgid', 'announce_group'],
|
||||
['tgid2', 'group'],
|
||||
['suid', 'target'],
|
||||
],
|
||||
"grp_aff_q": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['suid', 'source'],
|
||||
['suid2', 'target'],
|
||||
],
|
||||
"loc_reg_resp": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'rv'],
|
||||
['p2', 'rfss'],
|
||||
['p3', 'siteid'],
|
||||
['tgid', 'group'],
|
||||
['suid', 'target'],
|
||||
],
|
||||
"u_reg_resp": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'rv'],
|
||||
['suid', 'source'],
|
||||
['suid2', 'target'],
|
||||
],
|
||||
"u_reg_cmd": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['suid', 'source'],
|
||||
['suid2', 'target'],
|
||||
],
|
||||
"u_de_reg_ack": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['wacn', 'wacn'],
|
||||
['suid', 'source'],
|
||||
],
|
||||
"ext_fnct_cmd": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'mfrid'],
|
||||
['p', 'efclass'],
|
||||
['p2', 'efoperand'],
|
||||
['suid', 'efargs'],
|
||||
],
|
||||
"end_call": [
|
||||
['time', 'time'],
|
||||
['sysid', 'sysid'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'code'],
|
||||
['suid', 'srcaddr'],
|
||||
['tgid', 'tgid'],
|
||||
['p2', 'duration'],
|
||||
['p3', 'count'],
|
||||
],
|
||||
}
|
||||
|
||||
# cc_event to numerical id (oplog and sql_dbi)
|
||||
cc_events = {
|
||||
"ack_resp_fne": 1,
|
||||
"deny_resp": 2,
|
||||
"end_call": 3,
|
||||
"ext_fnct_cmd": 4,
|
||||
"grg_exenc_cmd": 5,
|
||||
"grp_aff_q": 6,
|
||||
"grp_aff_resp": 7,
|
||||
"grp_v_ch_grant": 8,
|
||||
"grp_v_ch_grant_mbt": 9,
|
||||
"grp_v_ch_grant_updt": 10,
|
||||
"grp_v_ch_grant_updt_exp": 11,
|
||||
"loc_reg_resp": 12,
|
||||
"u_de_reg_ack": 13,
|
||||
"u_reg_cmd": 14,
|
||||
"u_reg_resp": 15,
|
||||
"mot_grg_cn_grant": 16,
|
||||
}
|
||||
|
||||
# sql column names to DataTables (Oplog)
|
||||
oplog_map = {
|
||||
"grp_v_ch_grant_mbt": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'MFRID'],
|
||||
['p', 'Options'],
|
||||
['frequency', 'Frequency'],
|
||||
['tgid', 'Talkgroup ID'],
|
||||
['tgid', 'Talkgroup'],
|
||||
['suid', 'Source ID'],
|
||||
['suid', 'Source'],
|
||||
],
|
||||
"grg_exenc_cmd": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'MFRID'],
|
||||
['tgid', 'SG (tgid)'],
|
||||
['p', 'Key ID'],
|
||||
],
|
||||
"grp_v_ch_grant": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'MFRID'],
|
||||
['p', 'Options'],
|
||||
['frequency', 'Frequency'],
|
||||
['tgid', 'Talkgroup ID'],
|
||||
['tgid', 'Talkgroup'],
|
||||
['suid', 'Source ID'],
|
||||
['suid', 'Source'],
|
||||
],
|
||||
"mot_grg_cn_grant": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'MFRID'],
|
||||
['frequency', 'Frequency'],
|
||||
['tgid', 'Talkgroup ID'],
|
||||
['tgid', 'Talkgroup'],
|
||||
['suid', 'Source ID'],
|
||||
['suid', 'Source'],
|
||||
],
|
||||
"grp_v_ch_grant_updt": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'MFRID'],
|
||||
['frequency', 'Frequency'],
|
||||
['tgid', 'Talkgroup ID'],
|
||||
['tgid', 'Talkgroup'],
|
||||
],
|
||||
"grp_v_ch_grant_updt_exp": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'mfrid'],
|
||||
['p', 'Options'],
|
||||
['frequency', 'Frequency'],
|
||||
['tgid', 'Talkgroup ID'],
|
||||
['tgid', 'Talkgroup'],
|
||||
],
|
||||
"ack_resp_fne": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'aiv'],
|
||||
['p2', 'ex'],
|
||||
['p3', 'Additional'],
|
||||
['wacn', 'wacn'],
|
||||
['suid', 'System Source'],
|
||||
['suid2', 'Target ID'],
|
||||
['suid2', 'Target'],
|
||||
],
|
||||
"deny_resp": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'aiv'],
|
||||
['p2', 'Reason'],
|
||||
['p3', 'Additional'],
|
||||
['suid', 'Target ID'],
|
||||
['suid', 'Target'],
|
||||
],
|
||||
"grp_aff_resp": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'Affiliation'],
|
||||
['p2', 'Group Aff Value'],
|
||||
['tgid', 'Announce Group'],
|
||||
['tgid2', 'Talkgroup ID'],
|
||||
['tgid2', 'Talkgroup'],
|
||||
['suid', 'Target ID'],
|
||||
['suid', 'Target'],
|
||||
],
|
||||
"grp_aff_q": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['suid', 'System Source'],
|
||||
['suid2', 'Target ID'],
|
||||
['suid2', 'Target'],
|
||||
],
|
||||
"loc_reg_resp": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'rv'],
|
||||
['p2', 'RFSS'],
|
||||
['p3', 'Site'],
|
||||
['tgid', 'Talkgroup ID'],
|
||||
['tgid', 'Talkgroup'],
|
||||
['suid', 'Target ID'],
|
||||
['suid', 'Target'],
|
||||
],
|
||||
"u_reg_resp": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'rv'],
|
||||
['suid', 'Source ID'],
|
||||
['suid', 'Source'],
|
||||
['suid2', 'Target'],
|
||||
],
|
||||
"u_reg_cmd": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['suid', 'System Source'],
|
||||
['suid2', 'Target ID'],
|
||||
['suid2', 'Target'],
|
||||
],
|
||||
"u_de_reg_ack": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['wacn', 'WACN'],
|
||||
['suid', 'Source ID'],
|
||||
['suid', 'Source'],
|
||||
],
|
||||
"ext_fnct_cmd": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['mfrid', 'MFRID'],
|
||||
['p', 'Class'],
|
||||
['p2', 'Operand'],
|
||||
['suid', 'System Source'],
|
||||
],
|
||||
"end_call": [
|
||||
['time', 'Time'],
|
||||
['sysid', 'System'],
|
||||
['opcode', 'opcode'],
|
||||
['cc_event', 'cc_event'],
|
||||
['p', 'Code'],
|
||||
['suid', 'Source ID'],
|
||||
['suid', 'Source'],
|
||||
['tgid', 'Talkgroup ID'],
|
||||
['tgid', 'Talkgroup'],
|
||||
['p2', 'Duration (ms)'],
|
||||
['p3', 'Count (p3)'],
|
||||
],
|
||||
}
|
||||
|
||||
# friendly long description strings, used in Oplog
|
||||
cc_desc = {
|
||||
"ack_resp_fne": "Acknowledge Response FNE - 0x20",
|
||||
"deny_resp": "Deny Response - 0x27",
|
||||
"end_call": "End Call (not a naitve control channel event)",
|
||||
"ext_fnct_cmd": "Extended Function Command - 0x24",
|
||||
"grg_exenc_cmd": "Harris Group Regroup Explicit Encryption Command - 0x30",
|
||||
"grp_aff_q": "Group Affiliation Query - 0x2A",
|
||||
"grp_aff_resp": "Group Affiliation Response - 0x28",
|
||||
"grp_v_ch_grant": "Group Voice Channel Grant - 0x00",
|
||||
"grp_v_ch_grant_mbt": "Group Voice Channel Grant, Multiple Block Trunking",
|
||||
"grp_v_ch_grant_updt": "Group Voice Channel Grant Update - 0x02",
|
||||
"grp_v_ch_grant_updt_exp": "Group Voice Channel Grant Update, Explicit - 0x03",
|
||||
"loc_reg_resp": "Location Registration Response 0x2B",
|
||||
"mot_grg_cn_grant": "Motorola Patch Channel Grant - 0x02",
|
||||
"u_de_reg_ack": "De-Registration Acknowledge (Logout) - 0x2F",
|
||||
"u_reg_cmd": "Unit Registration Command (Force Unit Registration) - 0x2D",
|
||||
"u_reg_resp": "Unit Registration Response - 0x2C"
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/liquidsoap
|
||||
|
||||
# Example liquidsoap streaming from op25 to icecast
|
||||
# (c) 2019, 2020 gnorbury@bondcar.com, wllmbecks@gmail.com
|
||||
#
|
||||
|
||||
set("log.stdout", true)
|
||||
set("log.file.path", "/home/pi/op25/op25/gr-op25_repeater/apps/liquidsoap.log")
|
||||
set("log.file", true)
|
||||
set("log.level", 3)
|
||||
|
||||
# Make the native sample rate compatible with op25
|
||||
set("frame.audio.samplerate", 8000)
|
||||
|
||||
# SOURCE INPUT BLOCK OPTIONS
|
||||
# Mono and stereo input sources are mutually exclusive. Choose type.
|
||||
|
||||
# Mono input source
|
||||
input = mksafe(input.external(buffer=0.25, channels=2, samplerate=8000, restart_on_error=false, "./audio.py -x 2 -s"))
|
||||
# Consider increasing the buffer value on slow systems such as RPi3. e.g. buffer=0.25
|
||||
# Longer buffer results in less choppy audio but at the expense of increased latency.
|
||||
|
||||
# Left channel input source and audio summing for two-slot protocols
|
||||
#left = input.external(buffer=0.25, channels=2, samplerate=8000, restart_on_error=false, "./audio.py -u 23450 -x 2 -s")
|
||||
#left = audio_to_stereo(left)
|
||||
#left = stereo.pan(pan=1., left)
|
||||
|
||||
# Right channel input source and audio summing for two-slot protocols
|
||||
#right = input.external(buffer=0.25, channels=2, samplerate=8000, restart_on_error=false, "./audio.py -u 23460 -x 2 -s")
|
||||
#right = audio_to_stereo(right)
|
||||
#right = stereo.pan(pan=-1., right)
|
||||
|
||||
# OPTIONAL AUDIO SIGNAL PROCESSING BLOCKS
|
||||
# Uncomment to enable
|
||||
#
|
||||
# High pass filter mono
|
||||
input = filter.iir.butterworth.high(frequency = 200.0, order = 4, input)
|
||||
|
||||
# High pass filter stereo
|
||||
#left = filter.iir.butterworth.high(frequency = 200.0, order = 4, left)
|
||||
#right = filter.iir.butterworth.high(frequency = 200.0, order = 4, right)
|
||||
|
||||
# Low pass filter mono
|
||||
input = filter.iir.butterworth.low(frequency = 3250.0, order = 4, input)
|
||||
|
||||
# Low pass filter stereo
|
||||
#left = filter.iir.butterworth.low(frequency = 3250.0, order = 4, left)
|
||||
#right = filter.iir.butterworth.low(frequency = 3250.0, order = 4, right)
|
||||
|
||||
# Normalization mono
|
||||
input = normalize(input, gain_max = 3.0, gain_min = -3.0, target = -16.0, threshold = -40.0)
|
||||
|
||||
# Normalization stereo -- Note -- Adjust target gains independently to achieve left/right balance
|
||||
#left = normalize(left, gain_max = 3.0, gain_min = -3.0, target = -16.0, threshold = -40.0)
|
||||
#right = normalize(right, gain_max = 3.0, gain_min = -3.0, target = -16.0, threshold = -40.0)
|
||||
|
||||
# Commnent out the line below for "non-stereo" (mono) output
|
||||
#input = mksafe(add(normalize=false, [left,right]))
|
||||
|
||||
# LOCAL AUDIO OUTPUT
|
||||
# Uncomment the line below to enable local sound
|
||||
#output.ao(input)
|
||||
|
||||
# ICECAST STREAMING
|
||||
# Uncomment to enable output to an icecast server
|
||||
# Change the "host", "password", and "mount" strings appropriately first!
|
||||
# For metadata to work properly, the host address given here MUST MATCH the address in op25's meta.json file
|
||||
#
|
||||
# Mono Stream
|
||||
output.icecast(%mp3(bitrate=16, samplerate=22050, stereo=false), description="op25", genre="Public Safety", url="", fallible=false, icy_metadata="false", host="localhost", port=8000, mount="op25", password="hackme", mean(input))
|
||||
#
|
||||
# Stereo Stream
|
||||
#output.icecast(%mp3(bitrate=16, samplerate=22050, stereo=true), description="op25", genre="Public Safety", url="", fallible=false, icy_metadata="false", host="localhost", port=8000, mount="op25", password="hackme", input)
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/liquidsoap
|
||||
|
||||
# Example liquidsoap hls streaming from op25 to nginx
|
||||
# (c) 2019, 2020 gnorbury@bondcar.com, wllmbecks@gmail.com
|
||||
# (c) 2020 KA1RBI
|
||||
|
||||
set("log.stdout", true)
|
||||
set("log.file", false)
|
||||
set("log.level", 1)
|
||||
|
||||
set("frame.audio.samplerate", 8000)
|
||||
|
||||
input = mksafe(input.external(buffer=0.02, channels=2, samplerate=8000, restart_on_error=false, "./audio.py -x 2 -s -S"))
|
||||
|
||||
output.external(%wav(stereo=false, channels=1, samplesize=16, header=false, samplerate=8000), fallible=false, flush=true, "./ffmpeg.sh", mean(input))
|
|
@ -0,0 +1,42 @@
|
|||
#! /bin/sh
|
||||
|
||||
# Copyright (c) 2020 OP25
|
||||
#
|
||||
# This file is part of OP25 and part of GNU Radio
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
#
|
||||
# this script should not be run directly - run ffmpeg.liq instead
|
||||
#
|
||||
# requires ffmpeg configured with --enable-libx264
|
||||
#
|
||||
|
||||
ffmpeg \
|
||||
-ar 8000 \
|
||||
-ac 1 \
|
||||
-acodec pcm_s16le \
|
||||
-f s16le \
|
||||
-i pipe:0 \
|
||||
-f image2 \
|
||||
-loop 1 \
|
||||
-i ../www/images/status.png \
|
||||
-vcodec libx264 \
|
||||
-pix_fmt yuv420p \
|
||||
-f flv \
|
||||
-acodec aac \
|
||||
-b:a 48k \
|
||||
rtmp://localhost/live/stream
|
|
@ -0,0 +1,806 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2011, 2012, 2013, 2014, 2015 Max H. Parke KA1RBI
|
||||
#
|
||||
# This file is part of OP25
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import json
|
||||
import threading
|
||||
import glob
|
||||
|
||||
from gnuradio import gr, eng_notation
|
||||
from gnuradio import blocks, audio
|
||||
from gnuradio.eng_option import eng_option
|
||||
import numpy as np
|
||||
from gnuradio import gr
|
||||
from math import pi, sin, cos
|
||||
|
||||
_def_debug = 0
|
||||
_def_sps = 10
|
||||
_def_cpm_mode = 'cpm'
|
||||
|
||||
GNUPLOT = '/usr/bin/gnuplot'
|
||||
|
||||
FFT_AVG = 0.25
|
||||
MIX_AVG = 0.15
|
||||
BAL_AVG = 0.05
|
||||
FFT_BINS = 512
|
||||
|
||||
def degrees(r):
|
||||
d = 360 * r / (2*pi)
|
||||
while d <0:
|
||||
d += 360
|
||||
while d > 360:
|
||||
d -= 360
|
||||
return d
|
||||
|
||||
def limit(a,lim):
|
||||
if a > lim:
|
||||
return lim
|
||||
return a
|
||||
|
||||
def ensure_str(s): # for python 2/3
|
||||
if isinstance(s[0], str):
|
||||
return s
|
||||
ns = ''
|
||||
for i in range(len(s)):
|
||||
ns += chr(s[i])
|
||||
return ns
|
||||
|
||||
PSEQ = 0
|
||||
|
||||
class wrap_gp(object):
|
||||
def __init__(self, sps=_def_sps, logfile=None, title="", color_cfg='plot-colors.json'):
|
||||
global PSEQ
|
||||
self.sps = sps
|
||||
self.center_freq = 0.0
|
||||
self.relative_freq = 0.0
|
||||
self.offset_freq = 0.0
|
||||
self.width = None
|
||||
self.ffts = ()
|
||||
self.freqs = ()
|
||||
self.avg_pwr = np.zeros(FFT_BINS)
|
||||
self.avg_sum_pwr = 0.0
|
||||
self.buf = np.array([])
|
||||
self.plot_count = 0
|
||||
self.last_plot = 0
|
||||
self.plot_interval = None
|
||||
self.sequence = 0
|
||||
self.output_dir = None
|
||||
self.filename = None
|
||||
self.logfile = logfile
|
||||
self.title = title
|
||||
self.sequence_id = PSEQ
|
||||
PSEQ += 1
|
||||
x = self.sequence_id % 3
|
||||
y = self.sequence_id // 3
|
||||
self.position = (x, y)
|
||||
|
||||
self.colors = {}
|
||||
self.colors['label_color'] = ''
|
||||
self.colors['tic_color'] = ''
|
||||
self.colors['border_color'] = ''
|
||||
self.colors['plot_color'] = ''
|
||||
self.colors['background_color'] = ''
|
||||
if color_cfg and os.access(color_cfg, os.R_OK):
|
||||
ccfg = json.loads(open(color_cfg).read())
|
||||
for color in ccfg:
|
||||
self.colors[color] = ccfg[color]
|
||||
|
||||
self.next_cpmd = time.time()
|
||||
|
||||
self.attach_gp()
|
||||
|
||||
def attach_gp(self):
|
||||
args = (GNUPLOT, '-noraise')
|
||||
exe = GNUPLOT
|
||||
self.gp = subprocess.Popen(args, executable=exe, stdin=subprocess.PIPE)
|
||||
|
||||
def kill(self):
|
||||
try:
|
||||
self.gp.stdin.close() # closing pipe should cause subprocess to exit
|
||||
except IOError:
|
||||
pass
|
||||
sleep_count = 0
|
||||
while True: # wait politely, but only for so long
|
||||
self.gp.poll()
|
||||
if self.gp.returncode is not None:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
if self.gp.returncode is not None:
|
||||
break
|
||||
sleep_count += 1
|
||||
if (sleep_count & 1) == 0:
|
||||
self.gp.kill()
|
||||
if sleep_count >= 3:
|
||||
break
|
||||
|
||||
def set_interval(self, v):
|
||||
self.plot_interval = v
|
||||
|
||||
def set_output_dir(self, v):
|
||||
self.output_dir = v
|
||||
|
||||
def set_sps(self, sps):
|
||||
self.sps = sps
|
||||
|
||||
def plot(self, buf, bufsz, mode='eye'):
|
||||
BUFSZ = bufsz
|
||||
consumed = min(len(buf), BUFSZ-len(self.buf))
|
||||
if len(self.buf) < BUFSZ:
|
||||
self.buf = np.concatenate((self.buf, buf[:int(consumed)]))
|
||||
if len(self.buf) < BUFSZ:
|
||||
return consumed
|
||||
|
||||
self.plot_count += 1
|
||||
if mode == 'eye' and self.plot_count % 20 != 0:
|
||||
self.buf = np.array([])
|
||||
return consumed
|
||||
|
||||
plots = []
|
||||
s = ''
|
||||
plot_size = (320,240)
|
||||
if mode == 'eye':
|
||||
nplots = len(self.buf) // self.sps - 2
|
||||
for i in range(nplots):
|
||||
s += '\n'.join(['%f' % self.buf[i*self.sps+j] for j in range(self.sps+1)])
|
||||
s += '\ne\n'
|
||||
plots.append('"-" with lines')
|
||||
elif mode == 'cpm':
|
||||
nplots = len(self.buf)
|
||||
ab = np.abs(self.buf)
|
||||
for i in range(len(ab)):
|
||||
s += '%f\n' % ab[i]
|
||||
s += 'e\n'
|
||||
plots.append('"-" with lines')
|
||||
elif mode == 'cpmd':
|
||||
if time.time() < self.next_cpmd:
|
||||
self.buf = np.array([])
|
||||
return 0
|
||||
self.next_cpmd = time.time() + 0.5
|
||||
ab = np.abs(self.buf)
|
||||
thresh = np.max(ab) / 2.0
|
||||
mask1 = np.array(ab > thresh, dtype=np.int)
|
||||
maskdf = mask1[1:] - mask1[:-1]
|
||||
nz = np.array(np.nonzero(maskdf), dtype=np.int)[0]
|
||||
nzd = nz[1:]-nz[:-1]
|
||||
nzdn = nzd // self.sps
|
||||
sel = (nzdn > 165) & (nzdn < 185)
|
||||
valid = np.array(np.nonzero(sel), dtype=np.int)[0]
|
||||
if len(valid) < 1:
|
||||
self.buf = np.array([])
|
||||
return 0
|
||||
v0 = valid[0]
|
||||
i0 = nz[v0]
|
||||
samples = self.buf[i0 : i0+170*self.sps+2]
|
||||
fmd = np.angle(samples[1:] * np.conj(samples[:-1]))
|
||||
fmd = fmd[12*self.sps:]
|
||||
for n in range(0,len(fmd),self.sps):
|
||||
sl = fmd[n:n+self.sps+1]
|
||||
if len(sl) != self.sps+1:
|
||||
break
|
||||
s += '\n'.join(['%f' % (x*self.sps) for x in sl])
|
||||
s += '\ne\n'
|
||||
plots.append('"-" with lines')
|
||||
elif mode == 'constellation':
|
||||
plot_size = (240,240)
|
||||
self.buf = self.buf[:100]
|
||||
for b in self.buf:
|
||||
s += '%f\t%f\n' % (degrees(np.angle(b)), limit(np.abs(b),1.0))
|
||||
s += 'e\n'
|
||||
plots.append('"-" with points')
|
||||
for b in self.buf:
|
||||
#s += '%f\t%f\n' % (b.real, b.imag)
|
||||
s += '%f\t%f\n' % (degrees(np.angle(b)), limit(np.abs(b),1.0))
|
||||
s += 'e\n'
|
||||
plots.append('"-" with lines')
|
||||
elif mode == 'symbol':
|
||||
for b in self.buf:
|
||||
s += '%f\n' % (b)
|
||||
s += 'e\n'
|
||||
plots.append('"-" with points')
|
||||
elif mode == 'fftf':
|
||||
self.ffts = np.fft.rfft(self.buf * np.blackman(BUFSZ)) / (0.42 * BUFSZ)
|
||||
#self.ffts = np.fft.fftshift(self.ffts)
|
||||
self.ffts = np.abs(self.ffts) ** 2.0
|
||||
self.ffts /= np.max(self.ffts)
|
||||
for i in range(len(self.ffts)):
|
||||
s += '%f\n' % (self.ffts[i])
|
||||
s += 'e\n'
|
||||
plots.append('"-" with lines')
|
||||
elif mode == 'fft' or mode == 'mixer':
|
||||
sum_pwr = 0.0
|
||||
self.ffts = np.fft.fft(self.buf * np.blackman(BUFSZ)) / (0.42 * BUFSZ)
|
||||
self.ffts = np.fft.fftshift(self.ffts)
|
||||
self.freqs = np.fft.fftfreq(len(self.ffts))
|
||||
self.freqs = np.fft.fftshift(self.freqs)
|
||||
tune_freq = (self.center_freq - self.relative_freq) / 1e6
|
||||
if self.center_freq and self.width:
|
||||
self.freqs = ((self.freqs * self.width) + self.center_freq + self.offset_freq) / 1e6
|
||||
for i in range(len(self.ffts)):
|
||||
if mode == 'fft':
|
||||
self.avg_pwr[i] = ((1.0 - FFT_AVG) * self.avg_pwr[i]) + (FFT_AVG * np.abs(self.ffts[i]))
|
||||
else:
|
||||
self.avg_pwr[i] = ((1.0 - MIX_AVG) * self.avg_pwr[i]) + (MIX_AVG * np.abs(self.ffts[i]))
|
||||
s += '%f\t%f\n' % (self.freqs[i], 20 * np.log10(self.avg_pwr[i]))
|
||||
if (mode == 'mixer') and (self.avg_pwr[i] > 1e-5):
|
||||
if (self.freqs[i] - self.center_freq) < 0:
|
||||
sum_pwr -= self.avg_pwr[i]
|
||||
elif (self.freqs[i] - self.center_freq) > 0:
|
||||
sum_pwr += self.avg_pwr[i]
|
||||
self.avg_sum_pwr = ((1.0 - BAL_AVG) * self.avg_sum_pwr) + (BAL_AVG * sum_pwr)
|
||||
s += 'e\n'
|
||||
plots.append('"-" with lines')
|
||||
elif mode == 'float' or mode == 'correlation':
|
||||
for b in self.buf:
|
||||
s += '%f\n' % (b)
|
||||
s += 'e\n'
|
||||
plots.append('"-" with lines')
|
||||
elif mode == 'sync':
|
||||
s_abs = np.abs(self.buf)
|
||||
sums = np.zeros(self.sps)
|
||||
for i in range(self.sps):
|
||||
sums[i] = np.sum(s_abs[range(i, len(self.buf), self.sps)])
|
||||
am = np.argmax(sums)
|
||||
samples = self.buf[am:]
|
||||
|
||||
a1 = -np.angle(samples[0])
|
||||
rz = cos(a1) + 1j * sin(a1)
|
||||
|
||||
while len(samples) >= self.sps+1:
|
||||
for i in range(self.sps+1):
|
||||
z = samples[i] * rz
|
||||
s += '%f\t%f\n' % (z.real, z.imag)
|
||||
s += 'e\n'
|
||||
plots.append('"-" with linespoints')
|
||||
samples = samples[self.sps:]
|
||||
|
||||
self.buf = np.array([])
|
||||
|
||||
# FFT processing needs to be completed to maintain the weighted average buckets
|
||||
# regardless of whether we actually produce a new plot or not.
|
||||
if self.plot_interval and self.last_plot + self.plot_interval > time.time():
|
||||
return consumed
|
||||
self.last_plot = time.time()
|
||||
|
||||
filename = None
|
||||
if self.output_dir:
|
||||
if self.sequence >= 2:
|
||||
delete_pathname = '%s/plot-%s%d-%d.png' % (self.output_dir, mode, self.sequence_id, self.sequence-2)
|
||||
if os.access(delete_pathname, os.W_OK):
|
||||
os.remove(delete_pathname)
|
||||
h0= 'set terminal png size %d, %d\n' % (plot_size)
|
||||
filename = 'plot-%s%d-%d.png' % (mode, self.sequence_id, self.sequence)
|
||||
h0 += 'set output "%s/%s"\n' % (self.output_dir, filename)
|
||||
self.sequence += 1
|
||||
else:
|
||||
pos = ''
|
||||
if self.position is not None:
|
||||
x = self.position[0] * plot_size[0]
|
||||
y = self.position[1] * plot_size[1]
|
||||
x += 50
|
||||
y += 75
|
||||
pos = ' position %d, %d' % (x, y)
|
||||
h0= 'set terminal x11 noraise size %d, %d%s title "%s"\n' % (plot_size[0], plot_size[1], pos, self.title)
|
||||
background = ''
|
||||
|
||||
label_color = ''
|
||||
tic_color = ''
|
||||
border_color = ''
|
||||
plot_color = ''
|
||||
background_color = ''
|
||||
|
||||
if self.colors['label_color']:
|
||||
label_color = 'textcolor rgb"%s"' % self.colors['label_color']
|
||||
if self.colors['tic_color']:
|
||||
tic_color = 'textcolor rgb"%s"' % self.colors['tic_color']
|
||||
if self.colors['border_color']:
|
||||
border_color = 'linecolor rgb"%s"' % self.colors['border_color']
|
||||
if self.colors['plot_color']:
|
||||
plot_color = 'linecolor rgb"%s"' % self.colors['plot_color']
|
||||
if self.colors['background_color']:
|
||||
background_color = 'fillcolor rgb"%s"' % self.colors['background_color']
|
||||
|
||||
background += 'set object 1 rectangle from screen 0,0 to screen 1,1 %s behind\n' % (background_color)
|
||||
background += 'set xtics %s\n' % (tic_color)
|
||||
background += 'set ytics %s\n' % (tic_color)
|
||||
background += 'set border %s\n' % (border_color)
|
||||
|
||||
h = 'set key off\n'
|
||||
if mode == 'constellation':
|
||||
#h+= background
|
||||
plot_color = ''
|
||||
h+= 'set size square\n'
|
||||
h+= 'set xrange [-1:1]\n'
|
||||
h+= 'set yrange [-1:1]\n'
|
||||
h += 'unset border\n'
|
||||
h += 'set polar\n'
|
||||
h += 'set angles degrees\n'
|
||||
h += 'unset raxis\n'
|
||||
h += 'set object 1 rectangle from screen 0,0 to screen 1,1 %s behind\n' % (background_color)
|
||||
h += 'set object 2 circle at 0,0 size 1 fillcolor rgb 0x0f01 fillstyle solid behind\n'
|
||||
h += 'set object 3 circle at 0,0 size 1 %s\n' % 'linecolor rgb"#0000f0"'
|
||||
h += 'set style line 10 lt 1 lc rgb 0x404040 lw 0.1\n'
|
||||
h += 'set grid polar 45\n'
|
||||
h += 'set grid ls 10\n'
|
||||
h += 'set xtics axis\n'
|
||||
h += 'set ytics axis\n'
|
||||
h += 'set xtics scale 0\n'
|
||||
h += 'set xtics ("" 0.2, "" 0.4, "" 0.6, "" 0.8, "" 1)\n'
|
||||
h += 'set ytics 0, 0.2, 1\n'
|
||||
h += 'set format ""\n'
|
||||
h += 'set style line 11 lt 1 lw 2 pt 2 ps 2\n'
|
||||
|
||||
h+= 'set title "Constellation %s" %s\n' % (self.title, label_color)
|
||||
elif mode == 'eye':
|
||||
h+= background
|
||||
h+= 'set yrange [-4:4]\n'
|
||||
h+= 'set title "Datascope %s" %s\n' % (self.title, label_color)
|
||||
plot_color = ''
|
||||
elif mode == 'cpm':
|
||||
h+= background
|
||||
#h+= 'set yrange [-4:4]\n'
|
||||
h+= 'set title "CPM RSSI %s" %s\n' % (self.title, label_color)
|
||||
#plot_color = ''
|
||||
elif mode == 'cpmd':
|
||||
h+= background
|
||||
h+= 'set yrange [-4:4]\n'
|
||||
h+= 'set title "CPM Datascope %s" %s\n' % (self.title, label_color)
|
||||
plot_color = ''
|
||||
elif mode == 'sync':
|
||||
h += 'set object 1 rect from screen 0,0 to screen 1,1 %s behind\n' % (background_color)
|
||||
h += 'set size square\n'
|
||||
h += 'set xtics %s\n' % (tic_color)
|
||||
h += 'set ytics %s\n' % (tic_color)
|
||||
h += 'set border %s\n' % (border_color)
|
||||
elif mode == 'symbol':
|
||||
h+= background
|
||||
h+= 'set yrange [-4:4]\n'
|
||||
h+= 'set title "Symbol %s" %s\n' % (self.title, label_color)
|
||||
elif mode == 'fft' or mode == 'mixer':
|
||||
h+= background
|
||||
h+= 'unset arrow; unset title\n'
|
||||
h+= 'set xrange [%f:%f]\n' % (self.freqs[0], self.freqs[len(self.freqs)-1])
|
||||
h+= 'set xlabel "Frequency"\n'
|
||||
h+= 'set ylabel "Power(dB)"\n'
|
||||
h+= 'set grid\n'
|
||||
h+= 'set yrange [-100:0]\n'
|
||||
if mode == 'mixer': # mixer
|
||||
h+= 'set title "Mixer %s: balance %3.0f (smaller is better)" %s\n' % (self.title, np.abs(self.avg_sum_pwr * 1000), label_color)
|
||||
else: # fft
|
||||
h+= 'set title "Spectrum %s" %s\n' % (self.title, label_color)
|
||||
if self.center_freq:
|
||||
arrow_pos = (self.center_freq - self.relative_freq) / 1e6
|
||||
h+= 'set arrow from %f, graph 0 to %f, graph 1 nohead\n' % (arrow_pos, arrow_pos)
|
||||
h+= 'set title "Spectrum: tuned to %f Mhz" %s\n' % (arrow_pos, label_color)
|
||||
elif mode == 'fftf':
|
||||
h+= 'set yrange [-1:1.2]\n'
|
||||
h+= 'set title "fftf"\n'
|
||||
elif mode == 'float':
|
||||
h+= background
|
||||
h+= 'set yrange [-2:2]\n'
|
||||
h+= 'set title "Oscilloscope %s" %s\n' % (self.title, label_color)
|
||||
elif mode == 'correlation':
|
||||
h+= background
|
||||
title = 'Correlation'
|
||||
if self.title:
|
||||
title = self.title
|
||||
h+= 'set yrange [-1.1:1.1]\n'
|
||||
h+= 'set title "%s" %s\n' % (title, label_color)
|
||||
if self.output_dir:
|
||||
s += 'set output\n' ## flush output png
|
||||
dat = '%s%splot %s %s\n%s' % (h0, h, ','.join(plots), plot_color, s)
|
||||
if self.logfile is not None:
|
||||
with open(self.logfile, 'a') as fd:
|
||||
fd.write(dat)
|
||||
if sys.version[0] != '2':
|
||||
dat = bytes(dat, 'utf8')
|
||||
self.gp.poll()
|
||||
if self.gp.returncode is None: # make sure gnuplot is still running
|
||||
try:
|
||||
rc = self.gp.stdin.write(dat)
|
||||
except (IOError, ValueError):
|
||||
pass
|
||||
try:
|
||||
self.gp.stdin.flush()
|
||||
except (IOError, ValueError):
|
||||
pass
|
||||
if filename:
|
||||
self.filename = filename
|
||||
return consumed
|
||||
|
||||
def set_center_freq(self, f):
|
||||
self.center_freq = f
|
||||
|
||||
def set_relative_freq(self, f):
|
||||
self.relative_freq = f
|
||||
|
||||
def set_offset(self, f):
|
||||
self.offset_freq = f
|
||||
|
||||
def set_width(self, w):
|
||||
self.width = w
|
||||
|
||||
def set_logfile(self, logfile=None):
|
||||
self.logfile = logfile
|
||||
|
||||
def set_title(self, title):
|
||||
self.title = title
|
||||
|
||||
class eye_sink_f(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug, sps = _def_sps):
|
||||
gr.sync_block.__init__(self,
|
||||
name="eye_sink_f",
|
||||
in_sig=[np.float32],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.sps = sps
|
||||
self.gnuplot = wrap_gp(sps=self.sps)
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
in0 = input_items[0]
|
||||
consumed = self.gnuplot.plot(in0, 100*self.sps, mode='eye')
|
||||
return consumed ### len(input_items[0])
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
class cpm_sink_c(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug, sps = _def_sps, plot_mode=_def_cpm_mode):
|
||||
gr.sync_block.__init__(self,
|
||||
name="cpm_sink_c",
|
||||
in_sig=[np.complex64],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.sps = sps
|
||||
self.gnuplot = wrap_gp(sps=self.sps)
|
||||
self.plot_mode=plot_mode
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
in0 = input_items[0]
|
||||
l = len(in0)
|
||||
consumed = self.gnuplot.plot(in0, self.sps*180*10, mode=self.plot_mode)
|
||||
return len(input_items[0])
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
class constellation_sink_c(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug):
|
||||
gr.sync_block.__init__(self,
|
||||
name="constellation_sink_c",
|
||||
in_sig=[np.complex64],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.gnuplot = wrap_gp()
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
in0 = input_items[0]
|
||||
self.gnuplot.plot(in0, 1000, mode='constellation')
|
||||
return len(input_items[0])
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
class fft_sink_f(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug):
|
||||
gr.sync_block.__init__(self,
|
||||
name="fft_sink_f",
|
||||
in_sig=[np.float32],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.gnuplot = wrap_gp()
|
||||
self.skip = 0
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
self.skip += 1
|
||||
if self.skip >= 50:
|
||||
self.skip = 0
|
||||
in0 = input_items[0]
|
||||
self.gnuplot.plot(in0, FFT_BINS, mode='fftf')
|
||||
return len(input_items[0])
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
class fft_sink_c(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug):
|
||||
gr.sync_block.__init__(self,
|
||||
name="fft_sink_c",
|
||||
in_sig=[np.complex64],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.gnuplot = wrap_gp()
|
||||
self.skip = 0
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
self.skip += 1
|
||||
if self.skip >= 50:
|
||||
self.skip = 0
|
||||
in0 = input_items[0]
|
||||
self.gnuplot.plot(in0, FFT_BINS, mode='fft')
|
||||
return len(input_items[0])
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
def set_center_freq(self, f):
|
||||
self.gnuplot.set_center_freq(f)
|
||||
self.gnuplot.set_relative_freq(0.0)
|
||||
|
||||
def set_relative_freq(self, f):
|
||||
self.gnuplot.set_relative_freq(f)
|
||||
|
||||
def set_offset(self, f):
|
||||
self.gnuplot.set_offset(f)
|
||||
|
||||
def set_width(self, w):
|
||||
self.gnuplot.set_width(w)
|
||||
|
||||
class mixer_sink_c(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug):
|
||||
gr.sync_block.__init__(self,
|
||||
name="mixer_sink_c",
|
||||
in_sig=[np.complex64],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.gnuplot = wrap_gp()
|
||||
self.skip = 0
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
self.skip += 1
|
||||
if self.skip >= 10:
|
||||
self.skip = 0
|
||||
in0 = input_items[0]
|
||||
self.gnuplot.plot(in0, FFT_BINS, mode='mixer')
|
||||
return len(input_items[0])
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
class sync_plot(threading.Thread):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug, block = None, **kwds):
|
||||
threading.Thread.__init__ (self, **kwds)
|
||||
self.setDaemon(1)
|
||||
self.SLEEP_TIME = 3 ## TODO - make more configurable
|
||||
self.sleep_until = time.time() + self.SLEEP_TIME
|
||||
self.last_file_time = time.time()
|
||||
self.keep_running = True
|
||||
self.debug = debug
|
||||
self.warned = False
|
||||
|
||||
block.enable_sync_plot(True) # block must refer to a gardner/costas instance
|
||||
self.blk_id = block.unique_id()
|
||||
|
||||
self.gnuplot = wrap_gp(sps = _def_sps)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
while self.keep_running == True:
|
||||
curr_time = time.time()
|
||||
if curr_time < self.sleep_until:
|
||||
time.sleep(1.0)
|
||||
if self.keep_running == False:
|
||||
break
|
||||
else:
|
||||
self.sleep_until = time.time() + self.SLEEP_TIME
|
||||
self.check_update()
|
||||
|
||||
def read_raw_file(self, fn):
|
||||
s = open(fn, 'rb').read()
|
||||
s_msg = ensure_str(s)
|
||||
p = s_msg.find('\n')
|
||||
if p < 1 or p > 24:
|
||||
return None # error
|
||||
hdrline = s_msg[:p]
|
||||
rest = s[p+1:]
|
||||
params = hdrline.split()
|
||||
params = [int(p) for p in params] #idx, p1p2, sps, error
|
||||
idx = params[0]
|
||||
p1p2 = params[1]
|
||||
sps = params[2]
|
||||
error_amt = params[3]
|
||||
self.gnuplot.set_sps(sps)
|
||||
if error_amt != 0:
|
||||
self.set_title("Tuning Error %d" % error_amt)
|
||||
else:
|
||||
self.set_title("")
|
||||
samples = np.frombuffer(rest, dtype=np.complex64)
|
||||
samples2 = np.concatenate((samples[idx:], samples[:idx]))
|
||||
needed = sps * 25 if p1p2 == 1 else sps * 21
|
||||
if len(samples2) < needed:
|
||||
if not self.warned:
|
||||
self.warned = True
|
||||
sys.stderr.write('read_raw_file: insufficient samples %d, needed %d\n' % (needed, len(samples2)))
|
||||
elif len(samples2) > needed:
|
||||
trim = len(samples2) - needed
|
||||
samples2 = samples2[trim:]
|
||||
return samples2 # return trimmed buf in np.complex64 format
|
||||
|
||||
def check_update(self):
|
||||
patt = 'sample-%d*.dat' % (self.blk_id)
|
||||
names = glob.glob(patt)
|
||||
if len(names) < 1: # no files to work with
|
||||
return
|
||||
d = {n: os.stat(n).st_mtime for n in names}
|
||||
ds = sorted(d.items(), key=lambda x:x[1], reverse = True)[0]
|
||||
if ds[1] <= self.last_file_time:
|
||||
return
|
||||
self.last_file_time = ds[1]
|
||||
dat = self.read_raw_file(ds[0])
|
||||
self.gnuplot.plot(dat, len(dat), mode='sync')
|
||||
|
||||
def kill(self):
|
||||
self.keep_running = False
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
class symbol_sink_f(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug):
|
||||
gr.sync_block.__init__(self,
|
||||
name="symbol_sink_f",
|
||||
in_sig=[np.float32],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.gnuplot = wrap_gp()
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
in0 = input_items[0]
|
||||
self.gnuplot.plot(in0, 2400, mode='symbol')
|
||||
return len(input_items[0])
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
class float_sink_f(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, debug = _def_debug):
|
||||
gr.sync_block.__init__(self,
|
||||
name="float_sink_f",
|
||||
in_sig=[np.float32],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.gnuplot = wrap_gp()
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
in0 = input_items[0]
|
||||
self.gnuplot.plot(in0, 2000, mode='float')
|
||||
return len(input_items[0])
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
class correlation_sink_f(gr.sync_block):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, sps=_def_sps, debug = _def_debug):
|
||||
gr.sync_block.__init__(self,
|
||||
name="plot_sink_f",
|
||||
in_sig=[np.float32],
|
||||
out_sig=None)
|
||||
self.debug = debug
|
||||
self.sps = sps
|
||||
self.gnuplot = wrap_gp()
|
||||
self.fs = []
|
||||
self.cbuf = np.array([])
|
||||
self.ignore = 0
|
||||
self.pktlen = 1024
|
||||
|
||||
def set_length(self, l):
|
||||
self.pktlen = l
|
||||
|
||||
def set_title(self, title):
|
||||
self.gnuplot.set_title(title)
|
||||
|
||||
def set_signature(self, fs):
|
||||
self.fs = []
|
||||
for s in fs:
|
||||
for i in range(self.sps):
|
||||
self.fs.append(s)
|
||||
self.fs.reverse() # reverse order for np.convolve
|
||||
self.fs = np.array(self.fs)
|
||||
|
||||
def work(self, input_items, output_items):
|
||||
if len(self.cbuf) == 0 and self.ignore > 0:
|
||||
self.ignore -= len(input_items[0])
|
||||
if self.ignore < 0:
|
||||
self.ignore = 0
|
||||
return len(input_items[0])
|
||||
if len(self.fs) == 0:
|
||||
return len(input_items[0])
|
||||
in0 = input_items[0]
|
||||
self.cbuf = np.append(self.cbuf, in0)
|
||||
if len(self.cbuf) < self.pktlen:
|
||||
return len(input_items[0])
|
||||
result = np.convolve(self.cbuf[:self.pktlen], self.fs)
|
||||
hi = np.max(np.abs(result))
|
||||
if hi != 0:
|
||||
result = result / hi
|
||||
self.cbuf = []
|
||||
self.ignore = 3000 * self.sps
|
||||
self.gnuplot.plot(result, len(result), mode='correlation')
|
||||
return len(input_items[0])
|
||||
|
||||
def kill(self):
|
||||
self.gnuplot.kill()
|
||||
|
||||
def setup_correlation(sps, title, connect_bb):
|
||||
CFG_FILE = 'correlation.json'
|
||||
if not os.access(CFG_FILE, os.R_OK):
|
||||
sys.stderr.write('correlation plot ignored, missing config file %s\n' % CFG_FILE)
|
||||
return []
|
||||
ccfg = json.loads(open(CFG_FILE).read())
|
||||
sinks = []
|
||||
for cfg in ccfg:
|
||||
sink = correlation_sink_f(sps=sps)
|
||||
sink.set_title('%s %s' % (title, cfg['name']))
|
||||
l = cfg['length'] * sps * 4
|
||||
LENGTH_LIMIT = 10000
|
||||
if l > LENGTH_LIMIT:
|
||||
l = LENGTH_LIMIT
|
||||
sink.set_length(l)
|
||||
sink.set_signature(cfg['fs'])
|
||||
connect_bb('baseband_amp', sink)
|
||||
sinks.append(sink)
|
||||
return sinks
|
|
@ -0,0 +1,350 @@
|
|||
|
||||
% P25 H-CPM Demodulator (C) Copyright 2022 Max H. Parke KA1RBI
|
||||
% % Experimental H-CPM Demodulator - Release 0 %
|
||||
%
|
||||
% This file is part of OP25
|
||||
%
|
||||
% OP25 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 3, or (at your option)
|
||||
% any later version.
|
||||
%
|
||||
% OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
% Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
% 02110-1301, USA.
|
||||
%
|
||||
%
|
||||
%
|
||||
% Accepts input IF file in GR binary complex64 format;
|
||||
% file must be at sample rate 24,000 samples/sec.
|
||||
%
|
||||
% this implementation has several simplifications, shortcuts,
|
||||
% and pitfalls, some of which are:
|
||||
%
|
||||
% * input sample must be clean and error free, there is
|
||||
% currently no tree search (viterbi) stage - decode errors
|
||||
% that should be recoverable are not autocorrected
|
||||
% * since the first and last one-quarter of the partial-response
|
||||
% period (in L=4) appear to contribute a negligible delta-phase,
|
||||
% this correlator ignores these intervals, using only the inner-
|
||||
% most two periods - accordingly, the value L=2 is assumed
|
||||
% * the code is too slow to demod in real time and there are no claims
|
||||
% to efficiency
|
||||
% * at each symbol period the received waveform vector is derotated.
|
||||
% this reduces the number of rows in the waveform matrix by a factor of
|
||||
% six (6); instead of derotating the samples, a correct correlator would
|
||||
% include these (approx. 80) additional rows
|
||||
%
|
||||
|
||||
pkg load signal;
|
||||
|
||||
global WI0 = [
|
||||
1.000000, 0.999827, 0.998111, 0.963512, 0.819784, 0.500000,
|
||||
1.000000, 0.958473, 0.800512, 0.483695, 0.022053, -0.475169,
|
||||
1.000000, 0.978152, 0.913558, 0.808999, 0.669116, 0.500000,
|
||||
1.000000, 0.966921, 0.819689, 0.504702, 0.047650, -0.424375,
|
||||
1.000000, 0.984174, 0.926374, 0.822966, 0.687929, 0.548428,
|
||||
1.000000, 0.918644, 0.736098, 0.572238, 0.506066, 0.548428,
|
||||
1.000000, 0.995165, 0.986853, 0.986153, 0.994884, 0.999596,
|
||||
1.000000, 0.947207, 0.867963, 0.865831, 0.946296, 0.999596,
|
||||
1.000000, 0.869202, 0.540278, 0.146957, -0.204506, -0.475169,
|
||||
1.000000, 0.884236, 0.567518, 0.170815, -0.179370, -0.424375,
|
||||
1.000000, 0.827032, 0.340050, -0.286034, -0.793734, -0.998382,
|
||||
1.000000, 0.905842, 0.713560, 0.552256, 0.483811, 0.500000,
|
||||
1.000000, 0.936720, 0.851252, 0.853489, 0.937706, 0.999596,
|
||||
1.000000, 0.998757, 0.999587, 0.969698, 0.834181, 0.548428,
|
||||
1.000000, 0.844204, 0.370632, -0.262797, -0.777895, -0.993535,
|
||||
1.000000, 0.991608, 0.981038, 0.981858, 0.991970, 0.999596,
|
||||
1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
|
||||
0.99179 0.98062998 0.95472002 0.92157 0.89235997 0.87256002];
|
||||
|
||||
global WQ0 = [
|
||||
0.000000, -0.018594, 0.061430, 0.267665, 0.572673, 0.866026,
|
||||
0.000000, 0.285183, 0.599317, 0.875237, 0.999757, 0.879895,
|
||||
0.000000, 0.207893, 0.406709, 0.587810, 0.743158, 0.866026,
|
||||
0.000000, -0.255075, -0.572808, -0.863294, -0.998864, -0.905486,
|
||||
0.000000, -0.177207, -0.376605, -0.568091, -0.725778, -0.836198,
|
||||
0.000000, -0.395087, -0.676875, -0.820088, -0.862495, -0.836198,
|
||||
0.000000, -0.098212, -0.161618, -0.165838, -0.101028, 0.028438,
|
||||
0.000000, -0.320622, -0.496628, -0.500337, -0.323300, 0.028438,
|
||||
0.000000, 0.494458, 0.841486, 0.989143, 0.978865, 0.879895,
|
||||
0.000000, -0.467040, -0.823361, -0.985303, -0.983782, -0.905486,
|
||||
0.000000, 0.562154, 0.940407, 0.958220, 0.608265, 0.056855,
|
||||
0.000000, 0.423616, 0.700594, 0.833674, 0.875173, 0.866025,
|
||||
0.000000, 0.350080, 0.524757, 0.521112, 0.347429, 0.028438,
|
||||
0.000000, 0.049845, -0.028745, -0.244306, -0.551491, -0.836198,
|
||||
0.000000, -0.536021, -0.928780, -0.964851, -0.628394, -0.113525,
|
||||
0.000000, 0.129280, 0.193816, 0.189618, 0.126474, 0.028438,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0. 0.13601001 0.26787999 0.37345001 0.44356 0.48087999];
|
||||
|
||||
global S_A = zeros(18,"int");
|
||||
global S_B = zeros(18,"int");
|
||||
S_A(1) = -1;
|
||||
S_B(1) = 3;
|
||||
S_A(2) = 1;
|
||||
S_B(2) = 3;
|
||||
S_A(3) = 1;
|
||||
S_B(3) = 1;
|
||||
S_A(4) = -1;
|
||||
S_B(4) = -3;
|
||||
S_A(5) = -1;
|
||||
S_B(5) = -1;
|
||||
S_A(6) = -3;
|
||||
S_B(6) = 1;
|
||||
S_A(7) = -1;
|
||||
S_B(7) = 1;
|
||||
S_A(8) = -3;
|
||||
S_B(8) = 3;
|
||||
S_A(9) = 3;
|
||||
S_B(9) = 1;
|
||||
S_A(10) = -3;
|
||||
S_B(10) = -1;
|
||||
S_A(11) = 3;
|
||||
S_B(11) = 3;
|
||||
S_A(12) = 3;
|
||||
S_B(12) = -1;
|
||||
S_A(13) = 3;
|
||||
S_B(13) = -3;
|
||||
S_A(14) = 1;
|
||||
S_B(14) = -3;
|
||||
S_A(15) = -3;
|
||||
S_B(15) = -3;
|
||||
S_A(16) = 1;
|
||||
S_B(16) = -1;
|
||||
S_A(17) = 0;
|
||||
S_B(17) = 0;
|
||||
S_A(18) = 1;
|
||||
S_B(18) = 1;
|
||||
|
||||
global L = 4; %sps
|
||||
global M = 10 % interp factor
|
||||
global LM = L*M;
|
||||
global Q = 8; % decim amount
|
||||
global NEWSPS = 5; % after decimation
|
||||
global K=360.0 / (2 * pi); % radians -> degrees
|
||||
global k = 6.0 / (2 * pi);
|
||||
|
||||
% change name of input data file
|
||||
fname = 'if-24000-IQ.dat';
|
||||
|
||||
function samples = load_text(fname)
|
||||
fid = fopen(fname, 'r');
|
||||
nn = 0;
|
||||
while 1
|
||||
nn = nn+1;
|
||||
s = fgetl(fid);
|
||||
if s == -1
|
||||
break;
|
||||
endif
|
||||
res = sscanf(s, "%f\t%f");
|
||||
samples(nn) = res(1) + 1j*res(2);
|
||||
endwhile
|
||||
g = max(abs(samples));
|
||||
samples = samples / g;
|
||||
endfunction
|
||||
|
||||
function amt_left = process_dat(dat)
|
||||
global L
|
||||
iq = dat(1:2:end-1) .+ 1j * dat(2:2:end);
|
||||
a = abs(iq);
|
||||
thresh = max(a) / 2.0;
|
||||
msk = a > thresh;
|
||||
m1 = msk(2:end) - msk(1:end-1);
|
||||
f = find(m1);
|
||||
m1f = m1(f)(end);
|
||||
lens = f(2:end) - f(1:end-1);
|
||||
l1 = (lens/L > 170 & lens/L < 180) | (lens/L > 350 & lens/L < 360);
|
||||
l2 = m1(f) == 1;
|
||||
l2 = l2(1:end-1);
|
||||
found = find(l1 & l2);
|
||||
if(length(found)) < 1
|
||||
amt_left = 0;
|
||||
return;
|
||||
endif
|
||||
for n = 1:length(found)
|
||||
valid = found(n);
|
||||
start1 = f(valid);
|
||||
len1 = lens(valid);
|
||||
demod_frag(iq(start1:start1+len1));
|
||||
end
|
||||
validn = found(end);
|
||||
startn = f(validn);
|
||||
lenn = lens(validn);
|
||||
datlen=length(dat);
|
||||
amt_left = length(dat) - (startn + lenn);
|
||||
if (amt_left < 0)
|
||||
amt_left = 0;
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function process_file(filename)
|
||||
global L
|
||||
bufsize = L * 180 * 12;
|
||||
fid = fopen(filename, 'r');
|
||||
save = [];
|
||||
while 1
|
||||
[dat, l] = fread(fid, bufsize, 'float32', 0);
|
||||
if l < 1
|
||||
break
|
||||
endif
|
||||
savel=size(save);
|
||||
datl=size(dat);
|
||||
concat = [save.' dat.'].';
|
||||
amt_left = process_dat(concat);
|
||||
if (amt_left > 0)
|
||||
save = concat(end-amt_left:end);
|
||||
else
|
||||
save = [];
|
||||
endif
|
||||
endwhile
|
||||
endfunction
|
||||
|
||||
function large_frag(msg)
|
||||
msgl = length(msg);
|
||||
padsz = 360 - msgl;
|
||||
lenh = round(length(msg) / 2);
|
||||
fmsg1 = frame_msg(msg(1:lenh),1);
|
||||
decode_msg(fmsg1);
|
||||
fmsg2 = frame_msg(msg(lenh:end),2);
|
||||
decode_msg(fmsg2);
|
||||
endfunction
|
||||
|
||||
function msg=demod_frag(frag)
|
||||
global L
|
||||
global NEWSPS
|
||||
global M
|
||||
amt_trim = mod(length(frag), L);
|
||||
frag = frag(1:end-amt_trim);
|
||||
g = max(abs(frag));
|
||||
frag = frag / g;
|
||||
nsyms = length(frag) / L;
|
||||
intrp0 = interp(frag, M);
|
||||
resampq=timing_sync(intrp0);
|
||||
nsyms = length(resampq) / NEWSPS;
|
||||
resamp1=frequency_sync(resampq);
|
||||
msg=correlation(resamp1);
|
||||
if length(msg) > 270
|
||||
large_frag(msg);
|
||||
else
|
||||
fmsg=frame_msg(msg,1);
|
||||
decode_msg(fmsg);
|
||||
lfmsg=length(fmsg);
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function decode_msg(msg)
|
||||
if length(msg) != 160
|
||||
return
|
||||
endif
|
||||
DMAP = [3 2 0 1];
|
||||
duid = msg([37,74,123,160]) + 3;
|
||||
duid = (duid / 2) + 1;
|
||||
dibits = DMAP(duid);
|
||||
duidx = dibits(1) * 64 + dibits(2) * 16 + dibits(3) * 4 + dibits(4);
|
||||
printf ('hex %x\n' , duidx);
|
||||
endfunction
|
||||
|
||||
function resampq=timing_sync(intrp0)
|
||||
global LM
|
||||
global NEWSPS
|
||||
global Q
|
||||
nsyms = length(intrp0) / LM;
|
||||
fmd = angle(intrp0(2:end) .* conj(intrp0(1:end-1)));
|
||||
fmx = mod(LM*6* ([fmd' 0] / (2*pi) + 0.5) + 0.5, 1);
|
||||
matx= reshape(fmx, LM, nsyms );
|
||||
res = std(matx');
|
||||
[m, amin] = min(res);
|
||||
amin = amin + 0 + (LM/2);
|
||||
if amin > LM
|
||||
amin = amin - LM;
|
||||
endif
|
||||
resampq = intrp0(amin:Q:end); # decim by Q
|
||||
amt_trim = mod(length(resampq), NEWSPS);
|
||||
resampq = resampq(1:end-amt_trim);
|
||||
endfunction
|
||||
|
||||
function resamp1=frequency_sync(resampq)
|
||||
global NEWSPS
|
||||
global k
|
||||
F = 0;
|
||||
sz1 = length(resampq);
|
||||
for iter = 1:4
|
||||
osc = [0:sz1-1] * (F / 30000);
|
||||
osc = exp(j*2*pi*osc);
|
||||
resamp1 = resampq .* osc.';
|
||||
row = resamp1(1:NEWSPS:end);
|
||||
rowz = mod(k*angle(row)+0.5, 1);
|
||||
rowz = unwrap((rowz-0.5) * 2*pi);
|
||||
meanr = mean(rowz(5:15)) - mean(rowz(end-15:end-5));
|
||||
F = F + meanr;
|
||||
end
|
||||
nsyms = length(resamp1) / NEWSPS;
|
||||
rfm = angle(resamp1(2:end) .* conj(resamp1(1:end-1)));
|
||||
afm = angle(resamp1);
|
||||
endfunction
|
||||
|
||||
function eye_plot(dat, sps)
|
||||
hold on
|
||||
for nn = 1:sps:length(dat)-sps*2
|
||||
sl = dat(nn:nn+sps);
|
||||
plot(sl);
|
||||
end
|
||||
hold off
|
||||
pause
|
||||
endfunction
|
||||
|
||||
function msg=correlation(resamp1)
|
||||
global NEWSPS
|
||||
global WI0
|
||||
global WQ0
|
||||
global S_A
|
||||
global S_B
|
||||
global K
|
||||
nsyms = length(resamp1) / NEWSPS;
|
||||
for n= 1 : NEWSPS : length(resamp1) - NEWSPS
|
||||
stepn=(n-1)/NEWSPS;
|
||||
idx = ((n-1) / NEWSPS) + 1;
|
||||
sl=resamp1(n:n+NEWSPS);
|
||||
sl = sl * conj(sl(1));
|
||||
sl_i = real(sl);
|
||||
sl_q = imag(sl);
|
||||
corr2 = sl_i' * WI0' + sl_q' * WQ0';
|
||||
[m,am] = max(corr2);
|
||||
msga(idx) = S_A(am);
|
||||
msgb(idx) = S_B(am);
|
||||
end
|
||||
msgok=msga(2:end) == msgb(1:end-1);
|
||||
ok=sum(msgok) / length(msga);
|
||||
msg=msgb;
|
||||
endfunction
|
||||
|
||||
function msg=frame_msg(imsg, fcode)
|
||||
if (fcode == 1)
|
||||
pilots = [1 -1 -1 1];
|
||||
else
|
||||
pilots = [-3 -3 -1 1];
|
||||
endif
|
||||
fml = length(imsg);
|
||||
msg = [];
|
||||
excess=length(imsg) - 160;
|
||||
for n=1:excess
|
||||
if n+163 > length(imsg)
|
||||
break
|
||||
endif
|
||||
slx = [imsg(n:n+1), imsg(n+162:n+163)];
|
||||
if sum(slx == pilots) == 4
|
||||
msg = imsg(n+2:n+161);
|
||||
return;
|
||||
endif
|
||||
end
|
||||
return;
|
||||
endfunction
|
||||
|
||||
process_file(fname);
|
|
@ -0,0 +1,589 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017, 2018, 2019, 2020 Max H. Parke KA1RBI
|
||||
#
|
||||
# This file is part of OP25
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
import json
|
||||
import socket
|
||||
import traceback
|
||||
import threading
|
||||
import glob
|
||||
import subprocess
|
||||
import zmq
|
||||
import op25
|
||||
|
||||
from gnuradio import gr
|
||||
from waitress.server import create_server
|
||||
from optparse import OptionParser
|
||||
from multi_rx import byteify
|
||||
from tsvfile import load_tsv, make_config
|
||||
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
|
||||
my_input_q = None
|
||||
my_output_q = None
|
||||
my_recv_q = None
|
||||
my_port = None
|
||||
my_backend = None
|
||||
CFG_DIR = '../www/config/'
|
||||
TSV_DIR = './'
|
||||
|
||||
"""
|
||||
fake http and ajax server module
|
||||
TODO: make less fake
|
||||
"""
|
||||
def ensure_str(s): # for python 2/3
|
||||
if isinstance(s[0], str):
|
||||
return s
|
||||
ns = ''
|
||||
for i in range(len(s)):
|
||||
ns += chr(s[i])
|
||||
return ns
|
||||
|
||||
class event_iterator:
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
_jslog_file = None # set to str(filename) to enable json log
|
||||
msgs = []
|
||||
while True:
|
||||
msg = my_input_q.delete_head()
|
||||
assert msg.type() == -4
|
||||
d = json.loads(msg.to_string())
|
||||
msgs.append(d)
|
||||
if my_input_q.empty_p():
|
||||
break
|
||||
js = json.dumps(msgs)
|
||||
# TODO: json.loads followed by dumps is redundant -
|
||||
# can this be optimized?
|
||||
s = 'data:%s\r\n\r\n' % (js)
|
||||
|
||||
if _jslog_file:
|
||||
t = json.dumps(msgs, indent=4, separators=[',',':'], sort_keys=True)
|
||||
with open(_jslog_file, 'a') as logfd:
|
||||
logfd.write('%s\n' % t)
|
||||
|
||||
if sys.version[0] != '2':
|
||||
if isinstance(s, str):
|
||||
s = s.encode()
|
||||
return s
|
||||
|
||||
next = __next__ # for python2
|
||||
|
||||
def static_file(environ, start_response):
|
||||
content_types = {'tsv': 'text/tab-separated-values', 'json': 'application/json', 'png': 'image/png', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'gif': 'image/gif', 'css': 'text/css', 'js': 'application/javascript', 'html': 'text/html', 'ico': 'image/vnd.microsoft.icon'}
|
||||
img_types = 'png jpg jpeg gif ico'.split()
|
||||
data_types = 'tsv txt json db'.split()
|
||||
if environ['PATH_INFO'] == '/':
|
||||
filename = 'index.html'
|
||||
else:
|
||||
filename = re.sub(r'[^a-zA-Z0-9_.\-/]', '', environ['PATH_INFO'])
|
||||
suf = filename.split('.')[-1]
|
||||
pathname = '../www/www-static'
|
||||
if suf in img_types:
|
||||
pathname = '../www/images'
|
||||
elif suf in data_types:
|
||||
pathname = TSV_DIR
|
||||
pathname = '%s/%s' % (pathname, filename)
|
||||
if suf not in content_types.keys() or '..' in filename or not os.access(pathname, os.R_OK):
|
||||
sys.stderr.write('404 %s\n' % pathname)
|
||||
status = '404 NOT FOUND - PATHNAME: %s FILENAME: %s CWD: %s' % (pathname, filename, os. getcwd())
|
||||
content_type = 'text/plain'
|
||||
output = status
|
||||
else:
|
||||
output = open(pathname, 'rb').read()
|
||||
content_type = content_types[suf]
|
||||
status = '200 OK'
|
||||
return status, content_type, output
|
||||
|
||||
def valid_tsv(filename):
|
||||
if not os.access(filename, os.R_OK):
|
||||
return False
|
||||
line = open(filename).readline()
|
||||
for word in 'Sysname Offset NAC Modulation TGID Whitelist Blacklist'.split():
|
||||
if word not in line:
|
||||
return False
|
||||
return True
|
||||
|
||||
def tsv_config(filename):
|
||||
DEFAULT_CFG = '../www/config/default.json'
|
||||
filename = '%s%s' % (TSV_DIR, filename)
|
||||
filename = filename.replace('[TSV]', '.tsv')
|
||||
if not valid_tsv(filename):
|
||||
return None
|
||||
cfg = make_config(load_tsv(filename))
|
||||
default_cfg = json.loads(open(DEFAULT_CFG).read())
|
||||
|
||||
result = default_cfg
|
||||
channels = [ {'active': True,
|
||||
'blacklist': cfg[nac]['blacklist'],
|
||||
'whitelist': cfg[nac]['whitelist'],
|
||||
'cclist': cfg[nac]['cclist'],
|
||||
'demod_type': 'cqpsk',
|
||||
'destination': 'udp://127.0.0.1:23456',
|
||||
'filter_type': 'rc',
|
||||
'frequency': 500000000,
|
||||
'if_rate': 24000,
|
||||
'nac': nac,
|
||||
'name': cfg[nac]['sysname'],
|
||||
'phase2_tdma': False,
|
||||
'plot': "",
|
||||
'tgids': cfg[nac]['tgid_map'],
|
||||
'trunked': True
|
||||
}
|
||||
for nac in cfg.keys() ]
|
||||
result['channels'] = channels
|
||||
return {'json_type':'config_data', 'data': result}
|
||||
|
||||
def do_request(d):
|
||||
global my_backend
|
||||
if d['command'].startswith('rx-'):
|
||||
msg = gr.message().make_from_string(json.dumps(d), -2, 0, 0)
|
||||
if not my_backend.input_q.full_p():
|
||||
my_backend.input_q.insert_tail(msg)
|
||||
return None
|
||||
elif d['command'] == 'config-load':
|
||||
if '[TSV]' in d['data']:
|
||||
return tsv_config(d['data'])
|
||||
filename = '%s%s.json' % (CFG_DIR, d['data'])
|
||||
if not os.access(filename, os.R_OK):
|
||||
return None
|
||||
js_msg = json.loads(open(filename).read())
|
||||
return {'json_type':'config_data', 'data': js_msg}
|
||||
elif d['command'] == 'config-list':
|
||||
files = glob.glob('%s*.json' % CFG_DIR)
|
||||
files = [x.replace('.json', '') for x in files]
|
||||
files = [x.replace(CFG_DIR, '') for x in files]
|
||||
if d['data'] == 'tsv':
|
||||
tsvfiles = glob.glob('%s*.tsv' % TSV_DIR)
|
||||
tsvfiles = [x for x in tsvfiles if valid_tsv(x)]
|
||||
tsvfiles = [x.replace('.tsv', '[TSV]') for x in tsvfiles]
|
||||
tsvfiles = [x.replace(TSV_DIR, '') for x in tsvfiles]
|
||||
files += tsvfiles
|
||||
return {'json_type':'config_list', 'data': files}
|
||||
elif d['command'] == 'config-save':
|
||||
name = d['data']['name']
|
||||
if '..' in name or '.json' in name or '/' in name:
|
||||
return None
|
||||
filename = '%s%s.json' % (CFG_DIR, d['data']['name'])
|
||||
open(filename, 'w').write(json.dumps(d['data']['value'], indent=4, separators=[',',':'], sort_keys=True))
|
||||
return None
|
||||
elif d['command'] == 'config-savesettings':
|
||||
filename = 'ui-settings.json'
|
||||
open(filename, 'w').write(d['data'])
|
||||
sys.stderr.write('saved UI settings to %s\n' % filename)
|
||||
return None
|
||||
elif d['command'] == 'config-tsvsave':
|
||||
filename = d['file']
|
||||
ok = True
|
||||
if filename.lower().endswith('tsv'):
|
||||
ok = True
|
||||
elif filename.lower().endswith('json'):
|
||||
ok = True
|
||||
else:
|
||||
ok = False
|
||||
if filename.startswith('.'):
|
||||
ok = False
|
||||
if '/' in filename:
|
||||
ok = False
|
||||
if '..' in filename:
|
||||
ok = False
|
||||
if not ok:
|
||||
sys.stderr.write('cfg-tsvsave: invalid filename %s\n' % filename)
|
||||
return None
|
||||
open(filename, 'w').write(d['data'])
|
||||
sys.stderr.write('saved UI settings to %s\n' % filename)
|
||||
return None
|
||||
|
||||
def post_req(environ, start_response, postdata):
|
||||
global my_input_q, my_output_q, my_recv_q, my_port
|
||||
resp_msg = []
|
||||
data = []
|
||||
try:
|
||||
data = json.loads(postdata)
|
||||
except:
|
||||
sys.stderr.write('post_req: error processing input: %s:\n' % (postdata))
|
||||
traceback.print_exc(limit=None, file=sys.stderr)
|
||||
sys.stderr.write('*** end traceback ***\n')
|
||||
for d in data:
|
||||
if type(d) is str:
|
||||
sys.stderr.write('%f possible json sequence error: len %d type %s value %s\n' % (time.time(), len(d), type(d), d))
|
||||
continue
|
||||
elif type(d) is not dict:
|
||||
sys.stderr.write('%f possible json sequence error: type %s value %s\n' % (time.time(), type(d), d))
|
||||
continue
|
||||
if d['command'].startswith('config-') or d['command'].startswith('rx-'):
|
||||
resp = do_request(d)
|
||||
if resp:
|
||||
resp_msg.append(resp)
|
||||
continue
|
||||
if d['command'].startswith('settings-'):
|
||||
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
|
||||
else:
|
||||
msg = gr.message().make_from_string(str(d['command']), -2, d['data'], 0)
|
||||
if my_output_q.full_p():
|
||||
my_output_q.delete_head_nowait() # ignores result
|
||||
if not my_output_q.full_p():
|
||||
my_output_q.insert_tail(msg)
|
||||
time.sleep(0.2)
|
||||
|
||||
status = '200 OK'
|
||||
content_type = 'application/json'
|
||||
output = json.dumps(resp_msg)
|
||||
return status, content_type, output
|
||||
|
||||
def http_request(environ, start_response):
|
||||
if environ['REQUEST_METHOD'] == 'GET' and '/stream' in environ['PATH_INFO']:
|
||||
status = '200 OK'
|
||||
content_type = 'text/event-stream'
|
||||
response_headers = [('Content-type', content_type),
|
||||
('Access-Control-Allow-Origin', '*')]
|
||||
start_response(status, response_headers)
|
||||
return iter(event_iterator())
|
||||
elif environ['REQUEST_METHOD'] == 'GET':
|
||||
status, content_type, output = static_file(environ, start_response)
|
||||
elif environ['REQUEST_METHOD'] == 'POST':
|
||||
postdata = environ['wsgi.input'].read()
|
||||
status, content_type, output = post_req(environ, start_response, postdata)
|
||||
else:
|
||||
status = '200 OK'
|
||||
content_type = 'text/plain'
|
||||
output = status
|
||||
sys.stderr.write('http_request: unexpected input %s\n' % environ['PATH_INFO'])
|
||||
|
||||
response_headers = [('Content-type', content_type),
|
||||
('Access-Control-Allow-Origin', '*'),
|
||||
('Content-Length', str(len(output)))]
|
||||
start_response(status, response_headers)
|
||||
|
||||
if sys.version[0] != '2':
|
||||
if isinstance(output, str):
|
||||
output = output.encode()
|
||||
return [output]
|
||||
|
||||
def application(environ, start_response):
|
||||
failed = False
|
||||
try:
|
||||
result = http_request(environ, start_response)
|
||||
except:
|
||||
failed = True
|
||||
sys.stderr.write('application: request failed:\n%s\n' % traceback.format_exc())
|
||||
if failed:
|
||||
status = '500 Internal Server Error'
|
||||
response_headers = [ ('Access-Control-Allow-Origin', '*') ]
|
||||
start_response(status, response_headers)
|
||||
output = status
|
||||
if sys.version[0] != '2':
|
||||
if isinstance(output, str):
|
||||
output = output.encode()
|
||||
return [output]
|
||||
|
||||
return result
|
||||
|
||||
def process_qmsg(msg):
|
||||
if my_recv_q.full_p():
|
||||
my_recv_q.delete_head_nowait() # ignores result
|
||||
if my_recv_q.full_p():
|
||||
return
|
||||
my_recv_q.insert_tail(msg)
|
||||
|
||||
class http_server(object):
|
||||
def __init__(self, input_q, output_q, endpoint, **kwds):
|
||||
global my_input_q, my_output_q, my_recv_q, my_port
|
||||
host, port = endpoint.split(':')
|
||||
if my_port is not None:
|
||||
raise AssertionError('this server is already active on port %s' % my_port)
|
||||
my_input_q = input_q
|
||||
my_output_q = output_q
|
||||
my_port = int(port)
|
||||
|
||||
my_recv_q = gr.msg_queue(10)
|
||||
|
||||
SEND_BYTES = 1024
|
||||
NTHREADS = 10 # TODO: make #threads a function of #plots ?
|
||||
self.server = create_server(application, host=host, port=my_port, send_bytes=SEND_BYTES, expose_tracebacks=True, threads=NTHREADS)
|
||||
|
||||
def run(self):
|
||||
self.server.run()
|
||||
|
||||
class queue_watcher(threading.Thread):
|
||||
def __init__(self, msgq, callback, **kwds):
|
||||
threading.Thread.__init__ (self, **kwds)
|
||||
self.setDaemon(1)
|
||||
self.msgq = msgq
|
||||
self.callback = callback
|
||||
self.keep_running = True
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
while(self.keep_running):
|
||||
msg = self.msgq.delete_head()
|
||||
self.callback(msg)
|
||||
|
||||
class Backend(threading.Thread):
|
||||
def __init__(self, options, input_q, output_q, init_config=None, **kwds):
|
||||
threading.Thread.__init__ (self, **kwds)
|
||||
self.setDaemon(1)
|
||||
self.keep_running = True
|
||||
self.rx_options = None
|
||||
self.input_q = input_q
|
||||
self.output_q = output_q
|
||||
self.verbosity = options.verbosity
|
||||
|
||||
self.zmq_context = zmq.Context()
|
||||
self.zmq_port = options.zmq_port
|
||||
|
||||
self.zmq_sub = self.zmq_context.socket(zmq.SUB)
|
||||
self.zmq_sub.connect('tcp://localhost:%d' % self.zmq_port)
|
||||
self.zmq_sub.setsockopt_string(zmq.SUBSCRIBE, '')
|
||||
|
||||
self.zmq_pub = self.zmq_context.socket(zmq.PUB)
|
||||
self.zmq_pub.sndhwm = 5
|
||||
self.zmq_pub.bind('tcp://*:%d' % (self.zmq_port+1))
|
||||
|
||||
self.start()
|
||||
self.subproc = None
|
||||
self.msg = None
|
||||
|
||||
self.q_watcher = queue_watcher(self.input_q, self.process_msg)
|
||||
|
||||
if init_config:
|
||||
d = {'command': 'rx-start', 'data': init_config}
|
||||
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
|
||||
self.input_q.insert_tail(msg)
|
||||
|
||||
def publish(self, msg):
|
||||
t = msg.type()
|
||||
s = msg.to_string()
|
||||
a = msg.arg1()
|
||||
s = ensure_str(s)
|
||||
self.zmq_pub.send_string(json.dumps({'command': s, 'data': a, 'msgtype': t}))
|
||||
|
||||
def check_subproc(self): # return True if subprocess is active
|
||||
if not self.subproc:
|
||||
return False
|
||||
rc = self.subproc.poll()
|
||||
if rc is None:
|
||||
return True
|
||||
else:
|
||||
self.subproc.wait()
|
||||
self.subproc = None
|
||||
return False
|
||||
|
||||
def process_msg(self, msg):
|
||||
def make_command(options, config_file):
|
||||
py_exe = 'python'
|
||||
if sys.version[0] == '3':
|
||||
py_exe = 'python3'
|
||||
trunked_ct = [True for x in options._js_config['channels'] if x['trunked']]
|
||||
total_ct = [True for x in options._js_config['channels']]
|
||||
if trunked_ct and len(trunked_ct) != len(total_ct):
|
||||
self.msg = 'no suitable backend found for this configuration'
|
||||
return None
|
||||
if not trunked_ct:
|
||||
self.backend = '%s/%s' % (os.getcwd(), 'multi_rx.py')
|
||||
opts = [py_exe, self.backend]
|
||||
filename = '%s%s.json' % (CFG_DIR, config_file)
|
||||
opts.append('--config-file')
|
||||
opts.append(filename)
|
||||
return opts
|
||||
|
||||
# TODO: this probably should be external and/or configurable
|
||||
# these options must match up one for one with the rx.py cli opts
|
||||
types = {'costas-alpha': 'float',
|
||||
'trunk-conf-file': 'str',
|
||||
'demod-type': 'str',
|
||||
'logfile-workers': 'int',
|
||||
'decim-amt': 'int',
|
||||
'wireshark-host': 'str',
|
||||
'gain-mu': 'float',
|
||||
'phase2-tdma': 'bool',
|
||||
'seek': 'int',
|
||||
'ifile': 'str',
|
||||
'pause': 'bool',
|
||||
'antenna': 'str',
|
||||
'calibration': 'float',
|
||||
'fine-tune': 'float',
|
||||
'raw-symbols': 'str',
|
||||
'audio-output': 'str',
|
||||
'vocoder': 'bool',
|
||||
'input': 'str',
|
||||
'wireshark': 'bool',
|
||||
'gains': 'str',
|
||||
'args': 'str',
|
||||
'sample-rate': 'int',
|
||||
'terminal-type': 'str',
|
||||
'gain': 'float',
|
||||
'excess-bw': 'float',
|
||||
'offset': 'float',
|
||||
'audio-input': 'str',
|
||||
'audio': 'bool',
|
||||
'plot-mode': 'str',
|
||||
'audio-if': 'bool',
|
||||
'tone-detect': 'bool',
|
||||
'frequency': 'int',
|
||||
'freq-corr': 'float',
|
||||
'hamlib-model': 'int',
|
||||
'udp-player': 'bool',
|
||||
'verbosity': 'int',
|
||||
'audio-gain': 'float',
|
||||
'freq-error-tracking': 'bool',
|
||||
'nocrypt': 'bool',
|
||||
'wireshark-port': 'int'
|
||||
}
|
||||
self.backend = '%s/%s' % (os.getcwd(), 'rx.py')
|
||||
opts = [py_exe, self.backend]
|
||||
for k in [ x for x in dir(options) if not x.startswith('_') ]:
|
||||
kw = k.replace('_', '-')
|
||||
val = getattr(options, k)
|
||||
if kw not in types.keys():
|
||||
self.msg = 'make_command: unknown option: %s %s type %s' % (k, val, type(val))
|
||||
return None
|
||||
elif types[kw] == 'str':
|
||||
if val:
|
||||
opts.append('--%s' % kw)
|
||||
opts.append('%s' % (val))
|
||||
elif types[kw] == 'float':
|
||||
opts.append('--%s' % kw)
|
||||
if val:
|
||||
opts.append('%f' % (val))
|
||||
else:
|
||||
opts.append('%f' % (0))
|
||||
elif types[kw] == 'int':
|
||||
opts.append('--%s' % kw)
|
||||
if val:
|
||||
opts.append('%d' % (val))
|
||||
else:
|
||||
opts.append('%d' % (0))
|
||||
elif types[kw] == 'bool':
|
||||
if val:
|
||||
opts.append('--%s' % kw)
|
||||
else:
|
||||
self.msg = 'make_command: unknown2 option: %s %s type %s' % (k, val, type(val))
|
||||
return None
|
||||
return opts
|
||||
|
||||
msg = json.loads(msg.to_string())
|
||||
if msg['command'] == 'rx-start':
|
||||
if self.check_subproc():
|
||||
self.msg = 'start command failed: subprocess pid %d already active' % self.subproc.pid
|
||||
return
|
||||
options = rx_options(msg['data'])
|
||||
if getattr(options, '_js_config', None) is None:
|
||||
self.msg = 'start command failed: rx_options: unable to initialize config=%s' % (msg['data'])
|
||||
return
|
||||
options.verbosity = self.verbosity
|
||||
options.terminal_type = 'zmq:tcp:%d' % (self.zmq_port)
|
||||
cmd = make_command(options, msg['data'])
|
||||
sys.stderr.write('executing %s\n' % (' '.join(cmd)))
|
||||
if cmd:
|
||||
self.subproc = subprocess.Popen(cmd)
|
||||
elif msg['command'] == 'rx-stop':
|
||||
if not self.check_subproc():
|
||||
self.msg = 'stop command failed: subprocess not active'
|
||||
return
|
||||
if msg['data'] == 'kill':
|
||||
self.subproc.kill()
|
||||
else:
|
||||
self.subproc.terminate()
|
||||
elif msg['command'] == 'rx-state':
|
||||
d = {}
|
||||
if self.check_subproc():
|
||||
d['rx-state'] = 'subprocess pid %d active' % self.subproc.pid
|
||||
else:
|
||||
d['rx-state'] = 'subprocess not active, last msg: %s' % self.msg
|
||||
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
|
||||
if not self.output_q.full_p():
|
||||
self.output_q.insert_tail(msg)
|
||||
|
||||
def run(self):
|
||||
while self.keep_running:
|
||||
js = self.zmq_sub.recv()
|
||||
if not self.keep_running:
|
||||
break
|
||||
js = ensure_str(js)
|
||||
msg = gr.message().make_from_string(js, -4, 0, 0)
|
||||
if not self.output_q.full_p():
|
||||
self.output_q.insert_tail(msg)
|
||||
|
||||
class rx_options(object):
|
||||
def __init__(self, name):
|
||||
def map_name(k):
|
||||
return k.replace('-', '_')
|
||||
|
||||
filename = '%s%s.json' % (CFG_DIR, name)
|
||||
if not os.access(filename, os.R_OK):
|
||||
sys.stderr.write('unable to access config file %s\n' % (filename))
|
||||
return
|
||||
config = byteify(json.loads(open(filename).read()))
|
||||
dev = [x for x in config['devices'] if x['active']][0]
|
||||
if not dev:
|
||||
return
|
||||
chan = [x for x in config['channels'] if x['active']][0]
|
||||
if not chan:
|
||||
return
|
||||
options = object()
|
||||
for k in config['backend-rx'].keys():
|
||||
setattr(self, map_name(k), config['backend-rx'][k])
|
||||
for k in 'args frequency gains offset'.split():
|
||||
setattr(self, k, dev[k])
|
||||
self.demod_type = chan['demod_type']
|
||||
self.freq_corr = dev['ppm']
|
||||
self.sample_rate = dev['rate']
|
||||
self.plot_mode = chan['plot']
|
||||
self.phase2_tdma = chan['phase2_tdma']
|
||||
self.trunk_conf_file = filename
|
||||
self._js_config = config
|
||||
|
||||
def http_main():
|
||||
global my_backend
|
||||
# command line argument parsing
|
||||
parser = OptionParser()
|
||||
parser.add_option("-c", "--config", type="string", default=None, help="config json name, without prefix/suffix")
|
||||
parser.add_option("-e", "--endpoint", type="string", default="127.0.0.1:8080", help="address:port to listen on (use addr 0.0.0.0 to enable external clients)")
|
||||
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
|
||||
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
|
||||
parser.add_option("-z", "--zmq-port", type="int", default=25000, help="backend sub port")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
# wait for gdb
|
||||
if options.pause:
|
||||
print ('Ready for GDB to attach (pid = %d)' % (os.getpid(),))
|
||||
raw_input("Press 'Enter' to continue...")
|
||||
|
||||
input_q = gr.msg_queue(20)
|
||||
output_q = gr.msg_queue(20)
|
||||
backend_input_q = gr.msg_queue(20)
|
||||
backend_output_q = gr.msg_queue(20)
|
||||
|
||||
my_backend = Backend(options, backend_input_q, backend_output_q, init_config=options.config)
|
||||
server = http_server(input_q, output_q, options.endpoint)
|
||||
q_watcher = queue_watcher(output_q, lambda msg : my_backend.publish(msg))
|
||||
backend_q_watcher = queue_watcher(backend_output_q, lambda msg : process_qmsg(msg))
|
||||
|
||||
server.run()
|
||||
|
||||
if __name__ == '__main__':
|
||||
http_main()
|
|
@ -0,0 +1,31 @@
|
|||
#! /bin/sh
|
||||
|
||||
PIP3=`which pip3`
|
||||
USERDIR=~/.local/bin
|
||||
|
||||
sudo apt-get install python3-pip
|
||||
|
||||
# PIP3=`which pip3`
|
||||
PIP3=/usr/bin/pip3
|
||||
|
||||
# # # # # # un-comment the following two lines for ubuntu 16.04 # # # # # #
|
||||
#pip3 install --user pip==10.0.1
|
||||
#PIP3=$USERDIR/pip3
|
||||
|
||||
echo PIP3 now set to $PIP3
|
||||
# # # $PIP3 --version # # # generates errors -- (?)
|
||||
|
||||
$PIP3 install --user sqlalchemy
|
||||
$PIP3 install --user flask
|
||||
$PIP3 install --user datatables
|
||||
$PIP3 install --user flask-sqlalchemy
|
||||
|
||||
cd
|
||||
git clone https://github.com/Pegase745/sqlalchemy-datatables.git
|
||||
cd sqlalchemy-datatables
|
||||
$PIP3 install --user .
|
||||
cd
|
||||
|
||||
echo the following line must be added to your .bashrc
|
||||
echo "export PATH=$USERDIR:\$PATH"
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2020 Graham Norbury
|
||||
#
|
||||
# This file is part of OP25
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
# Modify TS_FORMAT to control logger timestamp format
|
||||
# 0 = legacy epoch seconds
|
||||
# 1 = formatted mm/dd/yy hh:mm:ss.usec
|
||||
TS_FORMAT = 1
|
||||
|
||||
import time
|
||||
class log_ts(object):
|
||||
@staticmethod
|
||||
def get(supplied_ts=None):
|
||||
if supplied_ts is None:
|
||||
ts = time.time()
|
||||
else:
|
||||
ts = supplied_ts
|
||||
|
||||
if TS_FORMAT == 0:
|
||||
formatted_ts = "{:.6f}".format(ts)
|
||||
else:
|
||||
formatted_ts = "{:s}{:s}".format(time.strftime("%m/%d/%y %H:%M:%S",time.localtime(ts)),"{:.6f}".format(ts - int(ts)).lstrip("0"))
|
||||
|
||||
return formatted_ts
|
||||
|
|
@ -0,0 +1,864 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Max H. Parke KA1RBI
|
||||
#
|
||||
# This file is part of OP25
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import json
|
||||
import select
|
||||
import traceback
|
||||
import osmosdr
|
||||
|
||||
from gnuradio import audio, eng_notation, gr, filter, blocks, fft, analog, digital
|
||||
from gnuradio.eng_option import eng_option
|
||||
from math import pi
|
||||
from optparse import OptionParser
|
||||
|
||||
import trunking
|
||||
|
||||
import op25
|
||||
import op25_repeater
|
||||
import p25_demodulator
|
||||
import p25_decoder
|
||||
from sockaudio import audio_thread
|
||||
|
||||
from sql_dbi import sql_dbi
|
||||
|
||||
from gr_gnuplot import constellation_sink_c
|
||||
from gr_gnuplot import fft_sink_c
|
||||
from gr_gnuplot import mixer_sink_c
|
||||
from gr_gnuplot import symbol_sink_f
|
||||
from gr_gnuplot import eye_sink_f
|
||||
from gr_gnuplot import setup_correlation
|
||||
from gr_gnuplot import sync_plot
|
||||
from gr_gnuplot import cpm_sink_c
|
||||
|
||||
from nxdn_trunking import cac_message
|
||||
|
||||
from terminal import op25_terminal
|
||||
|
||||
sys.path.append('tdma')
|
||||
import lfsr
|
||||
|
||||
os.environ['IMBE'] = 'soft'
|
||||
|
||||
_def_symbol_rate = 4800
|
||||
_def_interval = 3.0 # sec
|
||||
_def_file_dir = '../www/images'
|
||||
_def_audio_port = 23456 # udp port for audio thread
|
||||
_def_audio_output = 'default' # output device name for audio thread
|
||||
|
||||
# The P25 receiver
|
||||
#
|
||||
|
||||
def byteify(input): # thx so
|
||||
if sys.version[0] != '2': # hack, must be a better way
|
||||
return input
|
||||
if isinstance(input, dict):
|
||||
return {byteify(key): byteify(value)
|
||||
for key, value in input.iteritems()}
|
||||
elif isinstance(input, list):
|
||||
return [byteify(element) for element in input]
|
||||
elif isinstance(input, unicode):
|
||||
return input.encode('utf-8')
|
||||
else:
|
||||
return input
|
||||
|
||||
class device(object):
|
||||
def __init__(self, config, tb):
|
||||
self.name = config['name']
|
||||
self.sample_rate = config['rate']
|
||||
self.args = config['args']
|
||||
self.tunable = config['tunable']
|
||||
self.tb = tb
|
||||
self.frequency = 0
|
||||
|
||||
if config['args'].startswith('audio-if:'):
|
||||
self.init_audio_if(config)
|
||||
elif config['args'].startswith('audio:'):
|
||||
self.init_audio(config)
|
||||
elif config['args'].startswith('file:'):
|
||||
self.init_file(config)
|
||||
elif config['args'].startswith('udp:'):
|
||||
self.init_udp(config)
|
||||
else:
|
||||
self.init_osmosdr(config)
|
||||
|
||||
def init_file(self, config):
|
||||
filename = config['args'].replace('file:', '', 1)
|
||||
src = blocks.file_source(gr.sizeof_gr_complex, filename, repeat = False)
|
||||
throttle = blocks.throttle(gr.sizeof_gr_complex, config['rate'])
|
||||
self.tb.connect(src, throttle)
|
||||
self.src = throttle
|
||||
self.frequency = config['frequency']
|
||||
self.offset = config['offset']
|
||||
|
||||
def init_audio_if(self, config):
|
||||
filename = config['args'].replace('audio-if:', '')
|
||||
self.audio_source = audio.source(config['rate'], filename)
|
||||
self.null_source = blocks.null_source (gr.sizeof_float)
|
||||
self.audio_cvt = blocks.float_to_complex()
|
||||
self.tb.connect(self.audio_source, (self.audio_cvt, 0))
|
||||
self.tb.connect(self.null_source, (self.audio_cvt, 1))
|
||||
self.src = self.audio_cvt
|
||||
self.frequency = config['frequency']
|
||||
self.offset = config['offset']
|
||||
|
||||
def init_audio(self, config):
|
||||
filename = config['args'].replace('audio:', '')
|
||||
if filename.startswith('file:'):
|
||||
filename = filename.replace('file:', '')
|
||||
repeat = False
|
||||
s2f = blocks.short_to_float()
|
||||
K = 1 / 32767.0
|
||||
src = blocks.multiply_const_ff(K)
|
||||
throttle = blocks.throttle(gr.sizeof_short, self.sample_rate) # may be redundant in stdin case ?
|
||||
if filename == '-':
|
||||
fd = 0 # stdin
|
||||
fsrc = blocks.file_descriptor_source(gr.sizeof_short, fd, repeat)
|
||||
else:
|
||||
fsrc = blocks.file_source(gr.sizeof_short, filename, repeat)
|
||||
self.tb.connect(fsrc, throttle, s2f, src)
|
||||
else:
|
||||
src = audio.source(self.sample_rate, filename)
|
||||
gain = 1.0
|
||||
if config['gains'].startswith('audio:'):
|
||||
gain = float(config['gains'].replace('audio:', ''))
|
||||
self.src = blocks.multiply_const_ff(gain)
|
||||
self.tb.connect(src, self.src)
|
||||
|
||||
def init_udp(self, config):
|
||||
hostinfo = config['args'].split(':')
|
||||
hostname = hostinfo[1]
|
||||
udp_port = int(hostinfo[2])
|
||||
bufsize = 32000 # might try enlarging this if packet loss
|
||||
self.src = blocks.udp_source(gr.sizeof_gr_complex, hostname, udp_port, payload_size = bufsize)
|
||||
self.ppm = 0
|
||||
self.frequency = config['frequency']
|
||||
self.offset = 0
|
||||
|
||||
def init_osmosdr(self, config):
|
||||
speeds = [250000, 1000000, 1024000, 1800000, 1920000, 2000000, 2048000, 2400000, 2560000]
|
||||
|
||||
sys.stderr.write('device: %s\n' % config)
|
||||
if config['args'].startswith('rtl') and config['rate'] not in speeds:
|
||||
sys.stderr.write('WARNING: requested sample rate %d for device %s may not\n' % (config['rate'], config['name']))
|
||||
sys.stderr.write("be optimal. You may want to use one of the following rates\n")
|
||||
sys.stderr.write('%s\n' % speeds)
|
||||
self.src = osmosdr.source(config['args'])
|
||||
|
||||
for tup in config['gains'].split(','):
|
||||
name, gain = tup.split(':')
|
||||
self.src.set_gain(int(gain), name)
|
||||
|
||||
self.src.set_freq_corr(config['ppm'])
|
||||
self.ppm = config['ppm']
|
||||
|
||||
self.src.set_sample_rate(config['rate'])
|
||||
|
||||
self.src.set_center_freq(config['frequency'])
|
||||
self.frequency = config['frequency']
|
||||
|
||||
self.offset = config['offset']
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
if frequency == self.frequency:
|
||||
return
|
||||
if not self.tunable:
|
||||
return
|
||||
self.frequency = frequency
|
||||
self.src.set_center_freq(frequency)
|
||||
|
||||
class channel(object):
|
||||
def __init__(self, config, dev, verbosity, msgq = None, process_msg=None, msgq_id=-1, role=''):
|
||||
sys.stderr.write('channel (dev %s): %s\n' % (dev.name, config))
|
||||
self.device = dev
|
||||
self.name = config['name']
|
||||
self.symbol_rate = _def_symbol_rate
|
||||
self.process_msg = process_msg
|
||||
self.role = role
|
||||
self.dev = ''
|
||||
self.sysid = []
|
||||
self.nac = []
|
||||
if 'symbol_rate' in config.keys():
|
||||
self.symbol_rate = config['symbol_rate']
|
||||
self.config = config
|
||||
self.verbosity = verbosity
|
||||
self.frequency = config['frequency'] if self.device.args.startswith('audio-if') else 0
|
||||
self.tdma_state = False
|
||||
self.xor_cache = {}
|
||||
|
||||
self.tuning_error = 0
|
||||
self.freq_correction = 0
|
||||
self.error_band = 0
|
||||
self.last_error_update = 0
|
||||
self.last_set_freq_at = time.time()
|
||||
self.warned_frequencies = {}
|
||||
self.msgq_id = msgq_id
|
||||
self.next_band_change = time.time()
|
||||
|
||||
self.audio_port = _def_audio_port
|
||||
self.audio_output = _def_audio_output
|
||||
self.audio_gain = 1.0
|
||||
if 'audio_gain' in config:
|
||||
self.audio_gain = float(config['audio_gain'])
|
||||
|
||||
if dev.args.startswith('audio:'):
|
||||
self.demod = p25_demodulator.p25_demod_fb(
|
||||
input_rate = dev.sample_rate,
|
||||
filter_type = config['filter_type'],
|
||||
if_rate = config['if_rate'],
|
||||
symbol_rate = self.symbol_rate)
|
||||
else:
|
||||
self.demod = p25_demodulator.p25_demod_cb(
|
||||
input_rate = dev.sample_rate,
|
||||
demod_type = config['demod_type'],
|
||||
filter_type = config['filter_type'],
|
||||
excess_bw = config['excess_bw'],
|
||||
relative_freq = dev.frequency + dev.offset - config['frequency'],
|
||||
offset = dev.offset,
|
||||
if_rate = config['if_rate'],
|
||||
symbol_rate = self.symbol_rate,
|
||||
use_old_decim = True if self.device.args.startswith('audio-if') else False)
|
||||
if msgq is not None:
|
||||
q = msgq
|
||||
else:
|
||||
q = gr.msg_queue(20)
|
||||
if 'decode' in config.keys() and config['decode'].startswith('p25_decoder'):
|
||||
num_ambe = 1
|
||||
(proto, wireshark_host, udp_port) = config['destination'].split(':')
|
||||
assert proto == 'udp'
|
||||
wireshark_host = wireshark_host.replace('/', '')
|
||||
udp_port = int(udp_port)
|
||||
if role == 'vc':
|
||||
self.audio_port = udp_port
|
||||
if 'audio_output' in config.keys():
|
||||
self.audio_output = config['audio_output']
|
||||
|
||||
self.decoder = p25_decoder.p25_decoder_sink_b(dest='audio', do_imbe=True, num_ambe=num_ambe, wireshark_host=wireshark_host, udp_port=udp_port, do_msgq = True, msgq=q, audio_output=self.audio_output, debug=verbosity, msgq_id=self.msgq_id)
|
||||
else:
|
||||
self.decoder = op25_repeater.frame_assembler(config['destination'], verbosity, q, self.msgq_id)
|
||||
|
||||
if self.symbol_rate == 6000 and role == 'cc':
|
||||
sps = config['if_rate'] // self.symbol_rate
|
||||
self.demod.set_symbol_rate(self.symbol_rate) # this and the foll. call should be merged?
|
||||
self.demod.clock.set_omega(float(sps))
|
||||
self.demod.clock.set_tdma(True)
|
||||
sys.stderr.write('initializing TDMA control channel %s channel ID %d\n' % (self.name, self.msgq_id))
|
||||
|
||||
if self.process_msg is not None and msgq is None:
|
||||
self.q_watcher = du_queue_watcher(q, lambda msg: self.process_msg(msg, sender=self))
|
||||
|
||||
self.kill_sink = []
|
||||
|
||||
if 'blacklist' in config.keys():
|
||||
for g in config['blacklist'].split(','):
|
||||
self.decoder.insert_blacklist(int(g))
|
||||
|
||||
if 'whitelist' in config.keys():
|
||||
for g in config['whitelist'].split(','):
|
||||
self.decoder.insert_whitelist(int(g))
|
||||
|
||||
self.sinks = []
|
||||
if 'plot' not in config.keys():
|
||||
return
|
||||
|
||||
for plot in config['plot'].split(','):
|
||||
if plot == 'datascope':
|
||||
assert config['demod_type'] == 'fsk4' ## datascope plot requires fsk4 demod type
|
||||
sink = eye_sink_f(sps=config['if_rate'] // self.symbol_rate)
|
||||
sink.set_title(self.name)
|
||||
self.sinks.append(sink)
|
||||
self.demod.connect_bb('symbol_filter', sink)
|
||||
self.kill_sink.append(sink)
|
||||
elif plot.startswith('cpm'):
|
||||
if self.symbol_rate != 6000: # fixed rate value for p25p2
|
||||
sys.stderr.write('warning: symbol rate %d may be incorrect for CPM channel %s\n' % (self.symbol_rate, self.name))
|
||||
sink = cpm_sink_c(sps=config['if_rate'] // self.symbol_rate, plot_mode=plot)
|
||||
sink.set_title(self.name)
|
||||
self.sinks.append(sink)
|
||||
self.demod.connect_complex('if_out', sink)
|
||||
self.kill_sink.append(sink)
|
||||
elif plot == 'symbol':
|
||||
sink = symbol_sink_f()
|
||||
sink.set_title(self.name)
|
||||
self.sinks.append(sink)
|
||||
self.demod.connect_float(sink)
|
||||
self.kill_sink.append(sink)
|
||||
elif plot == 'fft':
|
||||
i = len(self.sinks)
|
||||
sink = fft_sink_c()
|
||||
sink.set_title(self.name)
|
||||
self.sinks.append(sink)
|
||||
self.demod.connect_complex('src', self.sinks[i])
|
||||
self.kill_sink.append(self.sinks[i])
|
||||
elif plot == 'mixer':
|
||||
if config['demod_type'] == 'cqpsk':
|
||||
blk = 'mixer'
|
||||
else:
|
||||
blk = 'cutoff'
|
||||
i = len(self.sinks)
|
||||
sink = mixer_sink_c()
|
||||
sink.set_title(self.name)
|
||||
self.sinks.append(sink)
|
||||
self.demod.connect_complex(blk, self.sinks[i])
|
||||
self.kill_sink.append(self.sinks[i])
|
||||
elif plot == 'constellation':
|
||||
i = len(self.sinks)
|
||||
assert config['demod_type'] == 'cqpsk' ## constellation plot requires cqpsk demod type
|
||||
sink = constellation_sink_c()
|
||||
sink.set_title(self.name)
|
||||
self.sinks.append(sink)
|
||||
self.demod.connect_complex('diffdec', self.sinks[i])
|
||||
self.kill_sink.append(self.sinks[i])
|
||||
elif plot == 'correlation':
|
||||
assert config['demod_type'] == 'fsk4' ## correlation plot requires fsk4 demod type
|
||||
assert config['symbol_rate'] == 4800 ## 4800 required for correlation plot
|
||||
sps=config['if_rate'] // self.symbol_rate
|
||||
sinks = setup_correlation(sps, self.name, self.demod.connect_bb)
|
||||
self.kill_sink += sinks
|
||||
self.sinks += sinks
|
||||
elif plot == 'sync':
|
||||
assert config['demod_type'] == 'cqpsk' ## sync plot requires cqpsk demod type
|
||||
i = len(self.sinks)
|
||||
sink = sync_plot(block = self.demod.clock)
|
||||
sink.set_title(self.name)
|
||||
self.sinks.append(sink)
|
||||
self.kill_sink.append(self.sinks[i])
|
||||
# does not issue self.connect()
|
||||
else:
|
||||
sys.stderr.write('unrecognized plot type %s\n' % plot)
|
||||
return
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
assert frequency
|
||||
if self.device.tunable:
|
||||
self.device.set_frequency(frequency)
|
||||
f = self.frequency if self.device.args.startswith('audio-if') else frequency
|
||||
relative_freq = self.device.frequency + self.device.offset + self.tuning_error - f
|
||||
if (not self.device.tunable) and (not self.device.args.startswith('audio-if')) and abs(relative_freq) > ((self.demod.input_rate / 2) - (self.demod.if1 / 2)):
|
||||
if frequency not in self.warned_frequencies:
|
||||
sys.stderr.write('warning: set frequency %f to non-tunable device %s rejected.\n' % (frequency / 1000000.0, self.device.name))
|
||||
self.warned_frequencies[frequency] = 0
|
||||
self.warned_frequencies[frequency] += 1
|
||||
#print 'set_relative_frequency: error, relative frequency %d exceeds limit %d' % (relative_freq, self.demod.input_rate/2)
|
||||
return False
|
||||
self.demod.set_relative_frequency(relative_freq)
|
||||
self.last_set_freq_at = time.time()
|
||||
if not self.device.args.startswith('audio-if'):
|
||||
self.frequency = frequency
|
||||
|
||||
def error_tracking(self, last_change_freq):
|
||||
curr_time = time.time()
|
||||
if self.config['demod_type'] == 'fsk4':
|
||||
return None # todo: allow tracking in fsk4 demod
|
||||
UPDATE_TIME = 3
|
||||
if self.last_error_update + UPDATE_TIME > curr_time:
|
||||
return None
|
||||
self.last_error_update = time.time()
|
||||
if not self.demod.is_muted():
|
||||
band = self.demod.get_error_band()
|
||||
freq_error = self.demod.get_freq_error()
|
||||
if band and curr_time >= self.next_band_change:
|
||||
self.next_band_change = curr_time + 20.0
|
||||
self.error_band += band
|
||||
sys.stderr.write('channel %d set error band %d\n' % (self.msgq_id, self.error_band))
|
||||
self.freq_correction += freq_error * 0.15
|
||||
self.freq_correction = int(self.freq_correction)
|
||||
if self.freq_correction > 600:
|
||||
self.freq_correction -= 1200
|
||||
self.error_band += 1
|
||||
elif self.freq_correction < -600:
|
||||
self.freq_correction += 1200
|
||||
self.error_band -= 1
|
||||
self.error_band = min(self.error_band, 2)
|
||||
self.error_band = max(self.error_band, -2)
|
||||
self.tuning_error = int(self.error_band * 1200 + self.freq_correction)
|
||||
e = 0
|
||||
if last_change_freq > 0:
|
||||
e = (self.tuning_error*1e6) / float(last_change_freq)
|
||||
else:
|
||||
e = 0
|
||||
freq_error = 0
|
||||
band = 0
|
||||
### self.set_frequency(self.frequency) # adjust relative frequency with updated tuning_error
|
||||
if self.verbosity >= 10:
|
||||
sys.stderr.write('%f\terror_tracking\t%s\t%d\t%d\t%d\t%d\t%d\t%f\n' % (curr_time, self.name, self.msgq_id, freq_error, self.error_band, self.tuning_error, self.freq_correction, e))
|
||||
d = {'time': time.time(), 'json_type': 'freq_error_tracking', 'name': self.name, 'device': self.device.name, 'freq_error': freq_error, 'band': band, 'error_band': self.error_band, 'tuning_error': self.tuning_error, 'freq_correction': self.freq_correction}
|
||||
if self.frequency:
|
||||
self.set_frequency(self.frequency)
|
||||
return d
|
||||
|
||||
def configure_tdma(self, params):
|
||||
set_tdma = False
|
||||
if params['tdma'] is not None:
|
||||
set_tdma = True
|
||||
self.decoder.set_slotid(params['tdma'])
|
||||
self.demod.clock.set_tdma(set_tdma)
|
||||
if set_tdma == self.tdma_state:
|
||||
return # already in desired state
|
||||
self.tdma_state = set_tdma
|
||||
if set_tdma:
|
||||
hash = '%x%x%x' % (params['nac'], params['sysid'], params['wacn'])
|
||||
if hash not in self.xor_cache:
|
||||
self.xor_cache[hash] = lfsr.p25p2_lfsr(params['nac'], params['sysid'], params['wacn']).xor_chars
|
||||
self.decoder.set_xormask(self.xor_cache[hash], hash)
|
||||
self.decoder.set_nac(params['nac'])
|
||||
rate = 6000
|
||||
else:
|
||||
rate = 4800
|
||||
sps = self.config['if_rate'] / rate
|
||||
self.demod.set_symbol_rate(rate) # this and the foll. call should be merged?
|
||||
self.demod.clock.set_omega(float(sps))
|
||||
|
||||
class du_queue_watcher(threading.Thread):
|
||||
def __init__(self, msgq, callback, **kwds):
|
||||
threading.Thread.__init__ (self, **kwds)
|
||||
self.setDaemon(1)
|
||||
self.msgq = msgq
|
||||
self.callback = callback
|
||||
self.keep_running = True
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
while(self.keep_running):
|
||||
msg = self.msgq.delete_head()
|
||||
if not self.keep_running:
|
||||
break
|
||||
self.callback(msg)
|
||||
|
||||
class rx_block (gr.top_block):
|
||||
|
||||
# Initialize the receiver
|
||||
#
|
||||
def __init__(self, verbosity, config, trunk_conf_file=None, terminal_type=None, track_errors=False, udp_player=None):
|
||||
self.verbosity = verbosity
|
||||
gr.top_block.__init__(self)
|
||||
self.device_id_by_name = {}
|
||||
self.msg_types = {}
|
||||
self.terminal_type = terminal_type
|
||||
self.last_process_update = 0
|
||||
self.last_freq_params = {'freq' : 0.0, 'tgid' : None, 'tag' : "", 'tdma' : None}
|
||||
self.trunk_rx = None
|
||||
self.track_errors = track_errors
|
||||
self.last_change_freq = 0
|
||||
self.sql_db = sql_dbi()
|
||||
self.input_q = gr.msg_queue(20)
|
||||
self.output_q = gr.msg_queue(20)
|
||||
self.last_voice_channel_id = 0
|
||||
self.terminal = op25_terminal(self.input_q, self.output_q, terminal_type)
|
||||
self.configure_devices(config['devices'])
|
||||
self.configure_channels(config['channels'])
|
||||
if trunk_conf_file:
|
||||
self.trunk_rx = trunking.rx_ctl(frequency_set = self.change_freq, debug = self.verbosity, conf_file = trunk_conf_file, logfile_workers=[], send_event=self.send_event)
|
||||
self.sinks = []
|
||||
for chan in self.channels:
|
||||
if len(chan.sinks):
|
||||
self.sinks += chan.sinks
|
||||
if self.is_http_term():
|
||||
for sink in self.sinks:
|
||||
sink.gnuplot.set_interval(_def_interval)
|
||||
sink.gnuplot.set_output_dir(_def_file_dir)
|
||||
|
||||
if udp_player:
|
||||
chan = self.find_audio_channel() # find chan used for audio
|
||||
self.audio = audio_thread("127.0.0.1", chan.audio_port, chan.audio_output, False, chan.audio_gain)
|
||||
else:
|
||||
self.audio = None
|
||||
|
||||
def find_channel_uplink(self, params):
|
||||
channels = []
|
||||
for chan in self.channels:
|
||||
if chan.role != 'uplink':
|
||||
continue
|
||||
channels.append(chan)
|
||||
if self.verbosity > 0:
|
||||
sys.stderr.write('%f find_channel_uplink: selected channel %d (%s) for tuning request type %s frequency %f\n' % (time.time(), chan.msgq_id, chan.name, 'vc', params['uplink'] / 1000000.0))
|
||||
return channels
|
||||
|
||||
def find_channel_cc(self, params):
|
||||
channels = []
|
||||
for chan in self.channels:
|
||||
if chan.role != 'cc':
|
||||
continue
|
||||
if len(chan.nac) and params['nac'] not in chan.nac:
|
||||
continue
|
||||
if len(chan.sysid) and params['sysid'] not in chan.sysid:
|
||||
continue
|
||||
channels.append(chan)
|
||||
if self.verbosity > 0:
|
||||
sys.stderr.write('%f find_channel_cc: selected channel %d (%s) for tuning request type %s frequency %f\n' % (time.time(), chan.msgq_id, chan.name, 'cc', params['freq'] / 1000000.0))
|
||||
return channels
|
||||
|
||||
def find_channel_vc(self, params):
|
||||
channels = []
|
||||
for chan in self.channels: # pass1 - search for vc on non-tunable dev having frequency within band
|
||||
if chan.role != 'vc':
|
||||
continue
|
||||
if chan.device.tunable:
|
||||
continue
|
||||
if abs(params['freq'] - chan.device.frequency) >= chan.demod.relative_limit:
|
||||
#sys.stderr.write('%f skipping channel %d frequency %f dev freq %f limit %f\n' % (time.time(), chan.msgq_id, params['freq'] / 1000000.0, chan.device.frequency / 1000000.0, chan.demod.relative_limit / 1000000.0))
|
||||
continue
|
||||
channels.append(chan)
|
||||
if self.verbosity > 0:
|
||||
sys.stderr.write('%f find_channel_vc: selected channel %d (%s) for tuning request type %s frequency %f (1)\n' % (time.time(), chan.msgq_id, chan.name, 'vc', params['freq'] / 1000000.0))
|
||||
return channels
|
||||
for chan in self.channels: # pass2 - search for vc on tunable dev
|
||||
if chan.role != 'vc':
|
||||
continue
|
||||
if not chan.device.tunable:
|
||||
continue
|
||||
channels.append(chan)
|
||||
if self.verbosity > 0:
|
||||
sys.stderr.write('%f find_channel_vc: selected channel %d (%s) for tuning request type %s frequency %f (2)\n' % (time.time(), chan.msgq_id, chan.name, 'vc', params['freq'] / 1000000.0))
|
||||
return channels
|
||||
return [] # pass 1 and 2 failed
|
||||
|
||||
def do_error_tracking(self):
|
||||
if not self.track_errors:
|
||||
return
|
||||
for chan in self.channels:
|
||||
d = chan.error_tracking(self.last_change_freq)
|
||||
if d is not None and not self.input_q.full_p():
|
||||
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
|
||||
self.input_q.insert_tail(msg)
|
||||
|
||||
def change_freq(self, params):
|
||||
self.last_freq_params = params
|
||||
freq = params['freq']
|
||||
self.last_change_freq = freq
|
||||
channel_type = params['channel_type'] # vc or cc
|
||||
self.uplink_change_freq(params)
|
||||
if channel_type == 'vc':
|
||||
channels = self.find_channel_vc(params)
|
||||
elif channel_type == 'cc':
|
||||
channels = self.find_channel_cc(params)
|
||||
else:
|
||||
raise ValueError('change_freq: invalid channel_type: %s' % channel_type)
|
||||
if len(channels) == 0:
|
||||
sys.stderr.write('change_freq: no channel(s) found for %s frequency %f\n' % (channel_type, freq/1000000.0))
|
||||
return
|
||||
for chan in channels:
|
||||
chan.device.set_frequency(freq)
|
||||
chan.set_frequency(freq)
|
||||
chan.configure_tdma(params)
|
||||
self.freq_update()
|
||||
if channel_type == 'vc':
|
||||
self.last_voice_channel_id = chan.msgq_id
|
||||
#return
|
||||
if self.trunk_rx is None:
|
||||
return
|
||||
voice_chans = [chan for chan in self.channels if chan.role == 'vc']
|
||||
voice_state = channel_type == 'vc'
|
||||
# FIXME: fsk4 case needs work/testing
|
||||
for chan in voice_chans:
|
||||
if voice_state and chan.msgq_id == self.last_voice_channel_id:
|
||||
chan.demod.set_muted(False)
|
||||
else:
|
||||
chan.demod.set_muted(True)
|
||||
|
||||
def uplink_change_freq(self, params):
|
||||
channel_type = params['channel_type'] # vc or cc
|
||||
if 'uplink' not in params:
|
||||
return
|
||||
if channel_type != 'vc':
|
||||
return
|
||||
uplink = params['uplink']
|
||||
channels = self.find_channel_uplink(params)
|
||||
for chan in channels:
|
||||
chan.device.set_frequency(uplink)
|
||||
chan.set_frequency(uplink)
|
||||
chan.configure_tdma(params)
|
||||
if self.verbosity > 0:
|
||||
sys.stderr.write('set uplink frequency %f, channel %s\n' % (uplink / 1000000.0, chan.name))
|
||||
|
||||
def is_http_term(self):
|
||||
if self.terminal_type.startswith('http:'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def process_terminal_msg(self, msg):
|
||||
# return true = end top block
|
||||
RX_COMMANDS = 'skip lockout hold'.split()
|
||||
s = msg.to_string()
|
||||
t = msg.type()
|
||||
if t == -4:
|
||||
d = json.loads(s)
|
||||
s = d['command']
|
||||
if type(s) is not str and isinstance(s, bytes):
|
||||
# should only get here if python3
|
||||
s = s.decode()
|
||||
if s == 'quit': return True
|
||||
elif s == 'update': ## deprecated here: to be removed
|
||||
pass
|
||||
# self.process_update()
|
||||
elif s == 'set_freq':
|
||||
sys.stderr.write('set_freq not supported\n')
|
||||
return
|
||||
#freq = msg.arg1()
|
||||
#self.last_freq_params['freq'] = freq
|
||||
#self.set_freq(freq)
|
||||
elif s == 'adj_tune':
|
||||
freq = msg.arg1()
|
||||
elif s == 'dump_tgids':
|
||||
self.trunk_rx.dump_tgids()
|
||||
elif s == 'reload_tags':
|
||||
nac = msg.arg1()
|
||||
self.trunk_rx.reload_tags(int(nac))
|
||||
elif s == 'add_default_config':
|
||||
nac = msg.arg1()
|
||||
self.trunk_rx.add_default_config(int(nac))
|
||||
elif s in RX_COMMANDS:
|
||||
if self.trunk_rx is not None:
|
||||
self.trunk_rx.process_qmsg(msg)
|
||||
elif s == 'settings-enable' and self.trunk_rx is not None:
|
||||
self.trunk_rx.enable_status(d['data'])
|
||||
return False
|
||||
|
||||
def process_ajax(self):
|
||||
if not self.is_http_term():
|
||||
return
|
||||
if self.input_q.full_p():
|
||||
return
|
||||
filenames = [sink.gnuplot.filename for sink in self.sinks if sink.gnuplot.filename]
|
||||
error = []
|
||||
for chan in self.channels:
|
||||
if hasattr(chan.demod, 'get_freq_error'):
|
||||
error.append(chan.demod.get_freq_error())
|
||||
d = {'json_type': 'rx_update', 'error': error, 'files': filenames, 'time': time.time()}
|
||||
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
|
||||
self.input_q.insert_tail(msg)
|
||||
|
||||
def process_update(self):
|
||||
UPDATE_INTERVAL = 1.0 # sec.
|
||||
now = time.time()
|
||||
if now < self.last_process_update + UPDATE_INTERVAL:
|
||||
return
|
||||
self.last_process_update = now
|
||||
self.freq_update()
|
||||
if self.input_q.full_p():
|
||||
return
|
||||
if self.trunk_rx is None:
|
||||
return ## possible race cond - just ignore
|
||||
js = self.trunk_rx.to_json()
|
||||
msg = gr.message().make_from_string(js, -4, 0, 0)
|
||||
self.input_q.insert_tail(msg)
|
||||
self.process_ajax()
|
||||
|
||||
def send_event(self, d): ## called from trunking module to send json msgs / updates to client
|
||||
if d is not None:
|
||||
self.sql_db.event(d)
|
||||
if d and not self.input_q.full_p():
|
||||
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
|
||||
self.input_q.insert_tail(msg)
|
||||
self.process_update()
|
||||
|
||||
def freq_update(self):
|
||||
if self.input_q.full_p():
|
||||
return
|
||||
params = self.last_freq_params
|
||||
params['json_type'] = 'change_freq'
|
||||
params['current_time'] = time.time()
|
||||
js = json.dumps(params)
|
||||
msg = gr.message().make_from_string(js, -4, 0, 0)
|
||||
self.input_q.insert_tail(msg)
|
||||
|
||||
def process_msg(self, msg):
|
||||
mtype = msg.type()
|
||||
if mtype == -2 or mtype == -4:
|
||||
self.process_terminal_msg(msg)
|
||||
else:
|
||||
self.process_channel_msg(msg, mtype)
|
||||
|
||||
def process_channel_msg(self, msg, mtype):
|
||||
msgtext = msg.to_string()
|
||||
aa55 = trunking.get_ordinals(msgtext[:2])
|
||||
assert aa55 == 0xaa55
|
||||
msgq_id = trunking.get_ordinals(msgtext[2:4])
|
||||
msgtext = msgtext[4:]
|
||||
if mtype == -5:
|
||||
self.process_nxdn_msg(msgtext)
|
||||
else:
|
||||
self.process_trunked_qmsg(msg, msgq_id)
|
||||
|
||||
def process_nxdn_msg(self, s):
|
||||
if isinstance(s[0], str): # for python 2/3
|
||||
s = [ord(x) for x in s]
|
||||
msgtype = chr(s[0])
|
||||
lich = s[1]
|
||||
if self.verbosity > 2:
|
||||
sys.stderr.write ('process_nxdn_msg %s lich %x\n' % (msgtype, lich))
|
||||
if msgtype == 'c': # CAC type
|
||||
ran = s[2] & 0x3f
|
||||
msg = cac_message(s[2:])
|
||||
if msg['msg_type'] == 'CCH_INFO' and self.verbosity:
|
||||
sys.stderr.write ('%-10s %-10s system %d site %d ran %d\n' % (msg['cc1']/1e6, msg['cc2']/1e6, msg['location_id']['system'], msg['location_id']['site'], ran))
|
||||
if self.verbosity > 1:
|
||||
sys.stderr.write('%s\n' % json.dumps(msg))
|
||||
|
||||
def filtered(self, msg, msgq_id):
|
||||
# return True if msg should be suppressed
|
||||
chan = self.channels[msgq_id-1]
|
||||
t = msg.type()
|
||||
if chan.role == 'vc' and t in [7, 12]: ## suppress tsbk/mbt/pdu received over vc
|
||||
return True
|
||||
if chan.role == 'uplink' and t in [-1, 7, 12]: ## suppress as above and also timeout
|
||||
return True
|
||||
return False
|
||||
|
||||
def process_trunked_qmsg(self, msg, msgq_id): # p25 trunked message
|
||||
if self.trunk_rx is None:
|
||||
return
|
||||
if self.filtered(msg, msgq_id):
|
||||
return
|
||||
self.trunk_rx.process_qmsg(msg)
|
||||
self.trunk_rx.parallel_hunt_cc()
|
||||
self.do_error_tracking()
|
||||
|
||||
def configure_devices(self, config):
|
||||
self.devices = []
|
||||
for cfg in config:
|
||||
self.device_id_by_name[cfg['name']] = len(self.devices)
|
||||
self.devices.append(device(cfg, self))
|
||||
|
||||
def find_trunked_device(self, chan, requested_dev):
|
||||
if len(self.devices) == 1: # single SDR
|
||||
return self.devices[0]
|
||||
for dev in self.devices:
|
||||
if dev.name == requested_dev:
|
||||
return dev
|
||||
return None
|
||||
|
||||
def find_device(self, chan, requested_dev):
|
||||
if 'decode' in chan.keys() and chan['decode'].startswith('p25_decoder'):
|
||||
return self.find_trunked_device(chan, requested_dev)
|
||||
for dev in self.devices:
|
||||
if dev.args.startswith('audio:') and chan['demod_type'] == 'fsk4':
|
||||
return dev
|
||||
d = abs(chan['frequency'] - dev.frequency)
|
||||
nf = dev.sample_rate // 2
|
||||
if d + 6250 <= nf:
|
||||
return dev
|
||||
return None
|
||||
|
||||
def configure_channels(self, config):
|
||||
self.channels = []
|
||||
for cfg in config:
|
||||
decode_d = {'role': '', 'dev': ''}
|
||||
if 'decode' in cfg.keys() and cfg['decode'].startswith('p25_decoder'):
|
||||
decode_p = cfg['decode'].split(':')[1:]
|
||||
for p in decode_p: # possible keys: dev, role, nac, sysid; valid roles: cc vc
|
||||
(k, v) = p.split('=')
|
||||
if k == 'nac' or k == 'sysid':
|
||||
v = [int(x, base=0) for x in v.split(',')]
|
||||
decode_d[k] = v
|
||||
dev = self.find_device(cfg, decode_d['dev'])
|
||||
if dev is None:
|
||||
sys.stderr.write('* * * No device found for channel %s- ignoring!\n' % cfg['name'])
|
||||
continue
|
||||
msgq_id = len(self.channels) + 1
|
||||
chan = channel(cfg, dev, self.verbosity, msgq=self.output_q, msgq_id = msgq_id, role=decode_d['role'])
|
||||
for k in decode_d.keys():
|
||||
setattr(chan, k, decode_d[k])
|
||||
self.channels.append(chan)
|
||||
self.connect(dev.src, chan.demod, chan.decoder)
|
||||
sys.stderr.write('assigning channel "%s" (channel id %d) to device "%s"\n' % (chan.name, chan.msgq_id, dev.name))
|
||||
if 'log_if' in cfg.keys():
|
||||
chan.logfile_if = blocks.file_sink(gr.sizeof_gr_complex, 'if-%d-%s' % (chan.config['if_rate'], cfg['log_if']))
|
||||
if cfg['demod_type'] == 'cqpsk':
|
||||
chan.demod.connect_complex('agc', chan.logfile_if)
|
||||
else:
|
||||
chan.demod.connect_complex('if_out', chan.logfile_if)
|
||||
if 'log_symbols' in cfg.keys():
|
||||
chan.logfile = blocks.file_sink(gr.sizeof_char, cfg['log_symbols'])
|
||||
self.connect(chan.demod, chan.logfile)
|
||||
|
||||
def find_audio_channel(self):
|
||||
for chan in self.channels: # pass1 - look for 'vc'
|
||||
if chan.role == 'vc' and chan.audio_port:
|
||||
return chan
|
||||
for chan in self.channels: # pass2 - any chan with audio port specified
|
||||
if chan.audio_port:
|
||||
return chan
|
||||
return self.channels[0]
|
||||
|
||||
def scan_channels(self):
|
||||
for chan in self.channels:
|
||||
sys.stderr.write('scan %s: error %d\n' % (chan.config['frequency'], chan.demod.get_freq_error()))
|
||||
|
||||
class rx_main(object):
|
||||
def __init__(self):
|
||||
self.keep_running = True
|
||||
|
||||
# command line argument parsing
|
||||
parser = OptionParser(option_class=eng_option)
|
||||
parser.add_option("-c", "--config-file", type="string", default=None, help="specify config file name")
|
||||
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
|
||||
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
|
||||
parser.add_option("-M", "--monitor-stdin", action="store_false", default=True, help="enable press ENTER to quit")
|
||||
parser.add_option("-T", "--trunk-conf-file", type="string", default=None, help="trunking config file name")
|
||||
parser.add_option("-l", "--terminal-type", type="string", default="curses", help="'curses' or udp port or 'http:host:port'")
|
||||
parser.add_option("-X", "--freq-error-tracking", action="store_true", default=False, help="enable experimental frequency error tracking")
|
||||
parser.add_option("-U", "--udp-player", action="store_true", default=False, help="enable built-in udp audio player")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
self.options = options
|
||||
|
||||
# wait for gdb
|
||||
if options.pause:
|
||||
print ('Ready for GDB to attach (pid = %d)' % (os.getpid(),))
|
||||
raw_input("Press 'Enter' to continue...")
|
||||
|
||||
if options.config_file == '-':
|
||||
config = json.loads(sys.stdin.read())
|
||||
else:
|
||||
config = json.loads(open(options.config_file).read())
|
||||
self.tb = rx_block(options.verbosity, config = byteify(config), trunk_conf_file=options.trunk_conf_file, terminal_type=options.terminal_type, track_errors=options.freq_error_tracking, udp_player = options.udp_player)
|
||||
sys.stderr.write('python version detected: %s\n' % sys.version)
|
||||
sys.stderr.flush()
|
||||
|
||||
def run(self):
|
||||
self.tb.start()
|
||||
if self.options.monitor_stdin:
|
||||
print("Running. press ENTER to quit")
|
||||
while self.keep_running:
|
||||
if self.options.monitor_stdin and select.select([sys.stdin,],[],[],0.0)[0]:
|
||||
c = sys.stdin.read(1)
|
||||
self.keep_running = False
|
||||
break
|
||||
msg = self.tb.output_q.delete_head()
|
||||
if self.tb.process_msg(msg):
|
||||
self.keep_running = False
|
||||
break
|
||||
print('Quitting - now stopping top block')
|
||||
self.tb.stop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
rx = rx_main()
|
||||
try:
|
||||
rx.run()
|
||||
except KeyboardInterrupt:
|
||||
rx.keep_running = False
|
||||
print('Program ending')
|
||||
time.sleep(1)
|
|
@ -0,0 +1,316 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# (C) Copyright 2020 Max H. Parke, KA1RBI
|
||||
#
|
||||
# This file is part of OP25
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
#
|
||||
# nxdn trunking:
|
||||
# - CAC decoding
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append('tdma')
|
||||
from bit_utils import *
|
||||
|
||||
def locid(id):
|
||||
save_id = mk_int(id)
|
||||
cat = mk_int(id[:2])
|
||||
if cat == 0:
|
||||
ssize = 10
|
||||
elif cat == 2:
|
||||
ssize = 14
|
||||
elif cat == 1:
|
||||
ssize = 17
|
||||
else:
|
||||
return {'category': -1, 'system': -1, 'site': -1, 'id': '0x%x' % save_id}
|
||||
id = id[2:]
|
||||
syscode = mk_int(id[:ssize])
|
||||
id = id[ssize:]
|
||||
sitecode = mk_int(id)
|
||||
return {'category': cat, 'system': syscode, 'site': sitecode, 'id': '0x%x' % save_id}
|
||||
|
||||
def mk_freq(f):
|
||||
### todo: UHF currently untested; may fail at 400 MHz
|
||||
return int(f * 1250 + 100000000) # frequency in Hz
|
||||
|
||||
def cac_message(s):
|
||||
d = {}
|
||||
bits = []
|
||||
for c in s:
|
||||
for i in range(8):
|
||||
bits.append((c >> (7-i)) & 1)
|
||||
d['structure'] = mk_int(bits[:2])
|
||||
d['ran'] = mk_int(bits[2:8])
|
||||
bits = bits[8:]
|
||||
msg_type = mk_int(bits[2:8])
|
||||
d['msg_typeid'] = msg_type
|
||||
if msg_type == 0x18: # SITE_INFO
|
||||
assert len(bits) == 144
|
||||
d['msg_type'] = 'SITE_INFO'
|
||||
bits = bits[8:]
|
||||
d['location_id'] = locid(bits[:24])
|
||||
bits = bits[24:]
|
||||
d['channel_info'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['service_info'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['restr_info'] = mk_int(bits[:24])
|
||||
bits = bits[24:]
|
||||
d['access_info'] = mk_int(bits[:24])
|
||||
bits = bits[24:]
|
||||
d['version_no'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['adjacent_alloc'] = mk_int(bits[:4])
|
||||
bits = bits[4:]
|
||||
d['cc1'] = mk_int(bits[:10])
|
||||
bits = bits[10:]
|
||||
d['cc2'] = mk_int(bits[:10])
|
||||
elif msg_type == 0x19: # SRV_INFO
|
||||
assert len(bits) >= 72
|
||||
d['msg_type'] = 'SRV_INFO'
|
||||
bits = bits[8:]
|
||||
d['location_id'] = locid(bits[:24])
|
||||
bits = bits[24:]
|
||||
d['service_info'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['restr_info'] = mk_int(bits[:24])
|
||||
elif msg_type == 0x1a: # CCH_INFO
|
||||
assert len(bits) >= 72
|
||||
d['msg_type'] = 'CCH_INFO'
|
||||
bits = bits[8:]
|
||||
d['location_id'] = locid(bits[:24])
|
||||
bits = bits[24:]
|
||||
d['flags1'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['cc1'] = mk_freq(mk_int(bits[:16]))
|
||||
bits = bits[16:]
|
||||
d['cc2'] = mk_freq(mk_int(bits[:16]))
|
||||
elif msg_type == 0x1b: # ADJ_SITE_INFO
|
||||
assert len(bits) >= 72
|
||||
d['msg_type'] = 'ADJ_SITE_INFO'
|
||||
d1 = {}
|
||||
d2 = {}
|
||||
bits = bits[8:]
|
||||
d1['location'] = locid(bits[:24])
|
||||
bits = bits[24:]
|
||||
d1['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d1['cc'] = mk_freq(mk_int(bits[:16]))
|
||||
bits = bits[16:]
|
||||
d2['location'] = locid(bits[:24])
|
||||
bits = bits[24:]
|
||||
d2['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d2['cc'] = mk_freq(mk_int(bits[:16]))
|
||||
bits = bits[16:]
|
||||
d['sites'] = [d1, d2]
|
||||
#d['location_3'] = locid(bits[:24])
|
||||
#bits = bits[24:]
|
||||
#d['option_3'] = mk_int(bits[:6])
|
||||
#bits = bits[6:]
|
||||
#d['cc_3'] = mk_int(bits[:10])
|
||||
elif msg_type == 0x01: # VCALL_RESP
|
||||
assert len(bits) >= 64
|
||||
d['msg_type'] = 'VCALL_RESP'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['call_type'] = mk_int(bits[:3])
|
||||
d['call_option'] = mk_int(bits[3:8])
|
||||
bits = bits[8:]
|
||||
d['source_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['destination_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['cause'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
elif msg_type == 0x09: # DCALL_RESP
|
||||
assert len(bits) >= 64
|
||||
d['msg_type'] = 'DCALL_RESP'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['call_type'] = mk_int(bits[:3])
|
||||
d['call_option'] = mk_int(bits[3:8])
|
||||
bits = bits[8:]
|
||||
d['source_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['destination_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['cause'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
elif msg_type == 0x04: # VCALL_ASSGN
|
||||
assert len(bits) >= 72
|
||||
bits2 = bits
|
||||
s = ''
|
||||
while len(bits2):
|
||||
s += '%02x' % mk_int(bits2[:8])
|
||||
bits2 = bits2[8:]
|
||||
d['hexdata'] = s
|
||||
d['msg_type'] = 'VCALL_ASSGN'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['call_type'] = mk_int(bits[:3])
|
||||
d['call_option'] = mk_int(bits[3:8])
|
||||
bits = bits[8:]
|
||||
d['source_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['group_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['timer'] = mk_int(bits[:8])
|
||||
d['channel'] = mk_int(bits[6:16])
|
||||
bits = bits[8:]
|
||||
d['f1'] = mk_freq(mk_int(bits[:16]))
|
||||
bits = bits[16:]
|
||||
d['f2'] = mk_freq(mk_int(bits[:16]))
|
||||
elif msg_type == 0x0e: # DCALL_ASSGN
|
||||
assert len(bits) >= 104
|
||||
d['msg_type'] = 'DCALL_ASSGN'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['call_type'] = mk_int(bits[:3])
|
||||
d['call_option'] = mk_int(bits[3:8])
|
||||
bits = bits[8:]
|
||||
d['source_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['group_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['timer'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['f1'] = mk_freq(mk_int(bits[:16]))
|
||||
bits = bits[16:]
|
||||
d['f2'] = mk_freq(mk_int(bits[:16]))
|
||||
elif msg_type == 0x20: # REG_RESP
|
||||
assert len(bits) >= 72
|
||||
d['msg_type'] = 'REG_RESP'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['location id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['unit_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['group_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['cause'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['visitor_unit'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['visitor_group'] = mk_int(bits[:16])
|
||||
elif msg_type == 0x22: # REG_C_RESP
|
||||
assert len(bits) >= 56
|
||||
d['msg_type'] = 'REG_C_RESP'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['location id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['unit_id'] = mk_int(bits[:16])
|
||||
elif msg_type == 0x24: # GRP_REG_RESP
|
||||
assert len(bits) >= 72
|
||||
d['msg_type'] = 'GRP_REG_RESP'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['destination id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['group_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['cause'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['visitor_group_id'] = mk_int(bits[:16])
|
||||
elif msg_type == 0x32: # STAT_REQ
|
||||
assert len(bits) >= 72
|
||||
d['msg_type'] = 'STAT_REQ'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['call_type'] = mk_int(bits[:3])
|
||||
d['call_option'] = mk_int(bits[3:8])
|
||||
bits = bits[8:]
|
||||
d['source id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['destination_id'] = mk_int(bits[:16])
|
||||
bits = bits[8:]
|
||||
d['spare'] = mk_int(bits[:8])
|
||||
status = bits[8:]
|
||||
elif msg_type == 0x33: # STAT_RESP
|
||||
assert len(bits) >= 64
|
||||
d['msg_type'] = 'STAT_RESP'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['call_type'] = mk_int(bits[:3])
|
||||
d['call_option'] = mk_int(bits[3:8])
|
||||
bits = bits[8:]
|
||||
d['source id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['destination_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['cause'] = mk_int(bits[:8])
|
||||
elif msg_type == 0x38: # SDCALL_REQ_HEADER
|
||||
assert len(bits) >= 64
|
||||
d['msg_type'] = 'SDCALL_REQ_HEADER'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['call_type'] = mk_int(bits[:3])
|
||||
d['call_option'] = mk_int(bits[3:8])
|
||||
bits = bits[8:]
|
||||
d['source id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['destination_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['cipher_type'] = mk_int(bits[:2])
|
||||
d['key_id'] = mk_int(bits[2:8])
|
||||
elif msg_type == 0x39: # SDCALL_REQ_USERDATA
|
||||
assert len(bits) >= 64
|
||||
d['msg_type'] = 'SDCALL_REQ_USERDATA'
|
||||
bits = bits[8:]
|
||||
d['packet_frame'] = mk_int(bits[:4])
|
||||
d['block_number'] = mk_int(bits[4:8])
|
||||
bits = bits[8:]
|
||||
s = ''
|
||||
while len(bits):
|
||||
s += '%02x' % mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['hexdata'] = s
|
||||
elif msg_type == 0x3b: # SDCALL_RESP
|
||||
assert len(bits) >= 64
|
||||
d['msg_type'] = 'SDCALL_RESP'
|
||||
bits = bits[8:]
|
||||
d['option'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
d['call_type'] = mk_int(bits[:3])
|
||||
d['call_option'] = mk_int(bits[3:8])
|
||||
bits = bits[8:]
|
||||
d['source id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['destination_id'] = mk_int(bits[:16])
|
||||
bits = bits[16:]
|
||||
d['cause'] = mk_int(bits[:8])
|
||||
bits = bits[8:]
|
||||
else: # msg type unhandled
|
||||
d['msg_type'] = 'UNSUPPORTED 0x%x' % (msg_type)
|
||||
return d
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=op25-liq
|
||||
After=syslog.target network.target nss-lookup.target network-online.target
|
||||
Requires=network-online.target
|
||||
|
||||
[Service]
|
||||
User=1000
|
||||
Group=1000
|
||||
WorkingDirectory=/home/pi/op25/op25/gr-op25_repeater/apps
|
||||
ExecStart=/usr/bin/liquidsoap op25.liq
|
||||
RestartSec=5
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/liquidsoap
|
||||
|
||||
# Example liquidsoap streaming from op25 to icecast
|
||||
# (c) 2019, 2020 gnorbury@bondcar.com, wllmbecks@gmail.com
|
||||
#
|
||||
|
||||
set("log.stdout", true)
|
||||
set("log.file", false)
|
||||
set("log.level", 1)
|
||||
|
||||
# Make the native sample rate compatible with op25
|
||||
set("frame.audio.samplerate", 8000)
|
||||
|
||||
input = mksafe(input.external(buffer=0.02, channels=2, samplerate=8000, restart_on_error=false, "./audio.py -x 2 -s"))
|
||||
# Consider increasing the buffer value on slow systems such as RPi3. e.g. buffer=0.25
|
||||
# Longer buffer results in less choppy audio but at the expense of increased latency.
|
||||
|
||||
|
||||
|
||||
# OPTIONAL AUDIO SIGNAL PROCESSING BLOCKS
|
||||
# Uncomment to enable
|
||||
#
|
||||
# High pass filter
|
||||
#input = filter.iir.butterworth.high(frequency = 200.0, order = 4, input)
|
||||
|
||||
# Low pass filter
|
||||
#input = filter.iir.butterworth.low(frequency = 3250.0, order = 4, input)
|
||||
|
||||
# Normalization
|
||||
#input = normalize(input, gain_max = 3.0, gain_min = -3.0, target = -16.0, threshold = -40.0)
|
||||
|
||||
|
||||
|
||||
# LOCAL AUDIO OUTPUT
|
||||
# Uncomment the appropriate line below to enable local sound
|
||||
#
|
||||
# Default audio subsystem
|
||||
out (input)
|
||||
#
|
||||
# PulseAudio
|
||||
#output.pulseaudio(input)
|
||||
#
|
||||
# ALSA
|
||||
#output.alsa(input)
|
||||
|
||||
|
||||
|
||||
# ICECAST STREAMING
|
||||
# Uncomment to enable output to an icecast server
|
||||
# Change the "host", "password", and "mount" strings appropriately first!
|
||||
# For metadata to work properly, the host address given here MUST MATCH the address in op25's meta.json file
|
||||
#
|
||||
#output.icecast(%mp3(bitrate=16, samplerate=22050, stereo=false), description="op25", genre="Public Safety", url="", fallible=false, icy_metadata="false", host="localhost", port=8000, mount="mountpoint", password="hackme", mean(input))
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import os
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///%s/../op25-data.db' % (os.path.dirname(__file__))
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
@ -0,0 +1,844 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2021 Max H. Parke KA1RBI, Michael Rose
|
||||
#
|
||||
# This file is part of OP25
|
||||
#
|
||||
# OP25 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 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
||||
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
import time
|
||||
from time import sleep
|
||||
from time import gmtime, strftime
|
||||
import os
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
import sys
|
||||
import traceback
|
||||
import math
|
||||
import json
|
||||
import click
|
||||
import datetime
|
||||
from datatables import ColumnDT, DataTables
|
||||
from flask import Flask, jsonify, render_template, request, redirect, session
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy import func, desc, and_, or_, case, delete, insert, update, exc
|
||||
from sqlalchemy.orm import Query
|
||||
from sqlalchemy.exc import OperationalError
|
||||
import sqlalchemy.types as types
|
||||
from shutil import copyfile
|
||||
|
||||
sys.path.append('..') # for emap
|
||||
from emap import oplog_map, cc_events, cc_desc
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile("../app.cfg")
|
||||
app.config['SQLALCHEMY_ECHO'] = False # set to True to send sql statements to the console
|
||||
|
||||
# enables session variables to be used
|
||||
app.secret_key = b'kH8HT0ucrh' # random bytes - this key not used anywhere else
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
# db.init_app(app)
|
||||
|
||||
with app.app_context():
|
||||
try:
|
||||
db.reflect()
|
||||
except OperationalError as e:
|
||||
raise(e) # database is locked by another process
|
||||
|
||||
class MyDateType(types.TypeDecorator):
|
||||
impl = types.REAL
|
||||
def process_result_value(self, value, dialect):
|
||||
return datetime.datetime.fromtimestamp(value).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
class column_helper(object):
|
||||
# convenience class - enables columns to be referenced as
|
||||
# for example, Foo.bar instead of Foo['bar']
|
||||
def __init__(self, table):
|
||||
self.table_ = db.metadata.tables[table]
|
||||
cols = self.table_.columns
|
||||
for k in cols.keys():
|
||||
setattr(self, k, cols[k])
|
||||
|
||||
def dbstate():
|
||||
database = app.config['SQLALCHEMY_DATABASE_URI'][10:]
|
||||
if not os.path.isfile(database):
|
||||
return 1 # db file does not exist
|
||||
fs = os.path.getsize(database)
|
||||
if fs < 1024:
|
||||
return 2 # file size too small
|
||||
DataStore = column_helper('data_store')
|
||||
rows = db.session.query(DataStore.id).count()
|
||||
if rows < 1:
|
||||
return 4 # no rows present
|
||||
return 0
|
||||
|
||||
# clears the sm (successMessage) after being used in jinja
|
||||
def clear_sm():
|
||||
session['sm'] = 0
|
||||
return '' #must be an empty string or 'None' is displayed in the template
|
||||
|
||||
def t_gmt():
|
||||
t = time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime())
|
||||
return t
|
||||
|
||||
def t_loc():
|
||||
t = strftime("%a, %d %b %Y %H:%M:%S %Z")
|
||||
return t
|
||||
|
||||
# make these functions available to jinja
|
||||
app.jinja_env.globals.update(t_gmt=t_gmt)
|
||||
app.jinja_env.globals.update(t_loc=t_loc)
|
||||
app.jinja_env.globals.update(clear_sm=clear_sm)
|
||||
|
||||
# for displaying the db file size, shamelessly stolen from SO
|
||||
def convert_size(size_bytes):
|
||||
if size_bytes == 0:
|
||||
return "0 B"
|
||||
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
return "%s %s" % (s, size_name[i])
|
||||
|
||||
def dbStats():
|
||||
DataStore = column_helper('data_store')
|
||||
DataStore = column_helper('data_store')
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
DataStore.time.type = MyDateType()
|
||||
rows = db.session.query(func.count(DataStore.id)).scalar()
|
||||
if rows == 0:
|
||||
return(0, 0, 0, 0, 0, 0, 0)
|
||||
|
||||
sys_count = db.session.query(DataStore.sysid) \
|
||||
.distinct(DataStore.sysid) \
|
||||
.group_by(DataStore.sysid) \
|
||||
.filter(DataStore.sysid != 0) \
|
||||
.count()
|
||||
|
||||
# TODO: talkgroups and subs should be distinct by system
|
||||
talkgroups = db.session.query(DataStore.tgid) \
|
||||
.distinct(DataStore.tgid) \
|
||||
.group_by(DataStore.tgid) \
|
||||
.count()
|
||||
|
||||
subs = db.session.query(DataStore.suid) \
|
||||
.distinct(DataStore.suid) \
|
||||
.group_by(DataStore.suid) \
|
||||
.count()
|
||||
|
||||
firstRec = db.session.query(DataStore.time, func.min(DataStore.time)).scalar()
|
||||
lastRec = db.session.query(DataStore.time, func.max(DataStore.time)).scalar()
|
||||
f = app.config['SQLALCHEMY_DATABASE_URI'][10:] # db file name
|
||||
dbsize = convert_size(os.path.getsize(f))
|
||||
return(rows, sys_count, talkgroups, subs, firstRec, lastRec, dbsize, f)
|
||||
|
||||
def sysList():
|
||||
DataStore = column_helper('data_store')
|
||||
rows = db.session.query(func.count(DataStore.id)).scalar()
|
||||
if rows == 0:
|
||||
return []
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
sysList = db.session.query(DataStore.sysid, SysIDTags.tag.label('tag')) \
|
||||
.distinct(DataStore.sysid) \
|
||||
.outerjoin(SysIDTags.table_, SysIDTags.sysid == DataStore.sysid) \
|
||||
.filter(DataStore.sysid != 0)
|
||||
return sysList
|
||||
|
||||
def read_tsv(filename): # used by import_tsv and inspect_tsv, careful w/ changes
|
||||
rows = []
|
||||
with open(filename, 'r') as f:
|
||||
lines = f.read().rstrip().split('\n')
|
||||
for i in range(len(lines)):
|
||||
a = lines[i].split('\t')
|
||||
if i == 0: # check hdr
|
||||
if not a[0].strip().isdigit():
|
||||
continue
|
||||
if not a[0].strip().isdigit(): # check each a[0] for wildcards and skip (continue) if wildcards found
|
||||
continue
|
||||
rid = int(a[0])
|
||||
tag = a[1]
|
||||
priority = 0 if len(a) < 3 else int(a[2])
|
||||
s = (rid, tag, priority)
|
||||
rows.append(s)
|
||||
return rows
|
||||
|
||||
def import_tsv(argv):
|
||||
UnitIDTags = column_helper('unit_id_tags')
|
||||
TGIDTags = column_helper('tgid_tags')
|
||||
cmd = argv[1]
|
||||
filename = argv[2]
|
||||
sysid = int(argv[3])
|
||||
if cmd == 'import_tgid':
|
||||
tbl = TGIDTags
|
||||
elif cmd == 'import_unit':
|
||||
tbl = UnitIDTags
|
||||
else:
|
||||
print('%s unsupported' % (cmd))
|
||||
return
|
||||
rows = read_tsv(filename)
|
||||
rm = 0 # records matched
|
||||
nr = 0 # new records
|
||||
dr = 0 # duplicate records
|
||||
if len(rows):
|
||||
for i in rows:
|
||||
recCount = db.session.query(tbl.table_).where(and_(tbl.rid == i[0], tbl.sysid == argv[3])).count()
|
||||
if recCount == 1:
|
||||
# update record
|
||||
q = update(tbl.table_) \
|
||||
.where(and_(tbl.rid == i[0], tbl.sysid == argv[3])) \
|
||||
.values(rid = int(i[0]), sysid = int(argv[3]), tag = i[1], priority = int(i[2]))
|
||||
db.session.execute(q)
|
||||
db.session.commit()
|
||||
rm +=1
|
||||
elif recCount == 0:
|
||||
# insert record
|
||||
q = insert(tbl.table_).values(rid = int(i[0]), sysid = int(argv[3]), tag = i[1], priority = int(i[2]))
|
||||
db.session.execute(q)
|
||||
db.session.commit()
|
||||
nr += 1
|
||||
else:
|
||||
# delete all of the duplicates and insert new (duplicates break things)
|
||||
print('command %s - db error - %s records for %s %s' % (cmd, recCount, i[0], i[1]))
|
||||
delRec = delete(TGIDTags.table_).where(and_(tbl.rid == i[0], tbl.sysid == argv[3]))
|
||||
db.session.execute(delRec)
|
||||
db.session.commit()
|
||||
q = insert(tbl.table_).values(rid = int(i[0]), sysid = int(argv[3]), tag = i[1], priority = int(i[2]))
|
||||
db.session.execute(q)
|
||||
db.session.commit()
|
||||
dr += 1
|
||||
return(rm, nr, dr)
|
||||
|
||||
@app.route("/")
|
||||
def home():
|
||||
ds = dbstate()
|
||||
if ds != 0:
|
||||
return redirect('error?code=%s' % ds)
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
params['cc_desc'] = cc_desc
|
||||
return render_template("home.html", project="op25", params=params, dbstats=dbStats(), sysList=sysList())
|
||||
|
||||
@app.route("/about")
|
||||
def about():
|
||||
ds = dbstate()
|
||||
if ds != 0:
|
||||
return redirect('error?code=%s' % ds)
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
params['cc_desc'] = cc_desc
|
||||
return render_template("about.html", project="op25", params=params, sysList=sysList())
|
||||
|
||||
# error page for database errors
|
||||
@app.route("/error")
|
||||
def error_page():
|
||||
params = request.args.to_dict()
|
||||
params['file'] = app.config['SQLALCHEMY_DATABASE_URI'][10:]
|
||||
return render_template("error.html", params=params, file=params['file'], code=int(params['code']))
|
||||
|
||||
# Inspect TSV (import) - returns a table of the tsv for display in a div, accessed by ajax
|
||||
@app.route("/inspect")
|
||||
def inspect():
|
||||
params = request.args.to_dict()
|
||||
f = os.getcwd() + '/../' + params['file']
|
||||
i = read_tsv(f)
|
||||
return render_template("inspect.html", i=i)
|
||||
|
||||
# edit and import tags
|
||||
@app.route("/edit_tags")
|
||||
def edit_tags():
|
||||
UnitIDTags = column_helper('unit_id_tags')
|
||||
TGIDTags = column_helper('tgid_tags')
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
if 'cmd' not in params.keys(): # render talkgroup by default
|
||||
params['cmd'] = 'tgid'
|
||||
cmd = params['cmd']
|
||||
session['cmd'] = cmd
|
||||
systems = db.session.query(SysIDTags.sysid, SysIDTags.tag)
|
||||
p = os.getcwd() + '/..'
|
||||
tsvs = []
|
||||
for root, dirs, files in os.walk(p, topdown=True):
|
||||
for file in files:
|
||||
if file.endswith(".tsv") and not file.startswith("._"):
|
||||
print(os.path.join(root, file))
|
||||
tsvs.append(os.path.join(root, file))
|
||||
tsvs.sort()
|
||||
return render_template("edit_tags.html", params=params, systems=systems, sysList=sysList(), p=p, cmd=cmd, tsvs=tsvs)
|
||||
|
||||
# data for tags table editor
|
||||
@app.route("/edittg")
|
||||
def edittg():
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
cmd = params['cmd']
|
||||
sysid = int(params['sysid'])
|
||||
UnitIDTags = column_helper('unit_id_tags')
|
||||
TGIDTags = column_helper('tgid_tags')
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
if cmd == 'tgid':
|
||||
tbl = TGIDTags
|
||||
if cmd == 'unit':
|
||||
tbl = UnitIDTags
|
||||
column_d = {
|
||||
'tgid': [
|
||||
ColumnDT(TGIDTags.id),
|
||||
ColumnDT(TGIDTags.sysid),
|
||||
ColumnDT(TGIDTags.rid),
|
||||
ColumnDT(TGIDTags.tag),
|
||||
ColumnDT(TGIDTags.id)
|
||||
],
|
||||
'unit': [
|
||||
ColumnDT(UnitIDTags.id),
|
||||
ColumnDT(UnitIDTags.sysid),
|
||||
ColumnDT(UnitIDTags.rid),
|
||||
ColumnDT(UnitIDTags.tag),
|
||||
ColumnDT(UnitIDTags.id)
|
||||
]
|
||||
}
|
||||
q = db.session.query(tbl.id, tbl.sysid, tbl.rid, tbl.tag).order_by(tbl.rid)
|
||||
if sysid != 0:
|
||||
q = q.filter(tbl.sysid == sysid)
|
||||
rowTable = DataTables(params, q, column_d[cmd])
|
||||
js = jsonify(rowTable.output_result())
|
||||
return js
|
||||
|
||||
#dtd = delete tag data
|
||||
@app.route("/dtd")
|
||||
def dtd():
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
cmd = params['cmd']
|
||||
UnitIDTags = column_helper('unit_id_tags')
|
||||
TGIDTags = column_helper('tgid_tags')
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
recId = params['id']
|
||||
if cmd == 'tgid':
|
||||
tbl = TGIDTags
|
||||
if cmd == 'unit':
|
||||
tbl = UnitIDTags
|
||||
delRec = delete(tbl.table_).where(tbl.id == recId)
|
||||
db.session.execute(delRec)
|
||||
db.session.commit()
|
||||
session['sm'] = 2
|
||||
return redirect('/edit_tags?cmd=' + cmd)
|
||||
|
||||
#utd = update tag data
|
||||
@app.route("/utd")
|
||||
def utd():
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
cmd = params['cmd']
|
||||
UnitIDTags = column_helper('unit_id_tags')
|
||||
TGIDTags = column_helper('tgid_tags')
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
recId = params['id']
|
||||
tag = params['tag']
|
||||
if cmd == 'tgid':
|
||||
tbl = TGIDTags
|
||||
if cmd == 'unit':
|
||||
tbl = UnitIDTags
|
||||
upRec = update(tbl.table_).where(tbl.id == recId).values(tag=tag)
|
||||
db.session.execute(upRec)
|
||||
db.session.commit()
|
||||
session['sm'] = 1
|
||||
return redirect('/edit_tags?cmd=' + cmd)
|
||||
|
||||
# import tags
|
||||
@app.route("/itt")
|
||||
def itt():
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
cmd = params['cmd']
|
||||
argv = [ None, 'import_' + cmd, os.getcwd() + '/../' + params['file'], params['sysid'] ]
|
||||
session['imp_results'] = import_tsv(argv)
|
||||
session['sm'] = 3
|
||||
return redirect('/edit_tags?cmd=' + cmd)
|
||||
|
||||
# delete all talkgroup/subscriber tags
|
||||
@app.route("/delTags")
|
||||
def delTags():
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
cmd = params['cmd']
|
||||
UnitIDTags = column_helper('unit_id_tags')
|
||||
TGIDTags = column_helper('tgid_tags')
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
sysid = params['sysid']
|
||||
if cmd == 'tgid':
|
||||
tbl = TGIDTags
|
||||
if cmd == 'unit':
|
||||
tbl = UnitIDTags
|
||||
delRec = delete(tbl.table_).where(tbl.sysid == sysid)
|
||||
db.session.execute(delRec)
|
||||
db.session.commit()
|
||||
db.session.execute("VACUUM") # sqlite3 clean up -- reduces file size
|
||||
session['sm'] = 4
|
||||
return redirect('/edit_tags?cmd=' + cmd)
|
||||
|
||||
# system tag editor functions (entirely separate from the tags editor above)
|
||||
@app.route("/editsys")
|
||||
def editsys():
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
params['cc_desc'] = cc_desc
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
systems = db.session.query(SysIDTags.sysid, SysIDTags.tag)
|
||||
return render_template("editsys.html", params=params, systems=systems, sysList=sysList())
|
||||
|
||||
#dsd = delete system data
|
||||
@app.route("/dsd")
|
||||
def dsd():
|
||||
params = request.args.to_dict()
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
recId = params['id']
|
||||
delRec = delete(SysIDTags.table_).where(SysIDTags.id == recId)
|
||||
db.session.execute(delRec)
|
||||
db.session.commit()
|
||||
return redirect('/editsys')
|
||||
|
||||
#usd = update system data
|
||||
@app.route("/usd")
|
||||
def usd():
|
||||
params = request.args.to_dict()
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
recId = params['id']
|
||||
tag = params['tag']
|
||||
upRec = update(SysIDTags.table_).where(SysIDTags.id == recId).values(tag=tag)
|
||||
db.session.execute(upRec)
|
||||
db.session.commit()
|
||||
return redirect('/editsys')
|
||||
|
||||
#esd = edit system data (system tags)
|
||||
@app.route("/esd")
|
||||
def esd():
|
||||
params = request.args.to_dict()
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
column_d = {
|
||||
's': [
|
||||
ColumnDT(SysIDTags.id),
|
||||
ColumnDT(SysIDTags.sysid),
|
||||
ColumnDT(SysIDTags.tag),
|
||||
ColumnDT(SysIDTags.id)
|
||||
]
|
||||
}
|
||||
q = db.session.query(SysIDTags.id, SysIDTags.sysid, SysIDTags.tag)
|
||||
rowTable = DataTables(params, q, column_d['s'])
|
||||
js = jsonify(rowTable.output_result())
|
||||
return js
|
||||
|
||||
#asd = add system data
|
||||
@app.route("/asd")
|
||||
def asd():
|
||||
params = request.args.to_dict()
|
||||
ns = params['id']
|
||||
nt = params['tag']
|
||||
#todo: validate input
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
insRec = insert(SysIDTags.table_).values(sysid=ns, tag=nt)
|
||||
db.session.execute(insRec)
|
||||
db.session.commit()
|
||||
return redirect('/editsys')
|
||||
|
||||
# purge database functions
|
||||
@app.route("/purge")
|
||||
def purge():
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
DataStore = column_helper('data_store')
|
||||
destfile = ''
|
||||
b = False
|
||||
if 'bu' in params.keys():
|
||||
if params['bu'] == 'true':
|
||||
b = True
|
||||
t = strftime("%Y%m%d_%H%M%S")
|
||||
destfile = 'op25-backup-%s.db' % t
|
||||
src = app.config['SQLALCHEMY_DATABASE_URI'][10:]
|
||||
s = src.split('/')
|
||||
f = s[-1]
|
||||
dst = src.replace(f, destfile)
|
||||
if 'simulate' in params.keys():
|
||||
simulate = params['simulate']
|
||||
if 'action' in params.keys():
|
||||
if params['action'] == 'purge':
|
||||
sd = params['sd']
|
||||
ed = params['ed']
|
||||
sysid = int(params['sysid'])
|
||||
delRec = delete(DataStore.table_).where(DataStore.time >= int(sd), DataStore.time <= int(ed))
|
||||
recCount = db.session.query(DataStore.id).filter(and_(DataStore.time >= int(sd), DataStore.time <= int(ed)))
|
||||
if sysid != 0:
|
||||
recCount = recCount.filter(DataStore.sysid == sysid)
|
||||
delRec = delRec.where(DataStore.sysid == sysid)
|
||||
if 'kv' in params.keys(): # keep voice calls
|
||||
if params['kv'] == 'true':
|
||||
recCount = recCount.where(and_(DataStore.opcode != 0, DataStore.opcode != 2))
|
||||
delRec = delRec.where(and_(DataStore.opcode != 0, DataStore.opcode != 2))
|
||||
recCount = recCount.count()
|
||||
dispQuery = delRec.compile(compile_kwargs={"literal_binds": True})
|
||||
if simulate == 'false':
|
||||
if b == True:
|
||||
copyfile(src, dst)
|
||||
db.session.execute(delRec)
|
||||
db.session.commit()
|
||||
db.session.execute("VACUUM") # sqlite3 clean up -- reduces file size
|
||||
successMessage = 1
|
||||
else:
|
||||
successMessage = 2
|
||||
else:
|
||||
recCount = 0
|
||||
successMessage = 0
|
||||
dispQuery = ''
|
||||
|
||||
return render_template("purge.html", \
|
||||
project="op25", \
|
||||
params=params, \
|
||||
dbstats=dbStats(), \
|
||||
sysList=sysList(), \
|
||||
successMessage=successMessage, \
|
||||
recCount=recCount, \
|
||||
dispQuery=dispQuery, \
|
||||
destfile=destfile )
|
||||
|
||||
# displays all logs w/ datatables
|
||||
@app.route("/logs")
|
||||
def logs():
|
||||
UnitIDTags = column_helper('unit_id_tags')
|
||||
TGIDTags = column_helper('tgid_tags')
|
||||
tag = ''
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = oplog_map.keys()
|
||||
params['cc_desc'] = cc_desc
|
||||
t = None if 'q' not in params.keys() else params['q']
|
||||
sysid = 0 if 'sysid' not in params.keys() else int(params['sysid'])
|
||||
if sysid != 0:
|
||||
if t is not None and params['r'] == 'tgid':
|
||||
q = db.session.query(TGIDTags.tag).where(and_(TGIDTags.rid == t, TGIDTags.sysid == sysid))
|
||||
if t is not None and params['r'] == 'su':
|
||||
q = db.session.query(UnitIDTags.tag).where(and_(UnitIDTags.rid == t, UnitIDTags.sysid == sysid))
|
||||
if q.count() > 0:
|
||||
tg = (db.session.execute(q).one())
|
||||
tag = (' - %s' % tg.tag)
|
||||
if params['r'] == 'cc_event':
|
||||
mapl = oplog_map[params['p'].strip()]
|
||||
params['ckeys'] = [s[1] for s in mapl if s[0] != 'opcode' and s[0] != 'cc_event']
|
||||
|
||||
return render_template("logs.html", \
|
||||
project="logs", \
|
||||
params=params, \
|
||||
sysList=sysList(), \
|
||||
tag=tag )
|
||||
|
||||
# data for /logs
|
||||
@app.route("/data")
|
||||
def data():
|
||||
"""Return server side data."""
|
||||
# GET parameters
|
||||
params = request.args.to_dict()
|
||||
|
||||
host_rid = None if 'host_rid' not in params.keys() else params['host_rid']
|
||||
host_function_type = None if 'host_function_type' not in params.keys() else params['host_function_type']
|
||||
host_function_param = None if 'host_function_param' not in params.keys() else params['host_function_param'].strip()
|
||||
|
||||
filter_tgid = None if 'tgid' not in params.keys() else int(params['tgid'].strip())
|
||||
filter_suid = None if 'suid' not in params.keys() else int(params['suid'].strip())
|
||||
|
||||
start_time = None if 'sdate' not in params.keys() else datetime.datetime.utcfromtimestamp(float(params['sdate']))
|
||||
end_time = None if 'edate' not in params.keys() else datetime.datetime.utcfromtimestamp(float(params['edate']))
|
||||
print(params)
|
||||
sysid = None if 'sysid' not in params.keys() else int(params['sysid'])
|
||||
|
||||
stime = int(params['sdate']) #used in the queries
|
||||
etime = int(params['edate']) #used in the queries
|
||||
|
||||
DataStore = column_helper('data_store')
|
||||
EventKeys = column_helper('event_keys')
|
||||
SysIDTags = column_helper('sysid_tags')
|
||||
UnitIDTags = column_helper('unit_id_tags')
|
||||
TGIDTags = column_helper('tgid_tags')
|
||||
LocRegResp = column_helper('loc_reg_resp_rv')
|
||||
|
||||
DataStore.time.type = MyDateType()
|
||||
|
||||
k = 'logs'
|
||||
if host_function_type:
|
||||
k = '%s_%s' % (k, host_function_type)
|
||||
|
||||
column_d = {
|
||||
'logs_su': [
|
||||
ColumnDT(TGIDTags.tag),
|
||||
ColumnDT(DataStore.tgid),
|
||||
ColumnDT(DataStore.tgid),
|
||||
],
|
||||
'logs_tgid': [
|
||||
ColumnDT(DataStore.suid),
|
||||
ColumnDT(UnitIDTags.tag),
|
||||
ColumnDT(DataStore.suid),
|
||||
ColumnDT(DataStore.time)
|
||||
],
|
||||
|
||||
'logs_calls': [
|
||||
ColumnDT(DataStore.time),
|
||||
ColumnDT(SysIDTags.tag),
|
||||
ColumnDT(DataStore.tgid),
|
||||
ColumnDT(TGIDTags.tag),
|
||||
ColumnDT(DataStore.frequency),
|
||||
ColumnDT(DataStore.suid)
|
||||
],
|
||||
'logs_joins': [
|
||||
ColumnDT(DataStore.time),
|
||||
ColumnDT(DataStore.opcode),
|
||||
ColumnDT(DataStore.sysid),
|
||||
ColumnDT(SysIDTags.tag),
|
||||
ColumnDT(LocRegResp.tag),
|
||||
ColumnDT(DataStore.tgid),
|
||||
ColumnDT(TGIDTags.tag),
|
||||
ColumnDT(DataStore.suid),
|
||||
ColumnDT(UnitIDTags.tag)
|
||||
],
|
||||
'logs_total_tgid': [
|
||||
ColumnDT(DataStore.sysid),
|
||||
ColumnDT(SysIDTags.tag),
|
||||
ColumnDT(DataStore.tgid),
|
||||
ColumnDT(TGIDTags.tag),
|
||||
ColumnDT(DataStore.tgid)
|
||||
],
|
||||
'logs_call_detail': [
|
||||
ColumnDT(DataStore.time),
|
||||
ColumnDT(DataStore.opcode),
|
||||
ColumnDT(SysIDTags.sysid),
|
||||
ColumnDT(SysIDTags.tag),
|
||||
ColumnDT(DataStore.tgid),
|
||||
ColumnDT(TGIDTags.tag),
|
||||
ColumnDT(DataStore.suid),
|
||||
ColumnDT(UnitIDTags.tag),
|
||||
ColumnDT(DataStore.frequency)
|
||||
]
|
||||
}
|
||||
|
||||
"""or_( EventKeys.tag == 'grp_v_ch_grant', EventKeys.tag == 'grp_v_ch_grant_exp'),"""
|
||||
|
||||
query_d = {
|
||||
'logs_total_tgid': db.session.query(DataStore.sysid, \
|
||||
SysIDTags.tag, \
|
||||
DataStore.tgid, \
|
||||
TGIDTags.tag, \
|
||||
func.count(DataStore.tgid).label('count'))
|
||||
.group_by(DataStore.tgid)
|
||||
.outerjoin(SysIDTags.table_, DataStore.sysid == SysIDTags.sysid)
|
||||
.outerjoin(TGIDTags.table_, DataStore.tgid == TGIDTags.rid)
|
||||
.filter(and_(DataStore.tgid != 0), (DataStore.frequency != None) ),
|
||||
|
||||
'logs_call_detail': db.session.query(DataStore.time, \
|
||||
DataStore.opcode, \
|
||||
DataStore.sysid, \
|
||||
SysIDTags.tag, \
|
||||
DataStore.tgid, \
|
||||
TGIDTags.tag, \
|
||||
DataStore.suid, \
|
||||
UnitIDTags.tag, \
|
||||
DataStore.frequency )
|
||||
.outerjoin(SysIDTags.table_, DataStore.sysid == SysIDTags.sysid)
|
||||
.outerjoin(TGIDTags.table_, and_(DataStore.tgid == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid))
|
||||
.outerjoin(UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid))
|
||||
.filter(and_(DataStore.tgid != 0), (DataStore.frequency != None) )
|
||||
.filter(or_(DataStore.opcode == 0, and_(DataStore.opcode == 2, DataStore.mfrid == 144)) ),
|
||||
|
||||
|
||||
'logs_tgid': db.session.query(DataStore.suid, \
|
||||
UnitIDTags.tag, \
|
||||
func.count(DataStore.suid).label('count'), func.max(DataStore.time).label('last') )
|
||||
.outerjoin(UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid)),
|
||||
|
||||
'logs_su': db.session.query(TGIDTags.tag, \
|
||||
DataStore.tgid, \
|
||||
func.count(DataStore.tgid).label('count') )
|
||||
.outerjoin(TGIDTags.table_, DataStore.tgid == TGIDTags.rid),
|
||||
|
||||
'logs_calls': db.session.query(DataStore.time, \
|
||||
SysIDTags.tag, \
|
||||
DataStore.tgid, \
|
||||
TGIDTags.tag, \
|
||||
DataStore.frequency, \
|
||||
DataStore.suid )
|
||||
.join(EventKeys.table_, and_(or_( EventKeys.tag == 'grp_v_ch_grant', EventKeys.tag == 'grp_v_ch_grant_mbt'),EventKeys.id == DataStore.cc_event))
|
||||
.outerjoin(TGIDTags.table_, and_(TGIDTags.rid == DataStore.tgid, TGIDTags.sysid == DataStore.sysid))
|
||||
.outerjoin(SysIDTags.table_, DataStore.sysid == SysIDTags.sysid),
|
||||
|
||||
'logs_joins': db.session.query(DataStore.time, \
|
||||
DataStore.opcode, \
|
||||
DataStore.sysid, \
|
||||
SysIDTags.tag, \
|
||||
LocRegResp.tag, \
|
||||
DataStore.tgid, \
|
||||
TGIDTags.tag, \
|
||||
DataStore.suid, \
|
||||
UnitIDTags.tag )
|
||||
.join(LocRegResp.table_, DataStore.p == LocRegResp.rv)
|
||||
.outerjoin(SysIDTags.table_, DataStore.sysid == SysIDTags.sysid)
|
||||
.outerjoin(TGIDTags.table_, and_(DataStore.tgid == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid))
|
||||
.outerjoin(UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid))
|
||||
.filter(or_(DataStore.opcode == 40, DataStore.opcode == 43)) # joins
|
||||
} # end query_d
|
||||
|
||||
if host_function_type != 'cc_event':
|
||||
q = query_d[k]
|
||||
|
||||
if host_function_type in 'su tgid'.split():
|
||||
filter_col = {'su': DataStore.suid, 'tgid': DataStore.tgid}
|
||||
group_col = {'su': DataStore.tgid, 'tgid': DataStore.suid}
|
||||
if '?' in host_rid:
|
||||
id_start = int(host_rid.replace('?', '0'))
|
||||
id_end = int(host_rid.replace('?', '9'))
|
||||
q = q.filter(filter_col[host_function_type] >= id_start, filter_col[host_function_type] <= id_end)
|
||||
elif '-' in host_rid:
|
||||
id_start, id_end = host_rid.split('-')
|
||||
id_start = int(id_start)
|
||||
id_end = int(id_end)
|
||||
q = q.filter(filter_col[host_function_type] >= id_start, filter_col[host_function_type] <= id_end)
|
||||
else:
|
||||
q = q.filter(filter_col[host_function_type] == int(host_rid))
|
||||
q = q.group_by(group_col[host_function_type])
|
||||
q = q.filter(DataStore.suid != None)
|
||||
|
||||
dt_cols = {
|
||||
'logs_tgid' : [ DataStore.suid, UnitIDTags.tag, 'count' ],
|
||||
'logs_su' : [ TGIDTags.tag, DataStore.tgid, 'count' ],
|
||||
'logs_calls' : [ DataStore.time, SysIDTags.tag, DataStore.tgid, TGIDTags.tag, DataStore.frequency, DataStore.suid ],
|
||||
'logs_joins' : [ DataStore.time, SysIDTags.tag, LocRegResp.tag, TGIDTags.tag, DataStore.suid ],
|
||||
'logs_total_tgid' : [ DataStore.sysid, SysIDTags.tag, DataStore.tgid, TGIDTags.tag, 'count' ]
|
||||
}
|
||||
|
||||
if host_function_type == 'cc_event':
|
||||
mapl = oplog_map[host_function_param]
|
||||
columns = []
|
||||
for row in mapl:
|
||||
col = getattr(DataStore, row[0])
|
||||
if row[0] == 'sysid':
|
||||
col = SysIDTags.tag
|
||||
elif row[1] == 'Talkgroup':
|
||||
col = TGIDTags.tag
|
||||
elif row[1] == 'Source' or row[1] == 'Target':
|
||||
col = UnitIDTags.tag
|
||||
elif row[0] == 'cc_event':
|
||||
continue
|
||||
#col = EventKeys.tag
|
||||
elif row[0] == 'opcode':
|
||||
continue
|
||||
elif host_function_param == 'loc_reg_resp' and row[0] == 'p':
|
||||
col = LocRegResp.tag
|
||||
columns.append(col)
|
||||
|
||||
column_dt = [ColumnDT(s) for s in columns]
|
||||
|
||||
q = db.session.query(*columns
|
||||
).join(
|
||||
EventKeys.table_, and_( EventKeys.tag == host_function_param, EventKeys.id == DataStore.cc_event)
|
||||
).outerjoin(
|
||||
SysIDTags.table_, DataStore.sysid == SysIDTags.sysid
|
||||
)
|
||||
if host_function_param == 'grp_aff_resp':
|
||||
q = q.outerjoin(
|
||||
TGIDTags.table_, and_(DataStore.tgid2 == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid)
|
||||
).outerjoin(
|
||||
UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid)
|
||||
)
|
||||
|
||||
elif host_function_param == 'ack_resp_fne' or host_function_param == 'grp_aff_q' or host_function_param == 'u_reg_cmd':
|
||||
q = q.outerjoin(
|
||||
TGIDTags.table_, and_(DataStore.tgid2 == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid)
|
||||
).outerjoin(
|
||||
UnitIDTags.table_, and_(DataStore.suid2 == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid)
|
||||
)
|
||||
else:
|
||||
q = q.outerjoin(
|
||||
TGIDTags.table_, and_(DataStore.tgid == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid)
|
||||
).outerjoin(
|
||||
UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid)
|
||||
)
|
||||
|
||||
if host_function_param == 'loc_reg_resp':
|
||||
q = q.join(LocRegResp.table_, LocRegResp.rv == DataStore.p)
|
||||
|
||||
if host_function_type == 'cc_event':
|
||||
cl = columns
|
||||
elif k in dt_cols:
|
||||
cl = dt_cols[k]
|
||||
else:
|
||||
cl = None
|
||||
|
||||
# apply tgid and suid filters if present
|
||||
if host_function_type == 'cc_event':
|
||||
if filter_tgid is not None and int(filter_tgid) != 0:
|
||||
q = q.filter(DataStore.tgid == filter_tgid)
|
||||
if filter_suid is not None and int(filter_suid) != 0:
|
||||
q = q.filter(DataStore.suid == filter_suid)
|
||||
|
||||
if cl:
|
||||
c = int(params['order[0][column]'])
|
||||
d = params['order[0][dir]'] # asc or desc
|
||||
if d == 'asc':
|
||||
q = q.order_by(cl[c])
|
||||
else:
|
||||
q = q.order_by(desc(cl[c]))
|
||||
|
||||
q = q.filter(and_(DataStore.time >= int(stime), DataStore.time <= int(etime)))
|
||||
|
||||
if sysid != 0:
|
||||
q = q.filter(DataStore.sysid == sysid)
|
||||
|
||||
if host_function_type == 'cc_event':
|
||||
rowTable = DataTables(params, q, column_dt)
|
||||
else:
|
||||
rowTable = DataTables(params, q, column_d[k])
|
||||
|
||||
js = jsonify(rowTable.output_result())
|
||||
# j= 'skipped' # json.dumps(rowTable.output_result(), indent=4, separators=[',', ':'], sort_keys=True)
|
||||
# with open('data-log', 'a') as logf:
|
||||
# s = '\n\t'.join(['%s:%s' % (k, params[k]) for k in params.keys()])
|
||||
# logf.write('keys: %s\n' % (' '.join(params.keys())))
|
||||
# logf.write('params:\n\t%s\nrequest: %s\n' % (s, function_req))
|
||||
# logf.write('%s\n' % j)
|
||||
return js
|
||||
|
||||
# switch and backup database file
|
||||
@app.route("/switch_db")
|
||||
def switch_db():
|
||||
params = request.args.to_dict()
|
||||
params['ekeys'] = sorted(oplog_map.keys())
|
||||
p = os.getcwd() + '/..'
|
||||
files = [f for f in listdir(p) if isfile(join(p, f))]
|
||||
files.sort()
|
||||
if 'cmd' not in params.keys():
|
||||
curr_file = app.config['SQLALCHEMY_DATABASE_URI'].split('/')[-1]
|
||||
return render_template("switch_db.html", params=params, files=files, curr_file=curr_file)
|
||||
if params['cmd'] == 'backup':
|
||||
t = strftime("%Y-%m-%d_%H%M%S")
|
||||
destfile = 'op25-backup-%s.db' % t
|
||||
src = app.config['SQLALCHEMY_DATABASE_URI'][10:]
|
||||
s = src.split('/')
|
||||
curr_file = s[-1]
|
||||
dst = src.replace(curr_file, destfile)
|
||||
copyfile(src, dst)
|
||||
return render_template("switch_db.html", params=params, destfile=destfile, curr_file=curr_file, files=files, sm=1)
|
||||
if params['cmd'] == 'switch':
|
||||
new_f = params['file']
|
||||
database = app.config['SQLALCHEMY_DATABASE_URI']
|
||||
f = database.split('/')[-1]
|
||||
new_db = database.replace(f, new_f)
|
||||
print('switching database to: %s' % new_db)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = new_db
|
||||
return redirect('/')
|
11038
op25/gr-op25_repeater/apps/oplog/op25/static/css/bootstrap/bootstrap-darkly.css
vendored
Normal file
11038
op25/gr-op25_repeater/apps/oplog/op25/static/css/bootstrap/bootstrap-darkly.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue