Merge branch 'develop' into m17
This commit is contained in:
commit
6af19f44e8
@ -1,5 +1,14 @@
|
|||||||
**unreleased**
|
**unreleased**
|
||||||
- Introduced `squelch_auto_margin` config option that allows configuring the auto squelch level
|
- 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**
|
**0.20.0**
|
||||||
- Added the ability to sign multiple keys in a single request, thus enabling multiple users to claim a single receiver
|
- 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",
|
"name": "160m",
|
||||||
"lower_bound": 1810000,
|
"lower_bound": 1810000,
|
||||||
@ -9,7 +27,9 @@
|
|||||||
"wspr": 1836600,
|
"wspr": 1836600,
|
||||||
"jt65": 1838000,
|
"jt65": 1838000,
|
||||||
"jt9": 1839000,
|
"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:
|
# Currently supported types of sdr receivers:
|
||||||
# "rtl_sdr", "rtl_sdr_soapy", "sdrplay", "hackrf", "airspy", "airspyhf", "fifi_sdr",
|
# "rtl_sdr", "rtl_sdr_soapy", "sdrplay", "hackrf", "airspy", "airspyhf", "fifi_sdr",
|
||||||
# "perseussdr", "lime_sdr", "pluto_sdr", "soapy_remote", "hpsdr", "red_pitaya", "uhd",
|
# "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:
|
# For more details on specific types, please checkout the wiki:
|
||||||
# https://github.com/jketterl/openwebrx/wiki/Supported-Hardware#sdr-devices
|
# 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
|
# jt65 seems to be somewhat prone to erroneous decodes, this setting handles that to some extent
|
||||||
wsjt_decoding_depths = {"jt65": 1}
|
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 comes in different speeds: normal, slow, fast, turbo. This setting controls which ones are enabled.
|
||||||
js8_enabled_profiles = ["normal", "slow"]
|
js8_enabled_profiles = ["normal", "slow"]
|
||||||
# JS8 decoding depth; higher value will get more results, but will also consume more cpu
|
# 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
|
aprs_igate_beacon = False
|
||||||
|
|
||||||
# path to the aprs symbols repository (get it here: https://github.com/hessu/aprs-symbols)
|
# 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 ===
|
# === PSK Reporter setting ===
|
||||||
# enable this if you want to upload all ft8, ft4 etc spots to pskreporter.info
|
# 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 functools import partial
|
||||||
|
|
||||||
from owrx.kiss import KissClient, DirewolfConfig
|
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.js8 import Js8Profiles
|
||||||
from owrx.audio import AudioChopper
|
from owrx.audio import AudioChopper
|
||||||
|
|
||||||
@ -421,19 +421,23 @@ class dsp(object):
|
|||||||
|
|
||||||
if self.isWsjtMode():
|
if self.isWsjtMode():
|
||||||
smd = self.get_secondary_demodulator()
|
smd = self.get_secondary_demodulator()
|
||||||
chopper_profile = None
|
chopper_profiles = None
|
||||||
if smd == "ft8":
|
if smd == "ft8":
|
||||||
chopper_profile = Ft8Profile()
|
chopper_profiles = [Ft8Profile()]
|
||||||
elif smd == "wspr":
|
elif smd == "wspr":
|
||||||
chopper_profile = WsprProfile()
|
chopper_profiles = [WsprProfile()]
|
||||||
elif smd == "jt65":
|
elif smd == "jt65":
|
||||||
chopper_profile = Jt65Profile()
|
chopper_profiles = [Jt65Profile()]
|
||||||
elif smd == "jt9":
|
elif smd == "jt9":
|
||||||
chopper_profile = Jt9Profile()
|
chopper_profiles = [Jt9Profile()]
|
||||||
elif smd == "ft4":
|
elif smd == "ft4":
|
||||||
chopper_profile = Ft4Profile()
|
chopper_profiles = [Ft4Profile()]
|
||||||
if chopper_profile is not None:
|
elif smd == "fst4":
|
||||||
chopper = AudioChopper(self, self.secondary_process_demod.stdout, chopper_profile)
|
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()
|
chopper.start()
|
||||||
self.output.send_output("wsjt_demod", chopper.read)
|
self.output.send_output("wsjt_demod", chopper.read)
|
||||||
elif self.isJs8():
|
elif self.isJs8():
|
||||||
@ -575,7 +579,7 @@ class dsp(object):
|
|||||||
def isWsjtMode(self, demodulator=None):
|
def isWsjtMode(self, demodulator=None):
|
||||||
if demodulator is None:
|
if demodulator is None:
|
||||||
demodulator = self.get_secondary_demodulator()
|
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):
|
def isJs8(self, demodulator = None):
|
||||||
if demodulator is None:
|
if demodulator is None:
|
||||||
@ -665,6 +669,9 @@ class dsp(object):
|
|||||||
def get_operating_freq(self):
|
def get_operating_freq(self):
|
||||||
return self.center_freq + self.offset_freq
|
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):
|
def set_bpf(self, low_cut, high_cut):
|
||||||
self.low_cut = low_cut
|
self.low_cut = low_cut
|
||||||
self.high_cut = high_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
|
openwebrx (0.21.0) UNRELEASED; urgency=low
|
||||||
* Introduced `squelch_auto_margin` config option that allows configuring the
|
* Introduced `squelch_auto_margin` config option that allows configuring the
|
||||||
auto squelch level
|
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
|
-- 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
|
openwebrx (0.20.0) buster focal; urgency=low
|
||||||
|
|
||||||
* Added the ability to sign multiple keys in a single request, thus enabling
|
* 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
|
Package: openwebrx
|
||||||
Architecture: all
|
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}
|
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, soapysdr-tools
|
Recommends: digiham (>= 0.3), dsd (>= 1.7), sox, direwolf (>= 1.4), wsjtx, eb200-connector, hpsdrconnector, aprs-symbols
|
||||||
Description: multi-user web sdr
|
Description: multi-user web sdr
|
||||||
Open source, multi-user SDR receiver with a web interface
|
Open source, multi-user SDR receiver with a web interface
|
@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ARCH=$(uname -m)
|
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"
|
ALL_ARCHS="x86_64 armv7l aarch64"
|
||||||
TAG=${TAG:-"latest"}
|
TAG=${TAG:-"latest"}
|
||||||
ARCHTAG="$TAG-$ARCH"
|
ARCHTAG="$TAG-$ARCH"
|
||||||
|
@ -4,6 +4,7 @@ COPY docker/files/js8call/js8call-hamlib.patch \
|
|||||||
docker/files/wsjtx/wsjtx.patch \
|
docker/files/wsjtx/wsjtx.patch \
|
||||||
docker/files/wsjtx/wsjtx-hamlib.patch \
|
docker/files/wsjtx/wsjtx-hamlib.patch \
|
||||||
docker/files/dream/dream.patch \
|
docker/files/dream/dream.patch \
|
||||||
|
docker/files/direwolf/direwolf-hamlib.patch \
|
||||||
docker/scripts/install-dependencies.sh /
|
docker/scripts/install-dependencies.sh /
|
||||||
RUN /install-dependencies.sh && \
|
RUN /install-dependencies.sh && \
|
||||||
rm /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-radioberry.sh &&\
|
||||||
/install-dependencies-uhd.sh &&\
|
/install-dependencies-uhd.sh &&\
|
||||||
/install-dependencies-redpitaya.sh &&\
|
/install-dependencies-redpitaya.sh &&\
|
||||||
|
/install-dependencies-hpsdr.sh &&\
|
||||||
/install-connectors.sh &&\
|
/install-connectors.sh &&\
|
||||||
|
/install-dependencies-eb200.sh &&\
|
||||||
rm /install-dependencies-*.sh &&\
|
rm /install-dependencies-*.sh &&\
|
||||||
rm /install-lib.*.patch && \
|
rm /install-lib.*.patch && \
|
||||||
rm /install-connectors.sh
|
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
|
apt-get -y install --no-install-recommends $BUILD_PACKAGES
|
||||||
|
|
||||||
git clone https://github.com/jketterl/owrx_connector.git
|
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 -y purge --autoremove $BUILD_PACKAGES
|
||||||
apt-get clean
|
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}
|
cmakebuild ${WSJT_DIR}
|
||||||
rm ${WSJT_TGZ}
|
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
|
cd direwolf
|
||||||
# hamlib is present (necessary for the wsjt-x and js8call builds) and would be used, but there's no real need.
|
# 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.
|
# this patch prevents direwolf from linking to it, and it can be stripped at the end of the script.
|
||||||
make enable_hamlib=
|
patch -Np1 < /direwolf-hamlib.patch
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
make install
|
make install
|
||||||
cd ..
|
cd ../..
|
||||||
rm -rf direwolf
|
rm -rf direwolf
|
||||||
# strip lots of generic documentation that will never be read inside a docker container
|
# strip lots of generic documentation that will never be read inside a docker container
|
||||||
rm /usr/local/share/doc/direwolf/*.pdf
|
rm /usr/local/share/doc/direwolf/*.pdf
|
||||||
@ -106,8 +110,8 @@ popd
|
|||||||
rm -rf dream
|
rm -rf dream
|
||||||
rm dream-2.1.1-svn808.tar.gz
|
rm dream-2.1.1-svn808.tar.gz
|
||||||
|
|
||||||
git clone https://github.com/hessu/aprs-symbols /opt/aprs-symbols
|
git clone https://github.com/hessu/aprs-symbols /usr/share/aprs-symbols
|
||||||
pushd /opt/aprs-symbols
|
pushd /usr/share/aprs-symbols
|
||||||
git checkout 5c2abe2658ee4d2563f3c73b90c6f59124839802
|
git checkout 5c2abe2658ee4d2563f3c73b90c6f59124839802
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ Filter.prototype.getLimits = function() {
|
|||||||
max_bw = 80000;
|
max_bw = 80000;
|
||||||
} else if (this.demodulator.get_modulation() === 'drm') {
|
} else if (this.demodulator.get_modulation() === 'drm') {
|
||||||
max_bw = 100000;
|
max_bw = 100000;
|
||||||
|
} else if (this.demodulator.get_secondary_demod() === 'packet') {
|
||||||
|
max_bw = 12500;
|
||||||
} else {
|
} else {
|
||||||
max_bw = (audioEngine.getOutputRate() / 2) - 1;
|
max_bw = (audioEngine.getOutputRate() / 2) - 1;
|
||||||
}
|
}
|
||||||
|
@ -223,26 +223,14 @@
|
|||||||
case "config":
|
case "config":
|
||||||
var config = json.value;
|
var config = json.value;
|
||||||
if (!map) $.getScript("https://maps.googleapis.com/maps/api/js?key=" + config.google_maps_api_key).done(function(){
|
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], {
|
map = new google.maps.Map($('.openwebrx-map')[0], {
|
||||||
center: {
|
center: {
|
||||||
lat: config.receiver_gps.lat,
|
lat: config.receiver_gps.lat,
|
||||||
lng: config.receiver_gps.lon
|
lng: config.receiver_gps.lon
|
||||||
},
|
},
|
||||||
zoom: 5,
|
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(){
|
$.getScript("static/lib/nite-overlay.js").done(function(){
|
||||||
nite.init(map);
|
nite.init(map);
|
||||||
setInterval(function() { nite.refresh() }, 10000); // every 10s
|
setInterval(function() { nite.refresh() }, 10000); // every 10s
|
||||||
|
@ -194,10 +194,11 @@ class AudioWriter(object):
|
|||||||
try:
|
try:
|
||||||
rc = decoder.wait(timeout=10)
|
rc = decoder.wait(timeout=10)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
logger.warning("decoder return code: %i", rc)
|
raise RuntimeError("decoder return code: {0}".format(rc))
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
logger.warning("subprocess (pid=%i}) did not terminate correctly; sending kill signal.", decoder.pid)
|
logger.warning("subprocess (pid=%i}) did not terminate correctly; sending kill signal.", decoder.pid)
|
||||||
decoder.kill()
|
decoder.kill()
|
||||||
|
raise
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
(self.wavefilename, self.wavefile) = self.getWaveFile()
|
(self.wavefilename, self.wavefile) = self.getWaveFile()
|
||||||
|
@ -76,4 +76,4 @@ class Option(CommandMapping):
|
|||||||
|
|
||||||
class Argument(CommandMapping):
|
class Argument(CommandMapping):
|
||||||
def map(self, value):
|
def map(self, value):
|
||||||
return value
|
return str(value)
|
||||||
|
@ -68,7 +68,9 @@ class FeatureDetector(object):
|
|||||||
"red_pitaya": ["soapy_connector", "soapy_red_pitaya"],
|
"red_pitaya": ["soapy_connector", "soapy_red_pitaya"],
|
||||||
"radioberry": ["soapy_connector", "soapy_radioberry"],
|
"radioberry": ["soapy_connector", "soapy_radioberry"],
|
||||||
"fcdpp": ["soapy_connector", "soapy_fcdpp"],
|
"fcdpp": ["soapy_connector", "soapy_fcdpp"],
|
||||||
|
"sddc": ["sddc_connector"],
|
||||||
"hpsdr": ["hpsdr_connector"],
|
"hpsdr": ["hpsdr_connector"],
|
||||||
|
"eb200": ["eb200_connector"],
|
||||||
# optional features and their requirements
|
# optional features and their requirements
|
||||||
"digital_voice_digiham": ["digiham", "sox"],
|
"digital_voice_digiham": ["digiham", "sox"],
|
||||||
"digital_voice_dsd": ["dsd", "sox", "digiham"],
|
"digital_voice_dsd": ["dsd", "sox", "digiham"],
|
||||||
@ -256,7 +258,7 @@ class FeatureDetector(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _check_connector(self, command):
|
def _check_connector(self, command):
|
||||||
required_version = LooseVersion("0.3")
|
required_version = LooseVersion("0.4")
|
||||||
|
|
||||||
owrx_connector_version_regex = re.compile("^owrx-connector version (.*)$")
|
owrx_connector_version_regex = re.compile("^owrx-connector version (.*)$")
|
||||||
|
|
||||||
@ -499,9 +501,25 @@ class FeatureDetector(object):
|
|||||||
"""
|
"""
|
||||||
return self.command_is_runnable("dream --help", 0)
|
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):
|
def has_hpsdr_connector(self):
|
||||||
"""
|
"""
|
||||||
In order to use the HPSDR connector, you will need to install [hpsdrconnector]
|
In order to use the HPSDR connector, you will need to install [hpsdrconnector]
|
||||||
(https://github.com/jancona/hpsdrconnector).
|
(https://github.com/jancona/hpsdrconnector).
|
||||||
"""
|
"""
|
||||||
return self.command_is_runnable("hpsdrconnector -h")
|
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):
|
def is_service(self):
|
||||||
return self.service
|
return self.service
|
||||||
|
|
||||||
|
def get_bandpass(self):
|
||||||
|
return self.bandpass
|
||||||
|
|
||||||
|
def get_modulation(self):
|
||||||
|
return self.modulation
|
||||||
|
|
||||||
|
|
||||||
class AnalogMode(Mode):
|
class AnalogMode(Mode):
|
||||||
pass
|
pass
|
||||||
@ -36,6 +42,14 @@ class DigitalMode(Mode):
|
|||||||
super().__init__(modulation, name, bandpass, requirements, service, squelch)
|
super().__init__(modulation, name, bandpass, requirements, service, squelch)
|
||||||
self.underlying = underlying
|
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):
|
class Modes(object):
|
||||||
mappings = [
|
mappings = [
|
||||||
@ -54,16 +68,24 @@ class Modes(object):
|
|||||||
AnalogMode("drm", "DRM", bandpass=Bandpass(-5000, 5000), requirements=["drm"], squelch=False),
|
AnalogMode("drm", "DRM", bandpass=Bandpass(-5000, 5000), requirements=["drm"], squelch=False),
|
||||||
DigitalMode("bpsk31", "BPSK31", underlying=["usb"]),
|
DigitalMode("bpsk31", "BPSK31", underlying=["usb"]),
|
||||||
DigitalMode("bpsk63", "BPSK63", underlying=["usb"]),
|
DigitalMode("bpsk63", "BPSK63", underlying=["usb"]),
|
||||||
DigitalMode("ft8", "FT8", 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"], requirements=["wsjt-x"], service=True),
|
DigitalMode("ft4", "FT4", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
|
||||||
DigitalMode("jt65", "JT65", underlying=["usb"], requirements=["wsjt-x"], service=True),
|
DigitalMode("jt65", "JT65", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
|
||||||
DigitalMode("jt9", "JT9", underlying=["usb"], requirements=["wsjt-x"], service=True),
|
DigitalMode("jt9", "JT9", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
|
||||||
DigitalMode(
|
DigitalMode(
|
||||||
"wspr", "WSPR", underlying=["usb"], bandpass=Bandpass(1350, 1650), requirements=["wsjt-x"], service=True
|
"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(
|
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(
|
DigitalMode(
|
||||||
"pocsag",
|
"pocsag",
|
||||||
|
@ -30,7 +30,7 @@ class PskReporter(object):
|
|||||||
sharedInstance = None
|
sharedInstance = None
|
||||||
creationLock = threading.Lock()
|
creationLock = threading.Lock()
|
||||||
interval = 300
|
interval = 300
|
||||||
supportedModes = ["FT8", "FT4", "JT9", "JT65", "JS8"]
|
supportedModes = ["FT8", "FT4", "JT9", "JT65", "FST4", "FST4W", "JS8"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getSharedInstance():
|
def getSharedInstance():
|
||||||
|
@ -155,18 +155,14 @@ class ServiceHandler(SdrSourceEventClient):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
for group in groups:
|
for group in groups:
|
||||||
frequencies = sorted([f["frequency"] for f in group])
|
cf = self.get_center_frequency(group)
|
||||||
min = frequencies[0]
|
bw = self.get_bandwidth(group)
|
||||||
max = frequencies[-1]
|
|
||||||
cf = (min + max) / 2
|
|
||||||
bw = max - min
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"group center frequency: {0}, bandwidth: {1}".format(cf, bw)
|
"group center frequency: {0}, bandwidth: {1}".format(cf, bw)
|
||||||
)
|
)
|
||||||
resampler_props = PropertyLayer()
|
resampler_props = PropertyLayer()
|
||||||
resampler_props["center_freq"] = cf
|
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
|
||||||
resampler_props["samp_rate"] = bw + 24000
|
|
||||||
resampler = Resampler(resampler_props, self.source)
|
resampler = Resampler(resampler_props, self.source)
|
||||||
resampler.start()
|
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
|
# resampler goes in after the services since it must not be shutdown as long as the services are still running
|
||||||
self.services.append(resampler)
|
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):
|
def optimizeResampling(self, freqs, bandwidth):
|
||||||
freqs = sorted(freqs, key=lambda f: f["frequency"])
|
freqs = sorted(freqs, key=lambda f: f["frequency"])
|
||||||
distances = [
|
distances = [
|
||||||
@ -203,12 +216,10 @@ class ServiceHandler(SdrSourceEventClient):
|
|||||||
previous = split
|
previous = split
|
||||||
groups.append([f for f in freqs if previous < f["frequency"]])
|
groups.append([f for f in freqs if previous < f["frequency"]])
|
||||||
|
|
||||||
def get_bandwitdh(group):
|
def get_total_bandwidth(group):
|
||||||
freqs = sorted([f["frequency"] for f in group])
|
return bandwidth + len(group) * self.get_bandwidth(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)
|
|
||||||
|
|
||||||
total_bandwidth = sum([get_bandwitdh(group) for group in groups])
|
total_bandwidth = sum([get_total_bandwidth(group) for group in groups])
|
||||||
return {
|
return {
|
||||||
"num_splits": num_splits,
|
"num_splits": num_splits,
|
||||||
"total_bandwidth": total_bandwidth,
|
"total_bandwidth": total_bandwidth,
|
||||||
@ -250,16 +261,9 @@ class ServiceHandler(SdrSourceEventClient):
|
|||||||
center_freq = source.getProps()["center_freq"]
|
center_freq = source.getProps()["center_freq"]
|
||||||
d.set_offset_freq(frequency - center_freq)
|
d.set_offset_freq(frequency - center_freq)
|
||||||
d.set_center_freq(center_freq)
|
d.set_center_freq(center_freq)
|
||||||
if mode == "packet":
|
modeObject = Modes.findByModulation(mode)
|
||||||
d.set_demodulator("nfm")
|
d.set_demodulator(modeObject.get_modulation())
|
||||||
d.set_bpf(-4000, 4000)
|
d.set_bandpass(modeObject.get_bandpass())
|
||||||
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)
|
|
||||||
d.set_secondary_demodulator(mode)
|
d.set_secondary_demodulator(mode)
|
||||||
d.set_audio_compression("none")
|
d.set_audio_compression("none")
|
||||||
d.set_samp_rate(source.getProps()["samp_rate"])
|
d.set_samp_rate(source.getProps()["samp_rate"])
|
||||||
|
@ -59,9 +59,6 @@ class SdrSource(ABC):
|
|||||||
self.activateProfile()
|
self.activateProfile()
|
||||||
self.wireEvents()
|
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.monitor = None
|
||||||
self.clients = []
|
self.clients = []
|
||||||
|
@ -23,7 +23,7 @@ class ConnectorSource(SdrSource):
|
|||||||
"controlPort": Option("-c"),
|
"controlPort": Option("-c"),
|
||||||
"device": Option("-d"),
|
"device": Option("-d"),
|
||||||
"iqswap": Flag("-i"),
|
"iqswap": Flag("-i"),
|
||||||
"rtltcp_compat": Flag("-r"),
|
"rtltcp_compat": Option("-r"),
|
||||||
"ppm": Option("-P"),
|
"ppm": Option("-P"),
|
||||||
"rf_gain": Option("-g"),
|
"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")
|
81
owrx/wsjt.py
81
owrx/wsjt.py
@ -85,8 +85,56 @@ class Ft4Profile(WsjtProfile):
|
|||||||
return ["jt9", "--ft4", "-d", str(self.decoding_depth("ft4")), file]
|
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):
|
class WsjtParser(Parser):
|
||||||
modes = {"~": "FT8", "#": "JT65", "@": "JT9", "+": "FT4"}
|
modes = {"~": "FT8", "#": "JT65", "@": "JT9", "+": "FT4", "`": "FST4"}
|
||||||
|
|
||||||
def parse(self, messages):
|
def parse(self, messages):
|
||||||
for data in messages:
|
for data in messages:
|
||||||
@ -115,7 +163,7 @@ class WsjtParser(Parser):
|
|||||||
PskReporter.getSharedInstance().spot(out)
|
PskReporter.getSharedInstance().spot(out)
|
||||||
|
|
||||||
self.handler.write_wsjt_message(out)
|
self.handler.write_wsjt_message(out)
|
||||||
except ValueError:
|
except (ValueError, IndexError):
|
||||||
logger.exception("error while parsing wsjt message")
|
logger.exception("error while parsing wsjt message")
|
||||||
|
|
||||||
def pushDecode(self, mode):
|
def pushDecode(self, mode):
|
||||||
@ -139,6 +187,8 @@ class WsjtParser(Parser):
|
|||||||
|
|
||||||
|
|
||||||
class Decoder(ABC):
|
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):
|
def parse_timestamp(self, instring, dateformat):
|
||||||
ts = datetime.strptime(instring, dateformat)
|
ts = datetime.strptime(instring, dateformat)
|
||||||
return int(
|
return int(
|
||||||
@ -149,22 +199,35 @@ class Decoder(ABC):
|
|||||||
def parse(self, msg, dial_freq):
|
def parse(self, msg, dial_freq):
|
||||||
pass
|
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):
|
class Jt9Decoder(Decoder):
|
||||||
locator_pattern = re.compile(".*\\s([A-Z0-9]+)\\s([A-R]{2}[0-9]{2})$")
|
|
||||||
|
|
||||||
def parse(self, msg, dial_freq):
|
def parse(self, msg, dial_freq):
|
||||||
# ft8 sample
|
# ft8 sample
|
||||||
# '222100 -15 -0.0 508 ~ CQ EA7MJ IM66'
|
# '222100 -15 -0.0 508 ~ CQ EA7MJ IM66'
|
||||||
# jt65 sample
|
# jt65 sample
|
||||||
# '2352 -7 0.4 1801 # R0WAS R2ABM KO85'
|
# '2352 -7 0.4 1801 # R0WAS R2ABM KO85'
|
||||||
# '0003 -4 0.4 1762 # CQ R2ABM KO85'
|
# '0003 -4 0.4 1762 # CQ R2ABM KO85'
|
||||||
|
# fst4 sample
|
||||||
|
# '**** -23 0.6 3023 ` <...> <...> R 591631 BI53PV'
|
||||||
modes = list(WsjtParser.modes.keys())
|
modes = list(WsjtParser.modes.keys())
|
||||||
if msg[19] in modes:
|
if msg[19] in modes:
|
||||||
dateformat = "%H%M"
|
dateformat = "%H%M"
|
||||||
else:
|
else:
|
||||||
dateformat = "%H%M%S"
|
dateformat = "%H%M%S"
|
||||||
|
try:
|
||||||
timestamp = self.parse_timestamp(msg[0 : len(dateformat)], dateformat)
|
timestamp = self.parse_timestamp(msg[0 : len(dateformat)], dateformat)
|
||||||
|
except ValueError:
|
||||||
|
timestamp = None
|
||||||
msg = msg[len(dateformat) + 1:]
|
msg = msg[len(dateformat) + 1:]
|
||||||
modeChar = msg[14:15]
|
modeChar = msg[14:15]
|
||||||
mode = WsjtParser.modes[modeChar] if modeChar in WsjtParser.modes else "unknown"
|
mode = WsjtParser.modes[modeChar] if modeChar in WsjtParser.modes else "unknown"
|
||||||
@ -181,16 +244,6 @@ class Jt9Decoder(Decoder):
|
|||||||
result.update(self.parseMessage(wsjt_msg))
|
result.update(self.parseMessage(wsjt_msg))
|
||||||
return result
|
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):
|
class WsprDecoder(Decoder):
|
||||||
wspr_splitter_pattern = re.compile("([A-Z0-9]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)")
|
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