2019-05-13 19:19:15 +02:00
|
|
|
import subprocess
|
|
|
|
|
from functools import reduce
|
2020-09-20 19:30:18 +02:00
|
|
|
from operator import and_
|
2019-06-15 13:29:59 +02:00
|
|
|
import re
|
2021-09-22 13:11:27 +02:00
|
|
|
from distutils.version import LooseVersion, StrictVersion
|
2019-07-05 22:31:46 +02:00
|
|
|
import inspect
|
2021-02-11 19:31:44 +01:00
|
|
|
from owrx.config.core import CoreConfig
|
2021-05-31 20:41:37 +02:00
|
|
|
from owrx.config import Config
|
2019-11-14 22:13:02 +01:00
|
|
|
import shlex
|
2020-09-20 12:41:11 +02:00
|
|
|
import os
|
2020-10-10 22:08:35 +02:00
|
|
|
from datetime import datetime, timedelta
|
2019-05-12 15:56:18 +02:00
|
|
|
|
|
|
|
|
import logging
|
2019-07-21 19:40:28 +02:00
|
|
|
|
2019-05-12 15:56:18 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UnknownFeatureException(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
2019-07-05 22:31:46 +02:00
|
|
|
|
2020-10-10 22:08:35 +02:00
|
|
|
class FeatureCache(object):
|
|
|
|
|
sharedInstance = None
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def getSharedInstance():
|
|
|
|
|
if FeatureCache.sharedInstance is None:
|
|
|
|
|
FeatureCache.sharedInstance = FeatureCache()
|
|
|
|
|
return FeatureCache.sharedInstance
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.cache = {}
|
|
|
|
|
self.cachetime = timedelta(hours=2)
|
|
|
|
|
|
|
|
|
|
def has(self, feature):
|
|
|
|
|
if feature not in self.cache:
|
|
|
|
|
return False
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
if self.cache[feature]["valid_to"] < now:
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def get(self, feature):
|
|
|
|
|
return self.cache[feature]["value"]
|
|
|
|
|
|
|
|
|
|
def set(self, feature, value):
|
|
|
|
|
valid_to = datetime.now() + self.cachetime
|
|
|
|
|
self.cache[feature] = {"value": value, "valid_to": valid_to}
|
|
|
|
|
|
|
|
|
|
|
2019-05-12 15:56:18 +02:00
|
|
|
class FeatureDetector(object):
|
|
|
|
|
features = {
|
2019-12-21 19:24:14 +01:00
|
|
|
# core features; we won't start without these
|
2022-06-01 16:43:18 +02:00
|
|
|
"core": ["csdr"],
|
2019-12-21 19:24:14 +01:00
|
|
|
# different types of sdrs and their requirements
|
|
|
|
|
"rtl_sdr": ["rtl_connector"],
|
2020-01-10 20:43:28 +01:00
|
|
|
"rtl_sdr_soapy": ["soapy_connector", "soapy_rtl_sdr"],
|
2020-08-16 21:49:52 +02:00
|
|
|
"rtl_tcp": ["rtl_tcp_connector"],
|
2019-12-27 11:37:12 +01:00
|
|
|
"sdrplay": ["soapy_connector", "soapy_sdrplay"],
|
2020-05-30 22:58:31 +02:00
|
|
|
"hackrf": ["soapy_connector", "soapy_hackrf"],
|
2021-09-17 18:58:48 +02:00
|
|
|
"perseussdr": ["perseustest", "nmux"],
|
2019-12-27 11:37:12 +01:00
|
|
|
"airspy": ["soapy_connector", "soapy_airspy"],
|
|
|
|
|
"airspyhf": ["soapy_connector", "soapy_airspyhf"],
|
2023-10-15 12:31:59 +02:00
|
|
|
"afedri": ["soapy_connector", "soapy_afedri"],
|
2020-01-10 19:54:53 +01:00
|
|
|
"lime_sdr": ["soapy_connector", "soapy_lime_sdr"],
|
2021-09-17 18:58:48 +02:00
|
|
|
"fifi_sdr": ["alsa", "rockprog", "nmux"],
|
2020-01-15 22:44:11 +01:00
|
|
|
"pluto_sdr": ["soapy_connector", "soapy_pluto_sdr"],
|
2020-02-09 13:59:37 +01:00
|
|
|
"soapy_remote": ["soapy_connector", "soapy_remote"],
|
2020-04-10 16:33:04 +02:00
|
|
|
"uhd": ["soapy_connector", "soapy_uhd"],
|
2020-05-10 00:03:14 +02:00
|
|
|
"radioberry": ["soapy_connector", "soapy_radioberry"],
|
2020-07-09 15:39:33 +02:00
|
|
|
"fcdpp": ["soapy_connector", "soapy_fcdpp"],
|
2022-01-12 15:48:06 +01:00
|
|
|
"bladerf": ["soapy_connector", "soapy_bladerf"],
|
2020-11-27 18:49:33 +01:00
|
|
|
"sddc": ["sddc_connector"],
|
2020-11-02 13:11:54 +01:00
|
|
|
"hpsdr": ["hpsdr_connector"],
|
2021-02-03 03:21:09 +01:00
|
|
|
"runds": ["runds_connector"],
|
2019-12-21 19:24:14 +01:00
|
|
|
# optional features and their requirements
|
2021-09-17 18:58:48 +02:00
|
|
|
"digital_voice_digiham": ["digiham", "codecserver_ambe"],
|
|
|
|
|
"digital_voice_freedv": ["freedv_rx"],
|
2023-06-30 11:58:34 +02:00
|
|
|
"digital_voice_m17": ["m17_demod"],
|
2021-09-17 18:58:48 +02:00
|
|
|
"wsjt-x": ["wsjtx"],
|
|
|
|
|
"wsjt-x-2-3": ["wsjtx_2_3"],
|
|
|
|
|
"wsjt-x-2-4": ["wsjtx_2_4"],
|
2023-02-14 15:38:33 +01:00
|
|
|
"msk144": ["msk144decoder"],
|
2021-09-17 18:58:48 +02:00
|
|
|
"packet": ["direwolf"],
|
|
|
|
|
"pocsag": ["digiham"],
|
2021-09-22 13:11:27 +02:00
|
|
|
"js8call": ["js8", "js8py"],
|
2021-09-17 18:58:48 +02:00
|
|
|
"drm": ["dream"],
|
2023-08-22 19:59:00 +02:00
|
|
|
"dump1090": ["dump1090"],
|
2023-09-01 23:37:40 +02:00
|
|
|
"ism": ["rtl_433"],
|
2023-09-03 23:48:56 +02:00
|
|
|
"dumphfdl": ["dumphfdl"],
|
2023-09-04 19:02:43 +02:00
|
|
|
"dumpvdl2": ["dumpvdl2"],
|
2024-01-18 02:07:55 +01:00
|
|
|
"redsea": ["redsea"],
|
2024-01-30 01:29:25 +01:00
|
|
|
"dab": ["csdreti", "dablin"],
|
|
|
|
|
"mqtt": ["paho_mqtt"],
|
2019-05-12 15:56:18 +02:00
|
|
|
}
|
|
|
|
|
|
2019-05-13 19:19:15 +02:00
|
|
|
def feature_availability(self):
|
|
|
|
|
return {name: self.is_available(name) for name in FeatureDetector.features}
|
|
|
|
|
|
2019-07-05 19:30:24 +02:00
|
|
|
def feature_report(self):
|
2019-07-05 22:31:46 +02:00
|
|
|
def requirement_details(name):
|
|
|
|
|
available = self.has_requirement(name)
|
|
|
|
|
return {
|
|
|
|
|
"available": available,
|
|
|
|
|
# as of now, features are always enabled as soon as they are available. this may change in the future.
|
|
|
|
|
"enabled": available,
|
2019-07-21 19:40:28 +02:00
|
|
|
"description": self.get_requirement_description(name),
|
2019-07-05 22:31:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 19:30:24 +02:00
|
|
|
def feature_details(name):
|
2019-07-05 22:31:46 +02:00
|
|
|
return {
|
|
|
|
|
"available": self.is_available(name),
|
2019-07-21 19:40:28 +02:00
|
|
|
"requirements": {name: requirement_details(name) for name in self.get_requirements(name)},
|
2019-07-05 22:31:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 19:30:24 +02:00
|
|
|
return {name: feature_details(name) for name in FeatureDetector.features}
|
|
|
|
|
|
2019-05-12 15:56:18 +02:00
|
|
|
def is_available(self, feature):
|
2020-10-10 23:00:05 +02:00
|
|
|
return self.has_requirements(self.get_requirements(feature))
|
2019-05-12 15:56:18 +02:00
|
|
|
|
2022-06-01 17:11:45 +02:00
|
|
|
def get_failed_requirements(self, feature):
|
|
|
|
|
return [req for req in self.get_requirements(feature) if not self.has_requirement(req)]
|
|
|
|
|
|
2019-05-12 15:56:18 +02:00
|
|
|
def get_requirements(self, feature):
|
|
|
|
|
try:
|
|
|
|
|
return FeatureDetector.features[feature]
|
|
|
|
|
except KeyError:
|
2019-07-21 19:40:28 +02:00
|
|
|
raise UnknownFeatureException('Feature "{0}" is not known.'.format(feature))
|
2019-05-12 15:56:18 +02:00
|
|
|
|
|
|
|
|
def has_requirements(self, requirements):
|
|
|
|
|
passed = True
|
|
|
|
|
for requirement in requirements:
|
2019-07-05 22:31:46 +02:00
|
|
|
passed = passed and self.has_requirement(requirement)
|
2019-05-12 15:56:18 +02:00
|
|
|
return passed
|
|
|
|
|
|
2019-07-05 22:31:46 +02:00
|
|
|
def _get_requirement_method(self, requirement):
|
|
|
|
|
methodname = "has_" + requirement
|
|
|
|
|
if hasattr(self, methodname) and callable(getattr(self, methodname)):
|
|
|
|
|
return getattr(self, methodname)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def has_requirement(self, requirement):
|
2020-10-10 23:00:05 +02:00
|
|
|
cache = FeatureCache.getSharedInstance()
|
|
|
|
|
if cache.has(requirement):
|
|
|
|
|
return cache.get(requirement)
|
|
|
|
|
|
2019-07-05 22:31:46 +02:00
|
|
|
method = self._get_requirement_method(requirement)
|
2020-10-10 23:00:05 +02:00
|
|
|
result = False
|
2019-07-05 22:31:46 +02:00
|
|
|
if method is not None:
|
2020-10-10 23:00:05 +02:00
|
|
|
result = method()
|
2019-07-05 22:31:46 +02:00
|
|
|
else:
|
|
|
|
|
logger.error("detection of requirement {0} not implement. please fix in code!".format(requirement))
|
2020-10-10 23:00:05 +02:00
|
|
|
|
|
|
|
|
cache.set(requirement, result)
|
|
|
|
|
return result
|
2019-07-05 22:31:46 +02:00
|
|
|
|
|
|
|
|
def get_requirement_description(self, requirement):
|
|
|
|
|
return inspect.getdoc(self._get_requirement_method(requirement))
|
|
|
|
|
|
2020-09-20 12:41:11 +02:00
|
|
|
def command_is_runnable(self, command, expected_result=None):
|
2021-02-06 21:55:47 +01:00
|
|
|
tmp_dir = CoreConfig().get_temporary_directory()
|
2019-11-14 22:13:02 +01:00
|
|
|
cmd = shlex.split(command)
|
2020-09-20 12:41:11 +02:00
|
|
|
env = os.environ.copy()
|
|
|
|
|
# prevent X11 programs from opening windows if called from a GUI shell
|
|
|
|
|
env.pop("DISPLAY", None)
|
2019-11-14 22:13:02 +01:00
|
|
|
try:
|
2021-01-20 17:01:46 +01:00
|
|
|
process = subprocess.Popen(
|
|
|
|
|
cmd,
|
|
|
|
|
stdin=subprocess.DEVNULL,
|
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
|
stderr=subprocess.DEVNULL,
|
|
|
|
|
cwd=tmp_dir,
|
|
|
|
|
env=env,
|
|
|
|
|
)
|
2023-09-01 22:49:58 +02:00
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
rc = process.wait(10)
|
|
|
|
|
break
|
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
|
logger.warning("feature check command \"%s\" did not return after 10 seconds!", command)
|
|
|
|
|
process.kill()
|
|
|
|
|
|
2020-09-20 12:41:11 +02:00
|
|
|
if expected_result is None:
|
|
|
|
|
return rc != 32512
|
|
|
|
|
else:
|
|
|
|
|
return rc == expected_result
|
2019-11-14 22:13:02 +01:00
|
|
|
except FileNotFoundError:
|
|
|
|
|
return False
|
2019-05-13 19:19:15 +02:00
|
|
|
|
2019-05-12 15:56:18 +02:00
|
|
|
def has_csdr(self):
|
2019-07-05 22:31:46 +02:00
|
|
|
"""
|
|
|
|
|
OpenWebRX uses the demodulator and pipeline tools provided by the csdr project. Please check out [the project
|
2020-03-29 18:33:14 +02:00
|
|
|
page on github](https://github.com/jketterl/csdr) for further details and installation instructions.
|
2020-08-26 20:07:58 +02:00
|
|
|
|
2022-06-01 16:43:18 +02:00
|
|
|
In addition, the [pycsdr](https://github.com/jketterl/pycsdr) package must be installed to provide
|
|
|
|
|
python bindings for the csdr library.
|
2021-09-17 18:58:48 +02:00
|
|
|
"""
|
2024-02-03 12:00:19 +01:00
|
|
|
required_version = LooseVersion("0.19.0")
|
2022-06-01 16:43:18 +02:00
|
|
|
|
2020-08-26 20:07:58 +02:00
|
|
|
try:
|
2022-06-01 16:43:18 +02:00
|
|
|
from pycsdr.modules import csdr_version
|
2021-09-17 18:58:48 +02:00
|
|
|
from pycsdr.modules import version as pycsdr_version
|
2022-06-01 16:43:18 +02:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
LooseVersion(csdr_version) >= required_version and
|
|
|
|
|
LooseVersion(pycsdr_version) >= required_version
|
|
|
|
|
)
|
2021-09-17 18:58:48 +02:00
|
|
|
except ImportError:
|
2020-08-26 20:07:58 +02:00
|
|
|
return False
|
2019-05-12 15:56:18 +02:00
|
|
|
|
|
|
|
|
def has_nmux(self):
|
2019-07-05 22:31:46 +02:00
|
|
|
"""
|
|
|
|
|
Nmux is another tool provided by the csdr project. It is used for internal multiplexing of the IQ data streams.
|
|
|
|
|
If you're missing nmux even though you have csdr installed, please update your csdr version.
|
|
|
|
|
"""
|
2019-05-13 19:19:15 +02:00
|
|
|
return self.command_is_runnable("nmux --help")
|
2019-05-12 15:56:18 +02:00
|
|
|
|
2020-03-15 17:24:36 +01:00
|
|
|
def has_perseustest(self):
|
|
|
|
|
"""
|
|
|
|
|
To use a Microtelecom Perseus HF receiver, compile and
|
|
|
|
|
install the libperseus-sdr:
|
|
|
|
|
```
|
|
|
|
|
sudo apt install libusb-1.0-0-dev
|
|
|
|
|
cd /tmp
|
2020-03-16 00:13:51 +01:00
|
|
|
wget https://github.com/Microtelecom/libperseus-sdr/releases/download/v0.8.2/libperseus_sdr-0.8.2.tar.gz
|
|
|
|
|
tar -zxvf libperseus_sdr-0.8.2.tar.gz
|
|
|
|
|
cd libperseus_sdr-0.8.2/
|
2020-03-15 17:24:36 +01:00
|
|
|
./configure
|
|
|
|
|
make
|
|
|
|
|
sudo make install
|
|
|
|
|
sudo ldconfig
|
|
|
|
|
perseustest
|
|
|
|
|
```
|
|
|
|
|
"""
|
2020-03-16 00:13:51 +01:00
|
|
|
return self.command_is_runnable("perseustest -h")
|
2020-03-15 17:24:36 +01:00
|
|
|
|
2019-05-13 19:19:15 +02:00
|
|
|
def has_digiham(self):
|
2019-07-05 22:31:46 +02:00
|
|
|
"""
|
|
|
|
|
To use digital voice modes, the digiham package is required. You can find the package and installation
|
|
|
|
|
instructions [here](https://github.com/jketterl/digiham).
|
|
|
|
|
|
2022-06-01 16:43:18 +02:00
|
|
|
In addition, the [pydigiham](https://github.com/jketterl/pydigiham) package must be installed to provide
|
|
|
|
|
python bindings for the digiham library.
|
|
|
|
|
|
2019-07-05 22:31:46 +02:00
|
|
|
Please note: there is close interaction between digiham and openwebrx, so older versions will probably not work.
|
|
|
|
|
If you have an older verison of digiham installed, please update it along with openwebrx.
|
2022-06-01 16:43:18 +02:00
|
|
|
As of now, we require version 0.6 of digiham.
|
2019-07-05 22:31:46 +02:00
|
|
|
"""
|
2022-06-01 16:43:18 +02:00
|
|
|
required_version = LooseVersion("0.6")
|
2019-06-15 13:29:59 +02:00
|
|
|
|
2021-09-17 18:58:48 +02:00
|
|
|
try:
|
2022-06-01 16:43:18 +02:00
|
|
|
from digiham.modules import digiham_version as digiham_version
|
|
|
|
|
from digiham.modules import version as pydigiham_version
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
LooseVersion(digiham_version) >= required_version
|
|
|
|
|
and LooseVersion(pydigiham_version) >= required_version
|
|
|
|
|
)
|
2021-09-17 18:58:48 +02:00
|
|
|
except ImportError:
|
|
|
|
|
return False
|
2019-05-13 19:19:15 +02:00
|
|
|
|
2021-05-17 23:57:37 +02:00
|
|
|
def _check_connector(self, command, required_version):
|
|
|
|
|
owrx_connector_version_regex = re.compile("^{} version (.*)$".format(re.escape(command)))
|
2019-11-21 15:31:37 +01:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
process = subprocess.Popen([command, "--version"], stdout=subprocess.PIPE)
|
|
|
|
|
matches = owrx_connector_version_regex.match(process.stdout.readline().decode())
|
|
|
|
|
if matches is None:
|
|
|
|
|
return False
|
|
|
|
|
version = LooseVersion(matches.group(1))
|
|
|
|
|
process.wait(1)
|
|
|
|
|
return version >= required_version
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
return False
|
|
|
|
|
|
2021-05-17 23:57:37 +02:00
|
|
|
def _check_owrx_connector(self, command):
|
2023-03-18 03:14:49 +01:00
|
|
|
return self._check_connector(command, LooseVersion("0.7"))
|
2021-05-17 23:57:37 +02:00
|
|
|
|
2019-11-21 15:31:37 +01:00
|
|
|
def has_rtl_connector(self):
|
2019-11-11 18:07:14 +01:00
|
|
|
"""
|
|
|
|
|
The owrx_connector package offers direct interfacing between your hardware and openwebrx. It allows quicker
|
|
|
|
|
frequency switching, uses less CPU and can even provide more stability in some cases.
|
|
|
|
|
|
2019-12-27 11:37:12 +01:00
|
|
|
You can get it [here](https://github.com/jketterl/owrx_connector).
|
2019-11-11 18:07:14 +01:00
|
|
|
"""
|
2021-05-17 23:57:37 +02:00
|
|
|
return self._check_owrx_connector("rtl_connector")
|
2019-11-11 18:07:14 +01:00
|
|
|
|
2020-08-16 21:49:52 +02:00
|
|
|
def has_rtl_tcp_connector(self):
|
|
|
|
|
"""
|
|
|
|
|
The owrx_connector package offers direct interfacing between your hardware and openwebrx. It allows quicker
|
|
|
|
|
frequency switching, uses less CPU and can even provide more stability in some cases.
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/jketterl/owrx_connector).
|
|
|
|
|
"""
|
2021-05-17 23:57:37 +02:00
|
|
|
return self._check_owrx_connector("rtl_tcp_connector")
|
2020-08-16 21:49:52 +02:00
|
|
|
|
2019-11-21 15:31:37 +01:00
|
|
|
def has_soapy_connector(self):
|
|
|
|
|
"""
|
|
|
|
|
The owrx_connector package offers direct interfacing between your hardware and openwebrx. It allows quicker
|
|
|
|
|
frequency switching, uses less CPU and can even provide more stability in some cases.
|
2019-11-11 18:07:14 +01:00
|
|
|
|
2019-12-27 11:37:12 +01:00
|
|
|
You can get it [here](https://github.com/jketterl/owrx_connector).
|
2019-11-21 15:31:37 +01:00
|
|
|
"""
|
2021-05-17 23:57:37 +02:00
|
|
|
return self._check_owrx_connector("soapy_connector")
|
2019-11-11 18:07:14 +01:00
|
|
|
|
2019-12-27 11:37:12 +01:00
|
|
|
def _has_soapy_driver(self, driver):
|
2019-12-30 16:38:16 +01:00
|
|
|
try:
|
2023-03-18 03:14:49 +01:00
|
|
|
process = subprocess.Popen(["soapy_connector", "--listdrivers"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
|
|
|
|
|
|
|
|
|
drivers = [line.decode().strip() for line in process.stdout]
|
|
|
|
|
process.wait(1)
|
2019-12-28 01:24:07 +01:00
|
|
|
|
2020-04-10 16:33:04 +02:00
|
|
|
return driver in drivers
|
2019-12-30 16:38:16 +01:00
|
|
|
except FileNotFoundError:
|
|
|
|
|
return False
|
2019-12-27 11:37:12 +01:00
|
|
|
|
2020-01-10 23:31:51 +01:00
|
|
|
def has_soapy_rtl_sdr(self):
|
2020-01-10 20:43:28 +01:00
|
|
|
"""
|
|
|
|
|
The SoapySDR module for rtl-sdr devices can be used as an alternative to the rtl_connector. It provides
|
|
|
|
|
additional support for the direct sampling mod.
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/pothosware/SoapyRTLSDR/wiki).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("rtlsdr")
|
|
|
|
|
|
2020-01-10 23:31:51 +01:00
|
|
|
def has_soapy_sdrplay(self):
|
2019-12-27 11:37:12 +01:00
|
|
|
"""
|
|
|
|
|
The SoapySDR module for sdrplay devices is required for interfacing with SDRPlay devices (RSP1*, RSP2*, RSPDuo)
|
|
|
|
|
|
2020-05-24 14:43:25 +02:00
|
|
|
You can get it [here](https://github.com/SDRplay/SoapySDRPlay).
|
2019-12-27 11:37:12 +01:00
|
|
|
"""
|
2020-04-10 16:33:04 +02:00
|
|
|
return self._has_soapy_driver("sdrplay")
|
2019-12-27 11:37:12 +01:00
|
|
|
|
|
|
|
|
def has_soapy_airspy(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapySDR module for airspy devices is required for interfacing with Airspy devices (Airspy R2, Airspy Mini).
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/pothosware/SoapyAirspy/wiki).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("airspy")
|
|
|
|
|
|
|
|
|
|
def has_soapy_airspyhf(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapySDR module for airspyhf devices is required for interfacing with Airspy HF devices (Airspy HF+,
|
|
|
|
|
Airspy HF discovery).
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/pothosware/SoapyAirspyHF/wiki).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("airspyhf")
|
|
|
|
|
|
2023-10-15 12:31:59 +02:00
|
|
|
def has_soapy_afedri(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapyAfedri module allows using Afedri SDR-Net devices with SoapySDR.
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/alexander-sholohov/SoapyAfedri).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("afedri")
|
|
|
|
|
|
2020-01-10 19:54:53 +01:00
|
|
|
def has_soapy_lime_sdr(self):
|
|
|
|
|
"""
|
|
|
|
|
The Lime Suite installs - amongst others - a Soapy driver for the LimeSDR device series.
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/myriadrf/LimeSuite).
|
|
|
|
|
"""
|
2020-04-10 16:33:04 +02:00
|
|
|
return self._has_soapy_driver("lime")
|
2020-01-10 19:54:53 +01:00
|
|
|
|
2020-01-15 22:44:11 +01:00
|
|
|
def has_soapy_pluto_sdr(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapySDR module for PlutoSDR devices is required for interfacing with PlutoSDR devices.
|
|
|
|
|
|
2021-07-15 12:53:48 +02:00
|
|
|
You can get it [here](https://github.com/pothosware/SoapyPlutoSDR).
|
2020-01-15 22:44:11 +01:00
|
|
|
"""
|
2020-04-10 16:33:04 +02:00
|
|
|
return self._has_soapy_driver("plutosdr")
|
2020-01-15 22:44:11 +01:00
|
|
|
|
2020-02-09 13:59:37 +01:00
|
|
|
def has_soapy_remote(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapyRemote allows the usage of remote SDR devices using the SoapySDRServer.
|
|
|
|
|
|
|
|
|
|
You can get the code and find additional information [here](https://github.com/pothosware/SoapyRemote/wiki).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("remote")
|
|
|
|
|
|
2020-04-10 16:33:04 +02:00
|
|
|
def has_soapy_uhd(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapyUHD module allows using UHD / USRP devices with SoapySDR.
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/pothosware/SoapyUHD/wiki).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("uhd")
|
|
|
|
|
|
2020-05-10 00:03:14 +02:00
|
|
|
def has_soapy_radioberry(self):
|
|
|
|
|
"""
|
|
|
|
|
The Radioberry is a SDR hat for the Raspberry Pi.
|
|
|
|
|
|
|
|
|
|
You can find more information, along with its SoapySDR module [here](https://github.com/pa3gsb/Radioberry-2.x).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("radioberry")
|
|
|
|
|
|
2020-05-30 22:58:31 +02:00
|
|
|
def has_soapy_hackrf(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapyHackRF allows HackRF to be used with SoapySDR.
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/pothosware/SoapyHackRF/wiki).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("hackrf")
|
|
|
|
|
|
2020-07-09 15:39:33 +02:00
|
|
|
def has_soapy_fcdpp(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapyFCDPP module allows the use of the Funcube Dongle Pro+.
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/pothosware/SoapyFCDPP).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("fcdpp")
|
|
|
|
|
|
2022-01-12 15:48:06 +01:00
|
|
|
def has_soapy_bladerf(self):
|
|
|
|
|
"""
|
|
|
|
|
The SoapyBladeRF module allows the use of Blade RF devices.
|
|
|
|
|
|
|
|
|
|
You can get it [here](https://github.com/pothosware/SoapyBladeRF).
|
|
|
|
|
"""
|
|
|
|
|
return self._has_soapy_driver("bladerf")
|
|
|
|
|
|
2020-11-23 01:00:25 +01:00
|
|
|
def has_m17_demod(self):
|
2020-12-08 16:59:49 +01:00
|
|
|
"""
|
|
|
|
|
The `m17-demod` tool is used to demodulate M17 digital voice signals.
|
|
|
|
|
|
|
|
|
|
You can find more information [here](https://github.com/mobilinkd/m17-cxx-demod)
|
|
|
|
|
"""
|
2023-06-30 11:58:34 +02:00
|
|
|
return self.command_is_runnable("m17-demod", 0)
|
2020-11-23 01:00:25 +01:00
|
|
|
|
2019-06-07 15:11:04 +02:00
|
|
|
def has_direwolf(self):
|
2019-12-27 11:37:12 +01:00
|
|
|
"""
|
|
|
|
|
OpenWebRX uses the [direwolf](https://github.com/wb2osz/direwolf) software modem to decode Packet Radio and
|
|
|
|
|
report data back to APRS-IS. Direwolf is available from the package manager on many distributions, or you can
|
|
|
|
|
compile it from source.
|
|
|
|
|
"""
|
2019-06-22 18:20:01 +02:00
|
|
|
return self.command_is_runnable("direwolf --help")
|
|
|
|
|
|
2019-06-07 15:44:11 +02:00
|
|
|
def has_airspy_rx(self):
|
2019-07-05 22:31:46 +02:00
|
|
|
"""
|
|
|
|
|
In order to use an Airspy Receiver, you need to install the airspy_rx receiver software.
|
|
|
|
|
"""
|
2019-11-15 19:36:07 +01:00
|
|
|
return self.command_is_runnable("airspy_rx --help")
|
2019-07-08 20:16:29 +02:00
|
|
|
|
|
|
|
|
def has_wsjtx(self):
|
|
|
|
|
"""
|
|
|
|
|
To decode FT8 and other digimodes, you need to install the WSJT-X software suite. Please check the
|
2023-02-14 15:37:37 +01:00
|
|
|
[WSJT-X homepage](https://wsjt.sourceforge.io/) for ready-made packages or instructions
|
2019-07-08 20:16:29 +02:00
|
|
|
on how to build from source.
|
|
|
|
|
"""
|
2019-07-21 19:40:28 +02:00
|
|
|
return reduce(and_, map(self.command_is_runnable, ["jt9", "wsprd"]), True)
|
2019-12-19 21:37:19 +01:00
|
|
|
|
2021-01-09 19:19:53 +01:00
|
|
|
def _has_wsjtx_version(self, required_version):
|
|
|
|
|
wsjt_version_regex = re.compile("^WSJT-X (.*)$")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
process = subprocess.Popen(["wsjtx_app_version", "--version"], stdout=subprocess.PIPE)
|
|
|
|
|
matches = wsjt_version_regex.match(process.stdout.readline().decode())
|
|
|
|
|
if matches is None:
|
|
|
|
|
return False
|
|
|
|
|
version = LooseVersion(matches.group(1))
|
|
|
|
|
process.wait(1)
|
|
|
|
|
return version >= required_version
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def has_wsjtx_2_3(self):
|
|
|
|
|
"""
|
|
|
|
|
Newer digital modes (e.g. FST4, FST4) require WSJT-X in at least version 2.3.
|
|
|
|
|
"""
|
|
|
|
|
return self.has_wsjtx() and self._has_wsjtx_version(LooseVersion("2.3"))
|
|
|
|
|
|
2021-02-03 19:33:02 +01:00
|
|
|
def has_wsjtx_2_4(self):
|
|
|
|
|
"""
|
|
|
|
|
WSJT-X version 2.4 introduced the Q65 mode.
|
|
|
|
|
"""
|
|
|
|
|
return self.has_wsjtx() and self._has_wsjtx_version(LooseVersion("2.4"))
|
|
|
|
|
|
2023-02-14 15:38:33 +01:00
|
|
|
def has_msk144decoder(self):
|
|
|
|
|
"""
|
|
|
|
|
To decode the MSK144 digimode please install the "msk144decoder". See the
|
|
|
|
|
[project page](https://github.com/alexander-sholohov/msk144decoder) for more details.
|
|
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("msk144decoder")
|
|
|
|
|
|
2020-04-12 13:10:23 +02:00
|
|
|
def has_js8(self):
|
|
|
|
|
"""
|
|
|
|
|
To decode JS8, you will need to install [JS8Call](http://js8call.com/)
|
2020-05-03 12:09:36 +02:00
|
|
|
|
|
|
|
|
Please note that the `js8` command line decoder is not made available on $PATH by some JS8Call package builds.
|
|
|
|
|
You will need to manually make it available by either linking it to `/usr/bin` or by adding its location to
|
|
|
|
|
$PATH.
|
2020-04-12 13:10:23 +02:00
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("js8")
|
|
|
|
|
|
2021-09-22 13:11:27 +02:00
|
|
|
def has_js8py(self):
|
|
|
|
|
"""
|
|
|
|
|
The js8py library is used to decode binary JS8 messages into readable text. More information is available on
|
|
|
|
|
[its github page](https://github.com/jketterl/js8py).
|
|
|
|
|
"""
|
2022-11-30 01:16:12 +01:00
|
|
|
required_version = StrictVersion("0.2")
|
2021-09-22 13:11:27 +02:00
|
|
|
try:
|
|
|
|
|
from js8py.version import strictversion
|
2022-06-01 16:43:18 +02:00
|
|
|
|
2021-09-22 13:11:27 +02:00
|
|
|
return strictversion >= required_version
|
|
|
|
|
except ImportError:
|
|
|
|
|
return False
|
|
|
|
|
|
2019-12-19 21:37:19 +01:00
|
|
|
def has_alsa(self):
|
|
|
|
|
"""
|
|
|
|
|
Some SDR receivers are identifying themselves as a soundcard. In order to read their data, OpenWebRX relies
|
|
|
|
|
on the Alsa library. It is available as a package for most Linux distributions.
|
|
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("arecord --help")
|
2020-05-14 21:40:28 +02:00
|
|
|
|
|
|
|
|
def has_rockprog(self):
|
|
|
|
|
"""
|
|
|
|
|
The "rockprog" executable is required to send commands to your FiFiSDR. It needs to be installed separately.
|
|
|
|
|
|
|
|
|
|
You can find instructions and downloads [here](https://o28.sischa.net/fifisdr/trac/wiki/De%3Arockprog).
|
|
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("rockprog")
|
2020-07-28 00:28:20 +02:00
|
|
|
|
|
|
|
|
def has_freedv_rx(self):
|
2020-08-07 22:58:24 +02:00
|
|
|
"""
|
2024-02-01 04:07:53 +01:00
|
|
|
The "freedv\\_rx" executable is required to demodulate FreeDV digital transmissions. It comes together with the
|
2020-08-07 22:58:24 +02:00
|
|
|
codec2 library, but it's only a supplemental part and not installed by default or contained in its packages.
|
2024-02-01 04:07:53 +01:00
|
|
|
To install it, you will need to compile codec2 from source and manually install freedv\\_rx.
|
2020-08-07 22:58:24 +02:00
|
|
|
|
2020-10-16 19:52:51 +02:00
|
|
|
Detailed installation instructions are available on the
|
|
|
|
|
[OpenWebRX wiki](https://github.com/jketterl/openwebrx/wiki/FreeDV-demodulator-notes).
|
2020-08-07 22:58:24 +02:00
|
|
|
"""
|
2020-07-28 00:28:20 +02:00
|
|
|
return self.command_is_runnable("freedv_rx")
|
2020-09-04 18:09:02 +02:00
|
|
|
|
|
|
|
|
def has_dream(self):
|
2020-09-04 20:27:12 +02:00
|
|
|
"""
|
2020-10-16 19:52:51 +02:00
|
|
|
In order to be able to decode DRM broadcasts, OpenWebRX needs the "dream" DRM decoder.
|
2020-09-20 12:41:11 +02:00
|
|
|
|
2020-09-04 20:27:12 +02:00
|
|
|
The version supplied by most distributions will not work properly on the command line, so compiling from source
|
2020-10-16 19:52:51 +02:00
|
|
|
with a custom set of commands is recommended. Detailed installation instructions are available on the
|
|
|
|
|
[OpenWebRX wiki](https://github.com/jketterl/openwebrx/wiki/DRM-demodulator-notes).
|
2020-09-04 20:27:12 +02:00
|
|
|
"""
|
2020-09-20 12:41:11 +02:00
|
|
|
return self.command_is_runnable("dream --help", 0)
|
2020-11-02 13:11:54 +01:00
|
|
|
|
2020-11-27 18:39:10 +01:00
|
|
|
def has_sddc_connector(self):
|
2020-11-27 18:49:33 +01:00
|
|
|
"""
|
|
|
|
|
The sddc_connector allows connectivity with SDR devices powered by libsddc, e.g. RX666, RX888, HF103.
|
|
|
|
|
|
|
|
|
|
You can find more information [here](https://github.com/jketterl/sddc_connector).
|
|
|
|
|
"""
|
2021-05-17 23:57:37 +02:00
|
|
|
return self._check_connector("sddc_connector", LooseVersion("0.1"))
|
2020-11-12 23:45:39 +01:00
|
|
|
|
2020-11-02 13:11:54 +01:00
|
|
|
def has_hpsdr_connector(self):
|
|
|
|
|
"""
|
|
|
|
|
In order to use the HPSDR connector, you will need to install [hpsdrconnector]
|
|
|
|
|
(https://github.com/jancona/hpsdrconnector).
|
|
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("hpsdrconnector -h")
|
2020-11-30 00:34:44 +01:00
|
|
|
|
2021-02-03 03:21:09 +01:00
|
|
|
def has_runds_connector(self):
|
2020-11-30 00:34:44 +01:00
|
|
|
"""
|
2021-02-03 03:21:09 +01:00
|
|
|
To use radios supporting R&S radios via EB200 or Ammos, you need to install the runds_connector.
|
2020-11-30 00:34:44 +01:00
|
|
|
|
2021-02-03 03:21:09 +01:00
|
|
|
You can find more information [here](https://github.com/jketterl/runds_connector).
|
2020-11-30 00:34:44 +01:00
|
|
|
"""
|
2021-05-17 23:57:37 +02:00
|
|
|
return self._check_connector("runds_connector", LooseVersion("0.2"))
|
2021-05-31 20:41:37 +02:00
|
|
|
|
|
|
|
|
def has_codecserver_ambe(self):
|
2021-08-03 19:52:49 +02:00
|
|
|
"""
|
|
|
|
|
Codecserver is used to decode audio data from digital voice modes using the AMBE codec.
|
|
|
|
|
|
|
|
|
|
You can find more information [here](https://github.com/jketterl/codecserver).
|
2023-08-31 21:47:49 +02:00
|
|
|
|
|
|
|
|
NOTE: this feature flag checks both the availability of codecserver as well as the availability of the AMBE
|
|
|
|
|
codec in the configured codecserer instance.
|
2021-08-03 19:52:49 +02:00
|
|
|
"""
|
2021-09-29 17:23:23 +02:00
|
|
|
|
2021-05-31 20:41:37 +02:00
|
|
|
config = Config.get()
|
2021-09-29 17:23:23 +02:00
|
|
|
server = ""
|
2021-05-31 20:41:37 +02:00
|
|
|
if "digital_voice_codecserver" in config:
|
2021-09-29 17:23:23 +02:00
|
|
|
server = config["digital_voice_codecserver"]
|
2021-05-31 20:41:37 +02:00
|
|
|
try:
|
2021-09-29 17:23:23 +02:00
|
|
|
from digiham.modules import MbeSynthesizer
|
2022-06-01 16:43:18 +02:00
|
|
|
|
2021-09-29 17:23:23 +02:00
|
|
|
return MbeSynthesizer.hasAmbe(server)
|
|
|
|
|
except ImportError:
|
|
|
|
|
return False
|
|
|
|
|
except ConnectionError:
|
2021-05-31 20:41:37 +02:00
|
|
|
return False
|
2023-07-02 00:43:54 +02:00
|
|
|
except RuntimeError as e:
|
|
|
|
|
logger.exception("Codecserver error while checking for AMBE support:")
|
|
|
|
|
return False
|
2023-08-22 19:59:00 +02:00
|
|
|
|
|
|
|
|
def has_dump1090(self):
|
2023-08-31 22:44:56 +02:00
|
|
|
"""
|
|
|
|
|
To be able to decode Mode-S and ADS-B traffic originating from airplanes, you need to install the dump1090
|
|
|
|
|
decoder. There is a number of forks available, any version that supports the `--ifile` and `--iformat` arguments
|
|
|
|
|
should work.
|
|
|
|
|
|
|
|
|
|
Recommended fork: [dump1090 by Flightaware](https://github.com/flightaware/dump1090)
|
|
|
|
|
|
2023-09-06 00:37:59 +02:00
|
|
|
If you are using the OpenWebRX Debian or Ubuntu repository, you should be able to install the package
|
|
|
|
|
`dump1090-fa-minimal`.
|
2023-08-31 22:44:56 +02:00
|
|
|
|
|
|
|
|
If you are running a different fork, please make sure that the command `dump1090` (without suffixes) runs the
|
|
|
|
|
version you would like to use. You can use symbolic links or the
|
|
|
|
|
[Debian alternatives system](https://wiki.debian.org/DebianAlternatives) to achieve this.
|
|
|
|
|
"""
|
2023-09-01 17:42:08 +02:00
|
|
|
return self.command_is_runnable("dump1090 --version")
|
2023-09-01 23:37:40 +02:00
|
|
|
|
|
|
|
|
def has_rtl_433(self):
|
|
|
|
|
"""
|
|
|
|
|
OpenWebRX can make use of the `rtl_433` software to decode various signals in the ISM bands.
|
|
|
|
|
|
|
|
|
|
You can find more information [here](https://github.com/merbanan/rtl_433).
|
|
|
|
|
|
|
|
|
|
Debian and Ubuntu based systems should be able to install the package `rtl-433` from the package manager.
|
|
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("rtl_433 -h")
|
2023-09-03 23:48:56 +02:00
|
|
|
|
|
|
|
|
def has_dumphfdl(self):
|
|
|
|
|
"""
|
2023-09-06 00:37:59 +02:00
|
|
|
OpenWebRX supports decoding HFDL airplane communications using the `dumphfdl` decoder.
|
|
|
|
|
|
|
|
|
|
You can find more information [here](https://github.com/szpajder/dumphfdl)
|
|
|
|
|
|
|
|
|
|
If you are using the OpenWebRX Debian or Ubuntu repository, you should be able to install the package
|
|
|
|
|
`dumphfdl`.
|
2023-09-03 23:48:56 +02:00
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("dumphfdl --version")
|
2023-09-04 19:02:43 +02:00
|
|
|
|
|
|
|
|
def has_dumpvdl2(self):
|
|
|
|
|
"""
|
2023-09-06 00:37:59 +02:00
|
|
|
OpenWebRX supports decoding VDL Mode 2 airplane communications using the `dumpvdl2` decoder.
|
|
|
|
|
|
|
|
|
|
You can find more information [here](https://github.com/szpajder/dumpvdl2)
|
|
|
|
|
|
|
|
|
|
If you are using the OpenWebRX Debian or Ubuntu repository, you should be able to install the package
|
|
|
|
|
`dumpvdl2`.
|
2023-09-04 19:02:43 +02:00
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("dumpvdl2 --version")
|
2024-01-18 02:07:55 +01:00
|
|
|
|
|
|
|
|
def has_redsea(self):
|
|
|
|
|
"""
|
|
|
|
|
OpenWebRX can decode RDS data on WFM broadcast station if the `redsea` decoder is available.
|
|
|
|
|
|
|
|
|
|
You can find more information [here](https://github.com/windytan/redsea)
|
|
|
|
|
|
|
|
|
|
If you are using the OpenWebRX Debian or Ubuntu repository, you should be able to install the package
|
|
|
|
|
`redsea`.
|
|
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("redsea --version")
|
2024-01-22 02:38:20 +01:00
|
|
|
|
|
|
|
|
def has_csdreti(self):
|
|
|
|
|
"""
|
2024-01-27 16:36:14 +01:00
|
|
|
To decode DAB broadcast signals, OpenWebRX needs the ETI decoder from the
|
|
|
|
|
[`csdr-eti`](https://github.com/jketterl/csdr-eti) project, together with the
|
|
|
|
|
associated python bindings from [`pycsdr-eti`](https://github.com/jketterl/pycsdr-eti).
|
|
|
|
|
|
|
|
|
|
If you are using the OpenWebRX Debian or Ubuntu repository, the `python3-csdr-eti` package should be all you
|
|
|
|
|
need.
|
2024-01-22 02:38:20 +01:00
|
|
|
"""
|
|
|
|
|
required_version = LooseVersion("0.1")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from csdreti.modules import csdreti_version
|
|
|
|
|
from csdreti.modules import version as pycsdreti_version
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
LooseVersion(csdreti_version) >= required_version
|
|
|
|
|
and LooseVersion(pycsdreti_version) >= required_version
|
|
|
|
|
)
|
|
|
|
|
except ImportError:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def has_dablin(self):
|
|
|
|
|
"""
|
2024-01-27 16:36:14 +01:00
|
|
|
To decode DAB broadcast signals, OpenWebRX needs the [`dablin`](https://github.com/Opendigitalradio/dablin)
|
|
|
|
|
decoding software.
|
|
|
|
|
|
|
|
|
|
Dablin comes packaged with Debian and Ubuntu, so installing the `dablin` package should get you going.
|
2024-01-22 02:38:20 +01:00
|
|
|
"""
|
|
|
|
|
return self.command_is_runnable("dablin -h")
|
2024-01-30 01:29:25 +01:00
|
|
|
|
|
|
|
|
def has_paho_mqtt(self):
|
|
|
|
|
try:
|
|
|
|
|
from paho.mqtt import __version__
|
|
|
|
|
return True
|
|
|
|
|
except ImportError:
|
|
|
|
|
return False
|