Merge branch 'develop' into m17
This commit is contained in:
commit
6af19f44e8
@ -1,5 +1,14 @@
|
||||
**unreleased**
|
||||
- Introduced `squelch_auto_margin` config option that allows configuring the auto squelch level
|
||||
- Removed `port` configuration option; `rtltcp_compat` takes the port number with the new connectors
|
||||
- Added support for new WSJT-X modes FST4 and FST4W (only available with WSJT-X 2.3)
|
||||
- New devices supported:
|
||||
- HPSDR devices (Hermes Lite 2)
|
||||
- BBRF103 / RX666 / RX888 devices supported by libsddc
|
||||
- Devices using the EB200 protocol
|
||||
|
||||
**0.20.1**
|
||||
- Remove broken OSM map fallback
|
||||
|
||||
**0.20.0**
|
||||
- Added the ability to sign multiple keys in a single request, thus enabling multiple users to claim a single receiver
|
||||
|
22
bands.json
22
bands.json
@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"name": "2190m",
|
||||
"lower_bound": 135700,
|
||||
"upper_bound": 137800,
|
||||
"frequencies": {
|
||||
"fst4": 136000,
|
||||
"fst4w": 136000
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "630m",
|
||||
"lower_bound": 472000,
|
||||
"upper_bound": 479000,
|
||||
"frequencies": {
|
||||
"fst4": 474200,
|
||||
"fst4w": 474200
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "160m",
|
||||
"lower_bound": 1810000,
|
||||
@ -9,7 +27,9 @@
|
||||
"wspr": 1836600,
|
||||
"jt65": 1838000,
|
||||
"jt9": 1839000,
|
||||
"js8": 1842000
|
||||
"js8": 1842000,
|
||||
"fst4": 1839000,
|
||||
"fst4w": 1836800
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -117,7 +117,7 @@ Note: if you experience audio underruns while CPU usage is 100%, you can:
|
||||
# Currently supported types of sdr receivers:
|
||||
# "rtl_sdr", "rtl_sdr_soapy", "sdrplay", "hackrf", "airspy", "airspyhf", "fifi_sdr",
|
||||
# "perseussdr", "lime_sdr", "pluto_sdr", "soapy_remote", "hpsdr", "red_pitaya", "uhd",
|
||||
# "radioberry", "fcdpp", "rtl_tcp"
|
||||
# "radioberry", "fcdpp", "rtl_tcp", "sddc", "eb200"
|
||||
|
||||
# For more details on specific types, please checkout the wiki:
|
||||
# https://github.com/jketterl/openwebrx/wiki/Supported-Hardware#sdr-devices
|
||||
@ -306,6 +306,14 @@ wsjt_decoding_depth = 3
|
||||
# jt65 seems to be somewhat prone to erroneous decodes, this setting handles that to some extent
|
||||
wsjt_decoding_depths = {"jt65": 1}
|
||||
|
||||
# FST4 can be transmitted in different intervals. This setting determines which intervals will be decoded.
|
||||
# available values (in seconds): 15, 30, 60, 120, 300, 900, 1800
|
||||
fst4_enabled_intervals = [15, 30]
|
||||
|
||||
# FST4W can be transmitted in different intervals. This setting determines which intervals will be decoded.
|
||||
# available values (in seconds): 120, 300, 900, 1800
|
||||
fst4w_enabled_intervals = [120, 300]
|
||||
|
||||
# JS8 comes in different speeds: normal, slow, fast, turbo. This setting controls which ones are enabled.
|
||||
js8_enabled_profiles = ["normal", "slow"]
|
||||
# JS8 decoding depth; higher value will get more results, but will also consume more cpu
|
||||
@ -326,7 +334,7 @@ aprs_igate_password = ""
|
||||
aprs_igate_beacon = False
|
||||
|
||||
# path to the aprs symbols repository (get it here: https://github.com/hessu/aprs-symbols)
|
||||
aprs_symbols_path = "/opt/aprs-symbols/png"
|
||||
aprs_symbols_path = "/usr/share/aprs-symbols/png"
|
||||
|
||||
# === PSK Reporter setting ===
|
||||
# enable this if you want to upload all ft8, ft4 etc spots to pskreporter.info
|
||||
|
27
csdr/csdr.py
27
csdr/csdr.py
@ -29,7 +29,7 @@ import math
|
||||
from functools import partial
|
||||
|
||||
from owrx.kiss import KissClient, DirewolfConfig
|
||||
from owrx.wsjt import Ft8Profile, WsprProfile, Jt9Profile, Jt65Profile, Ft4Profile
|
||||
from owrx.wsjt import Ft8Profile, WsprProfile, Jt9Profile, Jt65Profile, Ft4Profile, Fst4Profile, Fst4wProfile
|
||||
from owrx.js8 import Js8Profiles
|
||||
from owrx.audio import AudioChopper
|
||||
|
||||
@ -421,19 +421,23 @@ class dsp(object):
|
||||
|
||||
if self.isWsjtMode():
|
||||
smd = self.get_secondary_demodulator()
|
||||
chopper_profile = None
|
||||
chopper_profiles = None
|
||||
if smd == "ft8":
|
||||
chopper_profile = Ft8Profile()
|
||||
chopper_profiles = [Ft8Profile()]
|
||||
elif smd == "wspr":
|
||||
chopper_profile = WsprProfile()
|
||||
chopper_profiles = [WsprProfile()]
|
||||
elif smd == "jt65":
|
||||
chopper_profile = Jt65Profile()
|
||||
chopper_profiles = [Jt65Profile()]
|
||||
elif smd == "jt9":
|
||||
chopper_profile = Jt9Profile()
|
||||
chopper_profiles = [Jt9Profile()]
|
||||
elif smd == "ft4":
|
||||
chopper_profile = Ft4Profile()
|
||||
if chopper_profile is not None:
|
||||
chopper = AudioChopper(self, self.secondary_process_demod.stdout, chopper_profile)
|
||||
chopper_profiles = [Ft4Profile()]
|
||||
elif smd == "fst4":
|
||||
chopper_profiles = Fst4Profile.getEnabledProfiles()
|
||||
elif smd == "fst4w":
|
||||
chopper_profiles = Fst4wProfile.getEnabledProfiles()
|
||||
if chopper_profiles is not None and len(chopper_profiles):
|
||||
chopper = AudioChopper(self, self.secondary_process_demod.stdout, *chopper_profiles)
|
||||
chopper.start()
|
||||
self.output.send_output("wsjt_demod", chopper.read)
|
||||
elif self.isJs8():
|
||||
@ -575,7 +579,7 @@ class dsp(object):
|
||||
def isWsjtMode(self, demodulator=None):
|
||||
if demodulator is None:
|
||||
demodulator = self.get_secondary_demodulator()
|
||||
return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4"]
|
||||
return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w"]
|
||||
|
||||
def isJs8(self, demodulator = None):
|
||||
if demodulator is None:
|
||||
@ -665,6 +669,9 @@ class dsp(object):
|
||||
def get_operating_freq(self):
|
||||
return self.center_freq + self.offset_freq
|
||||
|
||||
def set_bandpass(self, bandpass):
|
||||
self.set_bpf(bandpass.low_cut, bandpass.high_cut)
|
||||
|
||||
def set_bpf(self, low_cut, high_cut):
|
||||
self.low_cut = low_cut
|
||||
self.high_cut = high_cut
|
||||
|
14
debian/changelog
vendored
14
debian/changelog
vendored
@ -1,9 +1,23 @@
|
||||
openwebrx (0.21.0) UNRELEASED; urgency=low
|
||||
* Introduced `squelch_auto_margin` config option that allows configuring the
|
||||
auto squelch level
|
||||
* Removed `port` configuration option; `rtltcp_compat` takes the port number
|
||||
with the new connectors
|
||||
* Added support for new WSJT-X modes FST4 and FST4W (only available with
|
||||
WSJT-X 2.3)
|
||||
* New devices supported:
|
||||
- HPSDR devices (Hermes Lite 2) (`"type": "hpsdr"`)
|
||||
- BBRF103 / RX666 / RX888 devices supported by libsddc (`"type": "sddc"`)
|
||||
- Devices using the EB200 protocol (`"type": "eb200"`)
|
||||
|
||||
-- Jakob Ketterl <jakob.ketterl@gmx.de> Sun, 11 Oct 2020 21:12:00 +0000
|
||||
|
||||
openwebrx (0.20.1) buster focal; urgency=low
|
||||
|
||||
* Remove broken OSM map fallback
|
||||
|
||||
-- Jakob Ketterl <jakob.ketterl@gmx.de> Mon, 30 Nov 2020 17:29:00 +0000
|
||||
|
||||
openwebrx (0.20.0) buster focal; urgency=low
|
||||
|
||||
* Added the ability to sign multiple keys in a single request, thus enabling
|
||||
|
4
debian/control
vendored
4
debian/control
vendored
@ -10,7 +10,7 @@ Vcs-Git: https://github.com/jketterl/openwebrx.git
|
||||
|
||||
Package: openwebrx
|
||||
Architecture: all
|
||||
Depends: adduser, python3 (>= 3.5), python3-pkg-resources, csdr (>= 0.17), netcat, owrx-connector (>= 0.3), python3-js8py (>= 0.1), ${python3:Depends}, ${misc:Depends}
|
||||
Recommends: digiham (>= 0.3), dsd (>= 1.7), sox, direwolf (>= 1.4), wsjtx, soapysdr-tools
|
||||
Depends: adduser, python3 (>= 3.5), python3-pkg-resources, csdr (>= 0.17), netcat, owrx-connector (>= 0.4), soapysdr-tools, python3-js8py (>= 0.1), ${python3:Depends}, ${misc:Depends}
|
||||
Recommends: digiham (>= 0.3), dsd (>= 1.7), sox, direwolf (>= 1.4), wsjtx, eb200-connector, hpsdrconnector, aprs-symbols
|
||||
Description: multi-user web sdr
|
||||
Open source, multi-user SDR receiver with a web interface
|
@ -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-redpitaya openwebrx-rtltcp openwebrx-full openwebrx"
|
||||
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-redpitaya openwebrx-rtltcp openwebrx-eb200 openwebrx-hpsdr openwebrx-full openwebrx"
|
||||
ALL_ARCHS="x86_64 armv7l aarch64"
|
||||
TAG=${TAG:-"latest"}
|
||||
ARCHTAG="$TAG-$ARCH"
|
||||
|
@ -4,6 +4,7 @@ COPY docker/files/js8call/js8call-hamlib.patch \
|
||||
docker/files/wsjtx/wsjtx.patch \
|
||||
docker/files/wsjtx/wsjtx-hamlib.patch \
|
||||
docker/files/dream/dream.patch \
|
||||
docker/files/direwolf/direwolf-hamlib.patch \
|
||||
docker/scripts/install-dependencies.sh /
|
||||
RUN /install-dependencies.sh && \
|
||||
rm /install-dependencies.sh && \
|
||||
|
12
docker/Dockerfiles/Dockerfile-eb200
Normal file
12
docker/Dockerfiles/Dockerfile-eb200
Normal file
@ -0,0 +1,12 @@
|
||||
ARG ARCHTAG
|
||||
FROM openwebrx-base:$ARCHTAG
|
||||
|
||||
COPY docker/scripts/install-connectors.sh \
|
||||
docker/scripts/install-dependencies-eb200.sh /
|
||||
|
||||
RUN /install-connectors.sh &&\
|
||||
rm /install-connectors.sh && \
|
||||
/install-dependencies-eb200.sh && \
|
||||
rm /install-dependencies-eb200.sh
|
||||
|
||||
COPY . /opt/openwebrx
|
@ -19,7 +19,9 @@ RUN /install-dependencies-rtlsdr.sh &&\
|
||||
/install-dependencies-radioberry.sh &&\
|
||||
/install-dependencies-uhd.sh &&\
|
||||
/install-dependencies-redpitaya.sh &&\
|
||||
/install-dependencies-hpsdr.sh &&\
|
||||
/install-connectors.sh &&\
|
||||
/install-dependencies-eb200.sh &&\
|
||||
rm /install-dependencies-*.sh &&\
|
||||
rm /install-lib.*.patch && \
|
||||
rm /install-connectors.sh
|
||||
|
9
docker/Dockerfiles/Dockerfile-hpsdr
Normal file
9
docker/Dockerfiles/Dockerfile-hpsdr
Normal file
@ -0,0 +1,9 @@
|
||||
ARG ARCHTAG
|
||||
FROM openwebrx-base:$ARCHTAG
|
||||
|
||||
COPY docker/scripts/install-dependencies-hpsdr.sh /
|
||||
|
||||
RUN /install-dependencies-hpsdr.sh &&\
|
||||
rm /install-dependencies-hpsdr.sh
|
||||
|
||||
COPY . /opt/openwebrx
|
20
docker/files/direwolf/direwolf-hamlib.patch
Normal file
20
docker/files/direwolf/direwolf-hamlib.patch
Normal file
@ -0,0 +1,20 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 9e710f5..da90b43 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -257,13 +257,8 @@ else()
|
||||
set(GPSD_LIBRARIES "")
|
||||
endif()
|
||||
|
||||
-find_package(hamlib)
|
||||
-if(HAMLIB_FOUND)
|
||||
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_HAMLIB")
|
||||
-else()
|
||||
- set(HAMLIB_INCLUDE_DIRS "")
|
||||
- set(HAMLIB_LIBRARIES "")
|
||||
-endif()
|
||||
+set(HAMLIB_INCLUDE_DIRS "")
|
||||
+set(HAMLIB_LIBRARIES "")
|
||||
|
||||
if(LINUX)
|
||||
find_package(ALSA REQUIRED)
|
@ -24,7 +24,8 @@ apt-get update
|
||||
apt-get -y install --no-install-recommends $BUILD_PACKAGES
|
||||
|
||||
git clone https://github.com/jketterl/owrx_connector.git
|
||||
cmakebuild owrx_connector 0.3.0
|
||||
# latest develop as of 2020-11-28 (int32 samples; debhelper)
|
||||
cmakebuild owrx_connector 87a2fcc54e221aad71ec0700737ca7f385c388de
|
||||
|
||||
apt-get -y purge --autoremove $BUILD_PACKAGES
|
||||
apt-get clean
|
||||
|
33
docker/scripts/install-dependencies-eb200.sh
Executable file
33
docker/scripts/install-dependencies-eb200.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
set -euxo 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++ pkg-config"
|
||||
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
|
||||
|
||||
git clone https://github.com/jketterl/eb200_connector.git
|
||||
# latest from develop as of 2020-12-01
|
||||
cmakebuild eb200_connector 9c8313770c1072df72d2fdb85307ca206c29c60a
|
||||
|
||||
apt-get -y purge --autoremove $BUILD_PACKAGES
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
46
docker/scripts/install-dependencies-hpsdr.sh
Executable file
46
docker/scripts/install-dependencies-hpsdr.sh
Executable file
@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
set -euxo pipefail
|
||||
export MAKEFLAGS="-j4"
|
||||
|
||||
BUILD_PACKAGES="git wget gcc libc6-dev"
|
||||
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends $BUILD_PACKAGES
|
||||
|
||||
pushd /tmp
|
||||
|
||||
ARCH=$(uname -m)
|
||||
GOVERSION=1.15.5
|
||||
|
||||
case ${ARCH} in
|
||||
x86_64)
|
||||
PACKAGE=go${GOVERSION}.linux-amd64.tar.gz
|
||||
;;
|
||||
armv*)
|
||||
PACKAGE=go${GOVERSION}.linux-armv6l.tar.gz
|
||||
;;
|
||||
aarch64)
|
||||
PACKAGE=go${GOVERSION}.linux-arm64.tar.gz
|
||||
;;
|
||||
esac
|
||||
|
||||
wget https://golang.org/dl/${PACKAGE}
|
||||
tar xfz $PACKAGE
|
||||
|
||||
git clone https://github.com/jancona/hpsdrconnector.git
|
||||
pushd hpsdrconnector
|
||||
git checkout v0.4.2
|
||||
/tmp/go/bin/go build
|
||||
install -m 0755 hpsdrconnector /usr/local/bin
|
||||
|
||||
popd
|
||||
|
||||
rm -rf hpsdrconnector
|
||||
rm -rf go
|
||||
rm $PACKAGE
|
||||
|
||||
popd
|
||||
|
||||
apt-get -y purge --autoremove $BUILD_PACKAGES
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
@ -69,13 +69,17 @@ mv /wsjtx.patch ${WSJT_DIR}
|
||||
cmakebuild ${WSJT_DIR}
|
||||
rm ${WSJT_TGZ}
|
||||
|
||||
git clone --depth 1 -b 1.5 https://github.com/wb2osz/direwolf.git
|
||||
git clone --depth 1 -b 1.6 https://github.com/wb2osz/direwolf.git
|
||||
cd direwolf
|
||||
# hamlib is present (necessary for the wsjt-x and js8call builds) and would be used, but there's no real need.
|
||||
# by setting enable_hamlib we prevent direwolf from linking to it, and it can be stripped at the end of the script.
|
||||
make enable_hamlib=
|
||||
# this patch prevents direwolf from linking to it, and it can be stripped at the end of the script.
|
||||
patch -Np1 < /direwolf-hamlib.patch
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
make install
|
||||
cd ..
|
||||
cd ../..
|
||||
rm -rf direwolf
|
||||
# strip lots of generic documentation that will never be read inside a docker container
|
||||
rm /usr/local/share/doc/direwolf/*.pdf
|
||||
@ -106,8 +110,8 @@ popd
|
||||
rm -rf dream
|
||||
rm dream-2.1.1-svn808.tar.gz
|
||||
|
||||
git clone https://github.com/hessu/aprs-symbols /opt/aprs-symbols
|
||||
pushd /opt/aprs-symbols
|
||||
git clone https://github.com/hessu/aprs-symbols /usr/share/aprs-symbols
|
||||
pushd /usr/share/aprs-symbols
|
||||
git checkout 5c2abe2658ee4d2563f3c73b90c6f59124839802
|
||||
popd
|
||||
|
||||
|
@ -11,6 +11,8 @@ Filter.prototype.getLimits = function() {
|
||||
max_bw = 80000;
|
||||
} else if (this.demodulator.get_modulation() === 'drm') {
|
||||
max_bw = 100000;
|
||||
} else if (this.demodulator.get_secondary_demod() === 'packet') {
|
||||
max_bw = 12500;
|
||||
} else {
|
||||
max_bw = (audioEngine.getOutputRate() / 2) - 1;
|
||||
}
|
||||
|
@ -223,26 +223,14 @@
|
||||
case "config":
|
||||
var config = json.value;
|
||||
if (!map) $.getScript("https://maps.googleapis.com/maps/api/js?key=" + config.google_maps_api_key).done(function(){
|
||||
var mapTypeId = config.google_maps_api_key ? 'roadmap' : 'OSM';
|
||||
|
||||
map = new google.maps.Map($('.openwebrx-map')[0], {
|
||||
center: {
|
||||
lat: config.receiver_gps.lat,
|
||||
lng: config.receiver_gps.lon
|
||||
},
|
||||
zoom: 5,
|
||||
mapTypeId: mapTypeId
|
||||
});
|
||||
|
||||
map.mapTypes.set("OSM", new google.maps.ImageMapType({
|
||||
getTileUrl: function(coord, zoom) {
|
||||
return "https://maps.wikimedia.org/osm-intl/" + zoom + "/" + coord.x + "/" + coord.y + ".png";
|
||||
},
|
||||
tileSize: new google.maps.Size(256, 256),
|
||||
name: "OpenStreetMap",
|
||||
maxZoom: 18
|
||||
}));
|
||||
|
||||
$.getScript("static/lib/nite-overlay.js").done(function(){
|
||||
nite.init(map);
|
||||
setInterval(function() { nite.refresh() }, 10000); // every 10s
|
||||
|
@ -194,10 +194,11 @@ class AudioWriter(object):
|
||||
try:
|
||||
rc = decoder.wait(timeout=10)
|
||||
if rc != 0:
|
||||
logger.warning("decoder return code: %i", rc)
|
||||
raise RuntimeError("decoder return code: {0}".format(rc))
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("subprocess (pid=%i}) did not terminate correctly; sending kill signal.", decoder.pid)
|
||||
decoder.kill()
|
||||
raise
|
||||
|
||||
def start(self):
|
||||
(self.wavefilename, self.wavefile) = self.getWaveFile()
|
||||
|
@ -76,4 +76,4 @@ class Option(CommandMapping):
|
||||
|
||||
class Argument(CommandMapping):
|
||||
def map(self, value):
|
||||
return value
|
||||
return str(value)
|
||||
|
@ -68,7 +68,9 @@ class FeatureDetector(object):
|
||||
"red_pitaya": ["soapy_connector", "soapy_red_pitaya"],
|
||||
"radioberry": ["soapy_connector", "soapy_radioberry"],
|
||||
"fcdpp": ["soapy_connector", "soapy_fcdpp"],
|
||||
"sddc": ["sddc_connector"],
|
||||
"hpsdr": ["hpsdr_connector"],
|
||||
"eb200": ["eb200_connector"],
|
||||
# optional features and their requirements
|
||||
"digital_voice_digiham": ["digiham", "sox"],
|
||||
"digital_voice_dsd": ["dsd", "sox", "digiham"],
|
||||
@ -256,7 +258,7 @@ class FeatureDetector(object):
|
||||
)
|
||||
|
||||
def _check_connector(self, command):
|
||||
required_version = LooseVersion("0.3")
|
||||
required_version = LooseVersion("0.4")
|
||||
|
||||
owrx_connector_version_regex = re.compile("^owrx-connector version (.*)$")
|
||||
|
||||
@ -499,9 +501,25 @@ class FeatureDetector(object):
|
||||
"""
|
||||
return self.command_is_runnable("dream --help", 0)
|
||||
|
||||
def has_sddc_connector(self):
|
||||
"""
|
||||
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).
|
||||
"""
|
||||
return self._check_connector("sddc_connector")
|
||||
|
||||
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")
|
||||
|
||||
def has_eb200_connector(self):
|
||||
"""
|
||||
To use radios supporting the EB200 radios, you need to install the eb200_connector.
|
||||
|
||||
You can find more information [here](https://github.com/jketterl/eb200_connector).
|
||||
"""
|
||||
return self._check_connector("eb200_connector")
|
@ -24,6 +24,12 @@ class Mode(object):
|
||||
def is_service(self):
|
||||
return self.service
|
||||
|
||||
def get_bandpass(self):
|
||||
return self.bandpass
|
||||
|
||||
def get_modulation(self):
|
||||
return self.modulation
|
||||
|
||||
|
||||
class AnalogMode(Mode):
|
||||
pass
|
||||
@ -36,6 +42,14 @@ class DigitalMode(Mode):
|
||||
super().__init__(modulation, name, bandpass, requirements, service, squelch)
|
||||
self.underlying = underlying
|
||||
|
||||
def get_bandpass(self):
|
||||
if self.bandpass is not None:
|
||||
return self.bandpass
|
||||
return Modes.findByModulation(self.underlying[0]).get_bandpass()
|
||||
|
||||
def get_modulation(self):
|
||||
return Modes.findByModulation(self.underlying[0]).get_modulation()
|
||||
|
||||
|
||||
class Modes(object):
|
||||
mappings = [
|
||||
@ -54,16 +68,24 @@ class Modes(object):
|
||||
AnalogMode("drm", "DRM", bandpass=Bandpass(-5000, 5000), requirements=["drm"], squelch=False),
|
||||
DigitalMode("bpsk31", "BPSK31", underlying=["usb"]),
|
||||
DigitalMode("bpsk63", "BPSK63", underlying=["usb"]),
|
||||
DigitalMode("ft8", "FT8", underlying=["usb"], requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("ft4", "FT4", underlying=["usb"], requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("jt65", "JT65", underlying=["usb"], requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("jt9", "JT9", underlying=["usb"], requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("ft8", "FT8", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("ft4", "FT4", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("jt65", "JT65", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("jt9", "JT9", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
|
||||
DigitalMode(
|
||||
"wspr", "WSPR", underlying=["usb"], bandpass=Bandpass(1350, 1650), requirements=["wsjt-x"], service=True
|
||||
),
|
||||
DigitalMode("js8", "JS8Call", underlying=["usb"], requirements=["js8call"], service=True),
|
||||
DigitalMode("fst4", "FST4", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("fst4w", "FST4W", underlying=["usb"], bandpass=Bandpass(1350, 1650), requirements=["wsjt-x"], service=True),
|
||||
DigitalMode("js8", "JS8Call", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["js8call"], service=True),
|
||||
DigitalMode(
|
||||
"packet", "Packet", underlying=["nfm", "usb", "lsb"], requirements=["packet"], service=True, squelch=False
|
||||
"packet",
|
||||
"Packet",
|
||||
underlying=["nfm", "usb", "lsb"],
|
||||
bandpass=Bandpass(-6250, 6250),
|
||||
requirements=["packet"],
|
||||
service=True,
|
||||
squelch=False,
|
||||
),
|
||||
DigitalMode(
|
||||
"pocsag",
|
||||
|
@ -30,7 +30,7 @@ class PskReporter(object):
|
||||
sharedInstance = None
|
||||
creationLock = threading.Lock()
|
||||
interval = 300
|
||||
supportedModes = ["FT8", "FT4", "JT9", "JT65", "JS8"]
|
||||
supportedModes = ["FT8", "FT4", "JT9", "JT65", "FST4", "FST4W", "JS8"]
|
||||
|
||||
@staticmethod
|
||||
def getSharedInstance():
|
||||
|
@ -155,18 +155,14 @@ class ServiceHandler(SdrSourceEventClient):
|
||||
)
|
||||
else:
|
||||
for group in groups:
|
||||
frequencies = sorted([f["frequency"] for f in group])
|
||||
min = frequencies[0]
|
||||
max = frequencies[-1]
|
||||
cf = (min + max) / 2
|
||||
bw = max - min
|
||||
cf = self.get_center_frequency(group)
|
||||
bw = self.get_bandwidth(group)
|
||||
logger.debug(
|
||||
"group center frequency: {0}, bandwidth: {1}".format(cf, bw)
|
||||
)
|
||||
resampler_props = PropertyLayer()
|
||||
resampler_props["center_freq"] = cf
|
||||
# TODO the + 24000 is a temporary fix since the resampling optimizer does not account for required bandwidths
|
||||
resampler_props["samp_rate"] = bw + 24000
|
||||
resampler_props["samp_rate"] = bw
|
||||
resampler = Resampler(resampler_props, self.source)
|
||||
resampler.start()
|
||||
|
||||
@ -180,6 +176,23 @@ class ServiceHandler(SdrSourceEventClient):
|
||||
# resampler goes in after the services since it must not be shutdown as long as the services are still running
|
||||
self.services.append(resampler)
|
||||
|
||||
def get_min_max(self, group):
|
||||
frequencies = sorted(group, key=lambda f: f["frequency"])
|
||||
lowest = frequencies[0]
|
||||
min = lowest["frequency"] + Modes.findByModulation(lowest["mode"]).get_bandpass().low_cut
|
||||
highest = frequencies[-1]
|
||||
max = highest["frequency"] + Modes.findByModulation(highest["mode"]).get_bandpass().high_cut
|
||||
return min, max
|
||||
|
||||
def get_center_frequency(self, group):
|
||||
min, max = self.get_min_max(group)
|
||||
return (min + max) / 2
|
||||
|
||||
def get_bandwidth(self, group):
|
||||
minFreq, maxFreq = self.get_min_max(group)
|
||||
# minimum bandwidth for a resampler: 25kHz
|
||||
return max(maxFreq - minFreq, 25000)
|
||||
|
||||
def optimizeResampling(self, freqs, bandwidth):
|
||||
freqs = sorted(freqs, key=lambda f: f["frequency"])
|
||||
distances = [
|
||||
@ -203,12 +216,10 @@ class ServiceHandler(SdrSourceEventClient):
|
||||
previous = split
|
||||
groups.append([f for f in freqs if previous < f["frequency"]])
|
||||
|
||||
def get_bandwitdh(group):
|
||||
freqs = sorted([f["frequency"] for f in group])
|
||||
# the group will process the full BW once, plus the reduced BW once for each group member
|
||||
return bandwidth + len(group) * (freqs[-1] - freqs[0] + 24000)
|
||||
def get_total_bandwidth(group):
|
||||
return bandwidth + len(group) * self.get_bandwidth(group)
|
||||
|
||||
total_bandwidth = sum([get_bandwitdh(group) for group in groups])
|
||||
total_bandwidth = sum([get_total_bandwidth(group) for group in groups])
|
||||
return {
|
||||
"num_splits": num_splits,
|
||||
"total_bandwidth": total_bandwidth,
|
||||
@ -250,16 +261,9 @@ class ServiceHandler(SdrSourceEventClient):
|
||||
center_freq = source.getProps()["center_freq"]
|
||||
d.set_offset_freq(frequency - center_freq)
|
||||
d.set_center_freq(center_freq)
|
||||
if mode == "packet":
|
||||
d.set_demodulator("nfm")
|
||||
d.set_bpf(-4000, 4000)
|
||||
elif mode == "wspr":
|
||||
d.set_demodulator("usb")
|
||||
# WSPR only samples between 1400 and 1600 Hz
|
||||
d.set_bpf(1350, 1650)
|
||||
else:
|
||||
d.set_demodulator("usb")
|
||||
d.set_bpf(0, 3000)
|
||||
modeObject = Modes.findByModulation(mode)
|
||||
d.set_demodulator(modeObject.get_modulation())
|
||||
d.set_bandpass(modeObject.get_bandpass())
|
||||
d.set_secondary_demodulator(mode)
|
||||
d.set_audio_compression("none")
|
||||
d.set_samp_rate(source.getProps()["samp_rate"])
|
||||
|
@ -59,10 +59,7 @@ class SdrSource(ABC):
|
||||
self.activateProfile()
|
||||
self.wireEvents()
|
||||
|
||||
if "port" in props and props["port"] is not None:
|
||||
self.port = props["port"]
|
||||
else:
|
||||
self.port = getAvailablePort()
|
||||
self.port = getAvailablePort()
|
||||
self.monitor = None
|
||||
self.clients = []
|
||||
self.spectrumClients = []
|
||||
|
@ -23,7 +23,7 @@ class ConnectorSource(SdrSource):
|
||||
"controlPort": Option("-c"),
|
||||
"device": Option("-d"),
|
||||
"iqswap": Flag("-i"),
|
||||
"rtltcp_compat": Flag("-r"),
|
||||
"rtltcp_compat": Option("-r"),
|
||||
"ppm": Option("-P"),
|
||||
"rf_gain": Option("-g"),
|
||||
}
|
||||
|
15
owrx/source/eb200.py
Normal file
15
owrx/source/eb200.py
Normal file
@ -0,0 +1,15 @@
|
||||
from owrx.source.connector import ConnectorSource
|
||||
from owrx.command import Argument, Flag
|
||||
|
||||
|
||||
class Eb200Source(ConnectorSource):
|
||||
def getCommandMapper(self):
|
||||
return (
|
||||
super()
|
||||
.getCommandMapper()
|
||||
.setBase("eb200_connector")
|
||||
.setMappings({
|
||||
"long": Flag("-l"),
|
||||
"remote": Argument(),
|
||||
})
|
||||
)
|
6
owrx/source/sddc.py
Normal file
6
owrx/source/sddc.py
Normal file
@ -0,0 +1,6 @@
|
||||
from owrx.source.connector import ConnectorSource
|
||||
|
||||
|
||||
class SddcSource(ConnectorSource):
|
||||
def getCommandMapper(self):
|
||||
return super().getCommandMapper().setBase("sddc_connector")
|
85
owrx/wsjt.py
85
owrx/wsjt.py
@ -85,8 +85,56 @@ class Ft4Profile(WsjtProfile):
|
||||
return ["jt9", "--ft4", "-d", str(self.decoding_depth("ft4")), file]
|
||||
|
||||
|
||||
class Fst4Profile(WsjtProfile):
|
||||
availableIntervals = [15, 30, 60, 120, 300, 900, 1800]
|
||||
|
||||
def __init__(self, interval):
|
||||
self.interval = interval
|
||||
|
||||
def getInterval(self):
|
||||
return self.interval
|
||||
|
||||
def getFileTimestampFormat(self):
|
||||
if self.interval < 60:
|
||||
return "%y%m%d_%H%M%S"
|
||||
return "%y%m%d_%H%M"
|
||||
|
||||
def decoder_commandline(self, file):
|
||||
return ["jt9", "--fst4", "-p", str(self.interval), "-d", str(self.decoding_depth("fst4")), file]
|
||||
|
||||
@staticmethod
|
||||
def getEnabledProfiles():
|
||||
config = Config.get()
|
||||
profiles = config["fst4_enabled_intervals"] if "fst4_enabled_intervals" in config else []
|
||||
return [Fst4Profile(i) for i in profiles if i in Fst4Profile.availableIntervals]
|
||||
|
||||
|
||||
class Fst4wProfile(WsjtProfile):
|
||||
availableIntervals = [120, 300, 900, 1800]
|
||||
|
||||
def __init__(self, interval):
|
||||
self.interval = interval
|
||||
|
||||
def getInterval(self):
|
||||
return self.interval
|
||||
|
||||
def getFileTimestampFormat(self):
|
||||
if self.interval < 60:
|
||||
return "%y%m%d_%H%M%S"
|
||||
return "%y%m%d_%H%M"
|
||||
|
||||
def decoder_commandline(self, file):
|
||||
return ["jt9", "--fst4w", "-p", str(self.interval), "-d", str(self.decoding_depth("fst4w")), file]
|
||||
|
||||
@staticmethod
|
||||
def getEnabledProfiles():
|
||||
config = Config.get()
|
||||
profiles = config["fst4w_enabled_intervals"] if "fst4w_enabled_intervals" in config else []
|
||||
return [Fst4wProfile(i) for i in profiles if i in Fst4wProfile.availableIntervals]
|
||||
|
||||
|
||||
class WsjtParser(Parser):
|
||||
modes = {"~": "FT8", "#": "JT65", "@": "JT9", "+": "FT4"}
|
||||
modes = {"~": "FT8", "#": "JT65", "@": "JT9", "+": "FT4", "`": "FST4"}
|
||||
|
||||
def parse(self, messages):
|
||||
for data in messages:
|
||||
@ -115,7 +163,7 @@ class WsjtParser(Parser):
|
||||
PskReporter.getSharedInstance().spot(out)
|
||||
|
||||
self.handler.write_wsjt_message(out)
|
||||
except ValueError:
|
||||
except (ValueError, IndexError):
|
||||
logger.exception("error while parsing wsjt message")
|
||||
|
||||
def pushDecode(self, mode):
|
||||
@ -139,6 +187,8 @@ class WsjtParser(Parser):
|
||||
|
||||
|
||||
class Decoder(ABC):
|
||||
locator_pattern = re.compile(".*\\s([A-Z0-9]{2,})(\\sR)?\\s([A-R]{2}[0-9]{2})$")
|
||||
|
||||
def parse_timestamp(self, instring, dateformat):
|
||||
ts = datetime.strptime(instring, dateformat)
|
||||
return int(
|
||||
@ -149,23 +199,36 @@ class Decoder(ABC):
|
||||
def parse(self, msg, dial_freq):
|
||||
pass
|
||||
|
||||
def parseMessage(self, msg):
|
||||
m = Decoder.locator_pattern.match(msg)
|
||||
if m is None:
|
||||
return {}
|
||||
# this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very
|
||||
# likely this just means roger roger goodbye.
|
||||
if m.group(3) == "RR73":
|
||||
return {"callsign": m.group(1)}
|
||||
return {"callsign": m.group(1), "locator": m.group(3)}
|
||||
|
||||
|
||||
class Jt9Decoder(Decoder):
|
||||
locator_pattern = re.compile(".*\\s([A-Z0-9]+)\\s([A-R]{2}[0-9]{2})$")
|
||||
|
||||
def parse(self, msg, dial_freq):
|
||||
# ft8 sample
|
||||
# '222100 -15 -0.0 508 ~ CQ EA7MJ IM66'
|
||||
# jt65 sample
|
||||
# '2352 -7 0.4 1801 # R0WAS R2ABM KO85'
|
||||
# '0003 -4 0.4 1762 # CQ R2ABM KO85'
|
||||
# fst4 sample
|
||||
# '**** -23 0.6 3023 ` <...> <...> R 591631 BI53PV'
|
||||
modes = list(WsjtParser.modes.keys())
|
||||
if msg[19] in modes:
|
||||
dateformat = "%H%M"
|
||||
else:
|
||||
dateformat = "%H%M%S"
|
||||
timestamp = self.parse_timestamp(msg[0 : len(dateformat)], dateformat)
|
||||
msg = msg[len(dateformat) + 1 :]
|
||||
try:
|
||||
timestamp = self.parse_timestamp(msg[0 : len(dateformat)], dateformat)
|
||||
except ValueError:
|
||||
timestamp = None
|
||||
msg = msg[len(dateformat) + 1:]
|
||||
modeChar = msg[14:15]
|
||||
mode = WsjtParser.modes[modeChar] if modeChar in WsjtParser.modes else "unknown"
|
||||
wsjt_msg = msg[17:53].strip()
|
||||
@ -181,16 +244,6 @@ class Jt9Decoder(Decoder):
|
||||
result.update(self.parseMessage(wsjt_msg))
|
||||
return result
|
||||
|
||||
def parseMessage(self, msg):
|
||||
m = Jt9Decoder.locator_pattern.match(msg)
|
||||
if m is None:
|
||||
return {}
|
||||
# this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very
|
||||
# likely this just means roger roger goodbye.
|
||||
if m.group(2) == "RR73":
|
||||
return {"callsign": m.group(1)}
|
||||
return {"callsign": m.group(1), "locator": m.group(2)}
|
||||
|
||||
|
||||
class WsprDecoder(Decoder):
|
||||
wspr_splitter_pattern = re.compile("([A-Z0-9]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)")
|
||||
|
Loading…
Reference in New Issue
Block a user