Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 131 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

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


Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ blocks:
id: variable_qtgui_entry
parameters:
comment: ''
entry_signal: editingFinished
gui_hint: 0,0,1,2
label: Frequency
type: real
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -965,4 +945,4 @@ connections:

metadata:
file_format: 1
grc_version: 3.10.5.1
grc_version: 3.10.12.0
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down Expand Up @@ -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)


##################################################
Expand All @@ -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()
Expand Down Expand Up @@ -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()

Expand Down
Loading