From 87630dbf87315528bd49c80c69fc1f0dfa2d8a73 Mon Sep 17 00:00:00 2001 From: cemaxecuter Date: Sun, 25 Jan 2026 19:52:04 -0500 Subject: [PATCH 1/2] add DragonOS Noble support and Quick Start guide - install_telive.sh: detect DragonOS via /etc/os-dragonos, skip pre-installed packages (gnuradio, libosmocore), skip DVB module handling, install .desktop to /usr/share/applications - README.md: add Quick Start guide with architecture overview, setup instructions, keyboard commands, and troubleshooting --- README.md | 141 +++++++++++++++++++++++++++++++++++--- scripts/install_telive.sh | 101 +++++++++++++++++++++++---- 2 files changed, 217 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b09f34a..9b9f6e1 100644 --- a/README.md +++ b/README.md @@ -10,26 +10,147 @@ is done via external scripts. Note: this is a test version that will be used with osmo-tetra-sq5bpf-2: https://github.com/sq5bpf/osmo-tetra-sq5bpf-2 -For now this is for experimenting. +For comprehensive documentation, please read telive_doc.pdf: +https://github.com/sq5bpf/telive/raw/master/telive_doc.pdf -Please read telive_doc.pdf, either in this directory or here: -https://github.com/sq5bpf/telive/raw/master/telive_doc.pdf +## Quick Start Guide +### Architecture Overview ------------ Disclaimer ---------- +``` +┌─────────────┐ UDP ┌─────────────────┐ ┌─────────────┐ UDP ┌────────┐ +│ GnuRadio │ :42001 │ receiver1udp │ │ tetra-rx │ :7379 │ telive │ +│ (RTL-SDR) │ ─────────> │ + simdemod3 │ ───────> │ (decoder) │ ─────────> │ (UI) │ +│ RF Input │ IQ data │ (demodulator) │ bits │ │ decoded │ │ +└─────────────┘ └─────────────────┘ └─────────────┘ └────────┘ +``` -The program is licenced under GPL v3 (license text is also included in the -file LICENSE). I may not be held responsible for anything associated with -the use of this tool. +**What each component does:** +- **GnuRadio**: Receives RF signal from RTL-SDR dongle, outputs IQ samples via UDP +- **receiver1udp**: Wrapper script that chains together: + - `socat`: Receives UDP data from GnuRadio + - `simdemod3_telive.py`: CQPSK demodulator (converts IQ to bits) + - `tetra-rx`: TETRA protocol decoder (extracts signaling, voice, SDS) +- **telive**: ncurses UI that displays decoded information and plays audio + +### Prerequisites + +- RTL-SDR dongle (or other GnuRadio-supported SDR) +- GnuRadio 3.8+ with gr-osmosdr +- libosmocore and libosmocore-dev +- TETRA codecs in `/tetra/bin/` (installed via install-tetra-codec) +- osmo-tetra-sq5bpf-2 compiled + +### Installation + +Use the install script for supported distributions: +```bash +./scripts/install_telive.sh +``` + +Or install manually - see the FAQ section in telive_doc.pdf. + +### Running telive (Simple 1-Channel Setup) + +You need **3 terminals** (or use the xterm from the applications menu): + +**Terminal 1 - Start the demodulator/decoder:** +```bash +cd ~/tetra/osmo-tetra-sq5bpf-2/src +./receiver1udp 1 +``` +This listens on UDP port 42001 for IQ data from GnuRadio and sends decoded data to telive on port 7379. + +**Terminal 2 - Start telive (requires 203x60 terminal):** +```bash +xterm -geometry 203x60 & +# In the new xterm: +cd ~/tetra/telive-2 +./rxx +``` +Or use the "Telive xterm 203x60" entry from your applications menu. + +**Terminal 3 - Start the GnuRadio receiver:** + +With GUI (recommended for tuning): +```bash +cd ~/tetra/telive-2/gnuradio-companion/python3_based_gnuradio +python3 telive_1ch_simple_gr310_udp_xmlrpc.py +``` + +Or headless (lower CPU usage): +```bash +python3 telive_1ch_gr310_udp_xmlrpc_headless.py +``` +**Tune to a TETRA frequency** in the GnuRadio GUI: +- Set baseband frequency (e.g., `435M` for 435 MHz) +- Adjust PPM for your dongle's frequency correction +- The spectrum should show the TETRA signal + +### Multi-Channel Setup + +For 6 channels (to capture all traffic in a Location Area): + +```bash +# Start 6 demodulators +cd ~/tetra/osmo-tetra-sq5bpf-2/src +for i in $(seq 1 6); do xterm -T "RX$i" -e ./receiver1udp $i & done + +# Start telive +cd ~/tetra/telive-2 +./rxx + +# Start 6-channel GnuRadio receiver +cd ~/tetra/telive-2/gnuradio-companion/python3_based_gnuradio +python3 telive_6ch_gr310_udp_xmlrpc_headless.py +``` + +### Telive Keyboard Commands + +| Key | Function | +|-----|----------| +| `?` | Show help | +| `R` | Toggle recording | +| `L` | Toggle logging | +| `M` | Toggle mute | +| `m` | Toggle mutessi (filter traffic without SSI - hides encrypted) | +| `a` | Toggle alldump (show all signaling) | +| `s` | Stop current playback | +| `t` | Toggle between main window and frequency window | +| `V/v` | Increase/decrease verbosity | +| `f` | Toggle SSI filter | + +### Output Files + +- **Recordings**: `/tetra/in/` (raw ACELP), `/tetra/out/` (converted to OGG by tetrad) +- **Log file**: `telive.log` (signaling, SDS messages) +- **KML file**: Location information for Google Earth (if configured) + +### Troubleshooting + +- **No audio**: Check that codecs exist in `/tetra/bin/` and test with: + ```bash + /tetra/bin/tplay < testfile.acelp + ``` +- **Garbled display**: Ensure terminal is exactly 203x60 characters +- **No data in telive**: Verify receiver1udp is receiving data (should show scrolling text) +- **receiver1udp errors**: Check GnuRadio is running and sending to correct UDP port + + +## Disclaimer + +The program is licenced under GPL v3 (license text is also included in the +file LICENSE). I may not be held responsible for anything associated with +the use of this tool. This was a quick hack written in my spare time, and for my own pleasure, -and because of this the code is really ugly. The code is also based on wrong -assumptions - using the usage identifier as the key is not a good way of +and because of this the code is really ugly. The code is also based on wrong +assumptions - using the usage identifier as the key is not a good way of following calls (but it works most of the time). Maybe one day this will be rewritten to look better (or not). ------------------------------------ +--- diff --git a/scripts/install_telive.sh b/scripts/install_telive.sh index d583980..15a21b7 100755 --- a/scripts/install_telive.sh +++ b/scripts/install_telive.sh @@ -17,6 +17,7 @@ # Everything is the responsibility of the user. # # Changelog: +# 20260125: add support for DragonOS Noble (Ubuntu 24.04 based) # 20260119: add support for debian 12 --sq5bpf # 20181211: add support for debian 10 --sq5bpf # 20170709: add support for linux mint 18.2 and debian 9, both are totally untested --sq5bpf @@ -40,7 +41,43 @@ get_osr() { ( . /etc/os-release ; eval "echo \$$1" ) } +# Check if running on DragonOS +is_dragonos() { + if [ -f /etc/os-dragonos ]; then + return 0 + fi + return 1 +} + +get_dragonos_version() { + if [ -f /etc/os-dragonos ]; then + cat /etc/os-dragonos + fi +} + do_distro_specific_stuff() { + # Check for DragonOS first + if is_dragonos; then + DRAGONOS_VER=$(get_dragonos_version) + echo "Detected DragonOS: $DRAGONOS_VER" + case "$DRAGONOS_VER" in + *"Noble"*) + DISTRO_NAME="dragonos" + DISTRO_VERSION="noble" + IS_DRAGONOS=1 + ;; + *) + echo "Unknown DragonOS version: $DRAGONOS_VER" + echo "Will attempt to continue with generic DragonOS support" + DISTRO_NAME="dragonos" + DISTRO_VERSION="unknown" + IS_DRAGONOS=1 + ;; + esac + DISTRO="$DISTRO_NAME $DISTRO_VERSION" + return 0 + fi + if [ ! -f /etc/os-release ]; then echo "There is no /etc/os-release file, so this is an unknown distribution, and this script will not run" exit 1 @@ -109,6 +146,15 @@ verify_prerequisites() { install_gnuradio() { + # DragonOS already has gnuradio and related packages installed + if [ "$IS_DRAGONOS" = "1" ]; then + GR_VERSION=`gnuradio-config-info -v 2>/dev/null|tr -d v` + echo "DragonOS detected - gnuradio $GR_VERSION and SDR packages are pre-installed" + # Just ensure gqrx-sdr is available (pulls from DragonOS PPA if needed) + sudo apt-get -y install gqrx-sdr 2>/dev/null || true + return 0 + fi + GR_VERSION=`gnuradio-config-info -v 2>/dev/null|tr -d v` case "$GR_VERSION" in @@ -264,7 +310,26 @@ install_libosmocore () { } make_desktop_icons() { - cat > ~/Desktop/xterm_telive.desktop < /dev/null < ~/Desktop/xterm_telive.desktop </dev/null 2>&1 -done +# Skip udev restart and DVB module handling on DragonOS (not needed) +if [ "$IS_DRAGONOS" != "1" ]; then + #rtl-sdr stuff installs new udev rules, so restart just in case + sudo service udev restart + + #unload these modules just in case, installing librtlsdr blacklists them + #anyway, but they may be loaded now, and this might confuse the user + for i in dvb_usb_rtl28xxu e4000 rtl2832 + do + sudo rmmod $i >/dev/null 2>&1 + done +fi install_packages || exit 1 @@ -327,11 +394,15 @@ echo; echo echo "It seems that everything installed correctly :)" echo "All of the files are in `pwd`" echo -echo "PLEASE, before proceeding read the manual in `pwd`/telive/telive_doc.pdf" +echo "PLEASE, before proceeding read the manual in `pwd`/telive-2/telive_doc.pdf" #copy the manual, maybe some user will notice it is there and actually read it? -if [ -d ~/Desktop ]; then - cp "`pwd`/telive/telive_doc.pdf" ~/Desktop +if [ "$IS_DRAGONOS" = "1" ]; then + # For DragonOS, install desktop entry to /usr/share/applications + make_desktop_icons +elif [ -d ~/Desktop ]; then + cp "`pwd`/telive-2/telive_doc.pdf" ~/Desktop 2>/dev/null || \ + cp "`pwd`/telive/telive_doc.pdf" ~/Desktop 2>/dev/null || true echo "or the telive_doc.pdf file on the desktop" make_desktop_icons fi From 003af196c822a21c332fa43fba23ecd5c30b1458 Mon Sep 17 00:00:00 2001 From: cemaxecuter Date: Sun, 25 Jan 2026 20:48:47 -0500 Subject: [PATCH 2/2] fix duplicate fine tune slider in 1ch simple GRC Remove duplicate xlate_offset_fine1 variable_slider (WX widget) that was overriding the working variable_qtgui_range (Qt5). The WX widget doesn't exist in GnuRadio 3.8+ causing the flow graph to fail to load. --- .../telive_1ch_simple_gr310_udp_xmlrpc.grc | 24 +------ .../telive_1ch_simple_gr310_udp_xmlrpc.py | 67 +++++++------------ 2 files changed, 27 insertions(+), 64 deletions(-) diff --git a/gnuradio-companion/python3_based_gnuradio/telive_1ch_simple_gr310_udp_xmlrpc.grc b/gnuradio-companion/python3_based_gnuradio/telive_1ch_simple_gr310_udp_xmlrpc.grc index cf28390..7539a03 100644 --- a/gnuradio-companion/python3_based_gnuradio/telive_1ch_simple_gr310_udp_xmlrpc.grc +++ b/gnuradio-companion/python3_based_gnuradio/telive_1ch_simple_gr310_udp_xmlrpc.grc @@ -62,6 +62,7 @@ blocks: id: variable_qtgui_entry parameters: comment: '' + entry_signal: editingFinished gui_hint: 0,0,1,2 label: Frequency type: real @@ -917,27 +918,6 @@ blocks: coordinate: [832, 156.0] rotation: 0 state: true -- name: xlate_offset_fine1 - id: variable_slider - parameters: - alias: '' - comment: '' - converver: float_converter - grid_pos: 3,1,1,4 - label: Fine Tune - max: '10000' - min: '-10000' - notebook: '' - num_steps: '200' - style: wx.SL_HORIZONTAL - value: '0' - states: - bus_sink: false - bus_source: false - bus_structure: null - coordinate: [144, 770] - rotation: 0 - state: enabled - name: xmlrpc_server_0 id: xmlrpc_server parameters: @@ -965,4 +945,4 @@ connections: metadata: file_format: 1 - grc_version: 3.10.5.1 + grc_version: 3.10.12.0 diff --git a/gnuradio-companion/python3_based_gnuradio/telive_1ch_simple_gr310_udp_xmlrpc.py b/gnuradio-companion/python3_based_gnuradio/telive_1ch_simple_gr310_udp_xmlrpc.py index 52a7f2a..ded5326 100755 --- a/gnuradio-companion/python3_based_gnuradio/telive_1ch_simple_gr310_udp_xmlrpc.py +++ b/gnuradio-companion/python3_based_gnuradio/telive_1ch_simple_gr310_udp_xmlrpc.py @@ -7,47 +7,33 @@ # GNU Radio Python Flow Graph # Title: SQ5BPF Tetra live receiver 1ch simple UDP demo with fixed offset (gnuradio 3.10 version) xmlrpc # Author: Jacek Lipkowski SQ5BPF -# GNU Radio version: 3.10.5.1 - -from packaging.version import Version as StrictVersion - -if __name__ == '__main__': - import ctypes - import sys - if sys.platform.startswith('linux'): - try: - x11 = ctypes.cdll.LoadLibrary('libX11.so') - x11.XInitThreads() - except: - print("Warning: failed to XInitThreads()") +# GNU Radio version: 3.10.12.0 from PyQt5 import Qt -from gnuradio import eng_notation from gnuradio import qtgui -from gnuradio.filter import firdes -import sip +from PyQt5 import QtCore from gnuradio import analog from gnuradio import blocks import pmt +from gnuradio import eng_notation from gnuradio import filter +from gnuradio.filter import firdes from gnuradio import gr from gnuradio.fft import window import sys import signal +from PyQt5 import Qt from argparse import ArgumentParser from gnuradio.eng_arg import eng_float, intx from gnuradio import network -from gnuradio.qtgui import Range, RangeWidget -from PyQt5 import QtCore from xmlrpc.server import SimpleXMLRPCServer import threading import osmosdr import time +import sip -from gnuradio import qtgui - class telive_1ch_simple_gr310_udp_xmlrpc(gr.top_block, Qt.QWidget): def __init__(self): @@ -57,8 +43,8 @@ def __init__(self): qtgui.util.check_set_qss() try: self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc')) - except: - pass + except BaseException as exc: + print(f"Qt GUI: Could not set Icon: {str(exc)}", file=sys.stderr) self.top_scroll_layout = Qt.QVBoxLayout() self.setLayout(self.top_scroll_layout) self.top_scroll = Qt.QScrollArea() @@ -71,15 +57,15 @@ def __init__(self): self.top_grid_layout = Qt.QGridLayout() self.top_layout.addLayout(self.top_grid_layout) - self.settings = Qt.QSettings("GNU Radio", "telive_1ch_simple_gr310_udp_xmlrpc") + self.settings = Qt.QSettings("gnuradio/flowgraphs", "telive_1ch_simple_gr310_udp_xmlrpc") try: - if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"): - self.restoreGeometry(self.settings.value("geometry").toByteArray()) - else: - self.restoreGeometry(self.settings.value("geometry")) - except: - pass + geometry = self.settings.value("geometry") + if geometry: + self.restoreGeometry(geometry) + except BaseException as exc: + print(f"Qt GUI: Could not restore geometry: {str(exc)}", file=sys.stderr) + self.flowgraph_started = threading.Event() ################################################## # Variables @@ -107,22 +93,22 @@ def __init__(self): # Blocks ################################################## - self._xlate_offset_fine1_range = Range(-5e3, +5e3, 1, 0, 200) - self._xlate_offset_fine1_win = RangeWidget(self._xlate_offset_fine1_range, self.set_xlate_offset_fine1, "Fine tune1", "counter_slider", float, QtCore.Qt.Horizontal) + self._xlate_offset_fine1_range = qtgui.Range(-5e3, +5e3, 1, 0, 200) + self._xlate_offset_fine1_win = qtgui.RangeWidget(self._xlate_offset_fine1_range, self.set_xlate_offset_fine1, "Fine tune1", "counter_slider", float, QtCore.Qt.Horizontal) self.top_grid_layout.addWidget(self._xlate_offset_fine1_win, 0, 2, 1, 3) for r in range(0, 1): self.top_grid_layout.setRowStretch(r, 1) for c in range(2, 5): self.top_grid_layout.setColumnStretch(c, 1) - self._sdr_gain_range = Range(0, 50, 1, 30, 200) - self._sdr_gain_win = RangeWidget(self._sdr_gain_range, self.set_sdr_gain, "gain", "counter_slider", int, QtCore.Qt.Horizontal) + self._sdr_gain_range = qtgui.Range(0, 50, 1, 30, 200) + self._sdr_gain_win = qtgui.RangeWidget(self._sdr_gain_range, self.set_sdr_gain, "gain", "counter_slider", int, QtCore.Qt.Horizontal) self.top_grid_layout.addWidget(self._sdr_gain_win, 0, 8, 1, 1) for r in range(0, 1): self.top_grid_layout.setRowStretch(r, 1) for c in range(8, 9): self.top_grid_layout.setColumnStretch(c, 1) - self._ppm_corr_range = Range(-100, 100, 0.5, 0, 200) - self._ppm_corr_win = RangeWidget(self._ppm_corr_range, self.set_ppm_corr, "ppm", "counter_slider", float, QtCore.Qt.Horizontal) + self._ppm_corr_range = qtgui.Range(-100, 100, 0.5, 0, 200) + self._ppm_corr_win = qtgui.RangeWidget(self._ppm_corr_range, self.set_ppm_corr, "ppm", "counter_slider", float, QtCore.Qt.Horizontal) self.top_grid_layout.addWidget(self._ppm_corr_win, 0, 5, 1, 3) for r in range(0, 1): self.top_grid_layout.setRowStretch(r, 1) @@ -132,7 +118,7 @@ def __init__(self): self._freq_tool_bar.addWidget(Qt.QLabel("Frequency" + ": ")) self._freq_line_edit = Qt.QLineEdit(str(self.freq)) self._freq_tool_bar.addWidget(self._freq_line_edit) - self._freq_line_edit.returnPressed.connect( + self._freq_line_edit.editingFinished.connect( lambda: self.set_freq(eng_notation.str_to_num(str(self._freq_line_edit.text())))) self.top_grid_layout.addWidget(self._freq_tool_bar, 0, 0, 1, 2) for r in range(0, 1): @@ -267,8 +253,7 @@ def __init__(self): self.freq_xlating_fir_filter_xxx_0 = filter.freq_xlating_fir_filter_ccc(first_decim, firdes.low_pass(1, samp_rate, options_low_pass, options_low_pass*0.2), (xlate_offset1+xlate_offset_fine1), samp_rate) self.blocks_var_to_msg_0 = blocks.var_to_msg_pair('freq') self.blocks_message_strobe_0 = blocks.message_strobe(pmt.cons(pmt.intern("freq"),pmt.from_float(freq_with_offset)), 100) - self.analog_agc3_xx_0 = analog.agc3_cc((1e-3), (1e-4), 1.0, 1.0, 1) - self.analog_agc3_xx_0.set_max_gain(65536) + self.analog_agc3_xx_0 = analog.agc3_cc((1e-3), (1e-4), 1.0, 1.0, 1, 65536) ################################################## @@ -285,7 +270,7 @@ def __init__(self): def closeEvent(self, event): - self.settings = Qt.QSettings("GNU Radio", "telive_1ch_simple_gr310_udp_xmlrpc") + self.settings = Qt.QSettings("gnuradio/flowgraphs", "telive_1ch_simple_gr310_udp_xmlrpc") self.settings.setValue("geometry", self.saveGeometry()) self.stop() self.wait() @@ -429,14 +414,12 @@ def set_first_port(self, first_port): def main(top_block_cls=telive_1ch_simple_gr310_udp_xmlrpc, options=None): - if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"): - style = gr.prefs().get_string('qtgui', 'style', 'raster') - Qt.QApplication.setGraphicsSystem(style) qapp = Qt.QApplication(sys.argv) tb = top_block_cls() tb.start() + tb.flowgraph_started.set() tb.show()