diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bb3468d..6c3a3713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Added support for decoding HFDL and VDL2 aircraft communications - Added decoding of ISM band transmissions using rtl_433 - Added IPv6 support +- New devices supported: + - Afedri SDR-Net **1.2.2** - Fixed an over-the-air code injection vulnerability diff --git a/docker.sh b/docker.sh index a1bfb897..b3657af6 100755 --- a/docker.sh +++ b/docker.sh @@ -2,7 +2,7 @@ set -euo pipefail ARCH=$(uname -m) -IMAGES="openwebrx-rtlsdr openwebrx-sdrplay openwebrx-hackrf openwebrx-airspy openwebrx-rtlsdr-soapy openwebrx-plutosdr openwebrx-limesdr openwebrx-soapyremote openwebrx-perseus openwebrx-fcdpp openwebrx-radioberry openwebrx-uhd openwebrx-rtltcp openwebrx-runds openwebrx-hpsdr openwebrx-bladerf openwebrx-full openwebrx" +IMAGES="openwebrx-rtlsdr openwebrx-sdrplay openwebrx-hackrf openwebrx-airspy openwebrx-afedri openwebrx-rtlsdr-soapy openwebrx-plutosdr openwebrx-limesdr openwebrx-soapyremote openwebrx-perseus openwebrx-fcdpp openwebrx-radioberry openwebrx-uhd openwebrx-rtltcp openwebrx-runds openwebrx-hpsdr openwebrx-bladerf openwebrx-full openwebrx" ALL_ARCHS="x86_64 armv7l aarch64" TAG=${TAG:-"latest"} ARCHTAG="${TAG}-${ARCH}" diff --git a/docker/Dockerfiles/Dockerfile-afedri b/docker/Dockerfiles/Dockerfile-afedri new file mode 100644 index 00000000..ad7c88e9 --- /dev/null +++ b/docker/Dockerfiles/Dockerfile-afedri @@ -0,0 +1,8 @@ +ARG ARCHTAG +FROM openwebrx-soapysdr-base:$ARCHTAG + +COPY docker/scripts/install-dependencies-afedri.sh / +RUN /install-dependencies-afedri.sh &&\ + rm /install-dependencies-afedri.sh + +ADD . /opt/openwebrx diff --git a/docker/Dockerfiles/Dockerfile-full b/docker/Dockerfiles/Dockerfile-full index 10826d21..6d68e8a0 100644 --- a/docker/Dockerfiles/Dockerfile-full +++ b/docker/Dockerfiles/Dockerfile-full @@ -10,6 +10,7 @@ RUN /install-dependencies-rtlsdr.sh &&\ /install-dependencies-hackrf.sh &&\ /install-dependencies-sdrplay.sh &&\ /install-dependencies-airspy.sh &&\ + /install-dependencies-afedri.sh &&\ /install-dependencies-rtlsdr-soapy.sh &&\ /install-dependencies-plutosdr.sh &&\ /install-dependencies-limesdr.sh &&\ diff --git a/docker/scripts/install-dependencies-afedri.sh b/docker/scripts/install-dependencies-afedri.sh new file mode 100644 index 00000000..3843547b --- /dev/null +++ b/docker/scripts/install-dependencies-afedri.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail +export MAKEFLAGS="-j4" + +function cmakebuild() { + cd $1 + if [[ ! -z "${2:-}" ]]; then + git checkout $2 + fi + mkdir build + cd build + cmake .. + make + make install + cd ../.. + rm -rf $1 +} + +cd /tmp + +STATIC_PACKAGES="" +BUILD_PACKAGES="git cmake make gcc g++" + +apt-get update +apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES + +git clone https://github.com/alexander-sholohov/SoapyAfedri.git +# latest from master as of 2023-11-16 +cmakebuild SoapyAfedri a7d0d942fe966c2b69c8817dd6f097fc94122660 + +apt-get -y purge --autoremove $BUILD_PACKAGES +apt-get clean +rm -rf /var/lib/apt/lists/* diff --git a/owrx/feature.py b/owrx/feature.py index bcb73d2b..92c03e57 100644 --- a/owrx/feature.py +++ b/owrx/feature.py @@ -61,6 +61,7 @@ class FeatureDetector(object): "perseussdr": ["perseustest", "nmux"], "airspy": ["soapy_connector", "soapy_airspy"], "airspyhf": ["soapy_connector", "soapy_airspyhf"], + "afedri": ["soapy_connector", "soapy_afedri"], "lime_sdr": ["soapy_connector", "soapy_lime_sdr"], "fifi_sdr": ["alsa", "rockprog", "nmux"], "pluto_sdr": ["soapy_connector", "soapy_pluto_sdr"], @@ -344,6 +345,14 @@ class FeatureDetector(object): """ return self._has_soapy_driver("airspyhf") + 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") + def has_soapy_lime_sdr(self): """ The Lime Suite installs - amongst others - a Soapy driver for the LimeSDR device series. diff --git a/owrx/source/afedri.py b/owrx/source/afedri.py new file mode 100644 index 00000000..3f3c2357 --- /dev/null +++ b/owrx/source/afedri.py @@ -0,0 +1,109 @@ +import re +from owrx.source.soapy import SoapyConnectorSource, SoapyConnectorDeviceDescription +from owrx.form.input import Input, CheckboxInput, DropdownInput, Option +from owrx.form.input.device import TextInput +from owrx.form.input.validator import Validator, ValidationError +from typing import List + + +AFEDRI_DEVICE_KEYS = ["rx_mode"] +AFEDRI_PROFILE_KEYS = ["r820t_lna_agc", "r820t_mixer_agc"] + + +class IPv4AndPortValidator(Validator): + def validate(self, key, value) -> None: + m = re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$", value) + if not m: + raise ValidationError(key, "Wrong format. IPv4:Port expected") + + +class AfedriAddressPortInput(TextInput): + def __init__(self): + super().__init__( + "afedri_adress_port", + "Afedri IP and Port", + infotext="Afedri IP and port to connect to. Format = IPv4:Port", + validator=IPv4AndPortValidator(), + ) + + +class AfedriSource(SoapyConnectorSource): + def getSoapySettingsMappings(self): + mappings = super().getSoapySettingsMappings() + mappings.update({x: x for x in AFEDRI_PROFILE_KEYS}) + return mappings + + def getEventNames(self): + return super().getEventNames() + ["afedri_adress_port"] + AFEDRI_DEVICE_KEYS + + def getDriver(self): + return "afedri" + + def buildSoapyDeviceParameters(self, parsed, values): + params = super().buildSoapyDeviceParameters(parsed, values) + + address, port = values["afedri_adress_port"].split(":") + params += [{"address": address, "port": port}] + + can_be_set_at_start = AFEDRI_DEVICE_KEYS + for elm in can_be_set_at_start: + if elm in values: + params += [{elm: values[elm]}] + + return params + + +class AfedriDeviceDescription(SoapyConnectorDeviceDescription): + def getName(self): + return "Afedri device" + + def supportsPpm(self): + # not currently mapped, and it's unclear how this should be sent to the device + return False + + def hasAgc(self): + # not currently mapped + return False + + def getInputs(self) -> List[Input]: + return super().getInputs() + [ + AfedriAddressPortInput(), + CheckboxInput( + "r820t_lna_agc", + "Enable R820T LNA AGC", + ), + CheckboxInput( + "r820t_mixer_agc", + "Enable R820T Mixer AGC", + ), + DropdownInput( + "rx_mode", + "Switch the device to a specific RX mode at start", + options=[ + Option("0", "Single"), + Option("1", "DualDiversity"), + Option("2", "Dual"), + Option("3", "DiversityInternal"), + Option("4", "QuadDiversity"), + Option("5", "Quad"), + ], + ), + ] + + def getDeviceMandatoryKeys(self): + return super().getDeviceMandatoryKeys() + ["afedri_adress_port"] + + def getDeviceOptionalKeys(self): + return super().getDeviceOptionalKeys() + AFEDRI_DEVICE_KEYS + + def getProfileOptionalKeys(self): + return super().getProfileOptionalKeys() + AFEDRI_PROFILE_KEYS + + def getGainStages(self): + return [ + "RF", + "FE", + "R820T_LNA_GAIN", + "R820T_MIXER_GAIN", + "R820T_VGA_GAIN", + ]