Skip to content

Commit c154f1d

Browse files
committed
Importing changes from Jakob Ketterl's development branch.
1 parent 49f24a7 commit c154f1d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1313
-216
lines changed

csdr/chain/analog.py

+45-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, HdAudio, DeemphasisTauChain
2-
from pycsdr.modules import AmDemod, DcBlock, FmDemod, Limit, NfmDeemphasis, Agc, Afc, WfmDeemphasis, FractionalDecimator, RealPart
1+
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, HdAudio, \
2+
DeemphasisTauChain, MetaProvider, RdsChain
3+
from pycsdr.modules import AmDemod, DcBlock, FmDemod, Limit, NfmDeemphasis, Agc, Afc, \
4+
WfmDeemphasis, FractionalDecimator, RealPart, Writer, Buffer
35
from pycsdr.types import Format, AgcProfile
6+
from csdr.chain.redsea import Redsea
7+
from typing import Optional
8+
from owrx.feature import FeatureDetector
49

510

611
class Am(BaseDemodulatorChain):
@@ -38,18 +43,29 @@ def setSampleRate(self, sampleRate: int) -> None:
3843
self.replace(2, NfmDeemphasis(sampleRate))
3944

4045

41-
class WFm(BaseDemodulatorChain, FixedIfSampleRateChain, DeemphasisTauChain, HdAudio):
42-
def __init__(self, sampleRate: int, tau: float):
46+
class WFm(BaseDemodulatorChain, FixedIfSampleRateChain, DeemphasisTauChain, HdAudio, MetaProvider, RdsChain):
47+
def __init__(self, sampleRate: int, tau: float, rdsRbds: bool):
4348
self.sampleRate = sampleRate
4449
self.tau = tau
50+
self.rdsRbds = rdsRbds
51+
self.limit = Limit()
52+
# this buffer is used to tap into the raw audio stream for redsea RDS decoding
53+
self.metaTapBuffer = Buffer(Format.FLOAT)
4554
workers = [
4655
FmDemod(),
47-
Limit(),
56+
self.limit,
4857
FractionalDecimator(Format.FLOAT, 200000.0 / self.sampleRate, prefilter=True),
4958
WfmDeemphasis(self.sampleRate, self.tau),
5059
]
60+
self.metaChain = None
61+
self.metaWriter = None
5162
super().__init__(workers)
5263

64+
def _connect(self, w1, w2, buffer: Optional[Buffer] = None) -> None:
65+
if w1 is self.limit:
66+
buffer = self.metaTapBuffer
67+
super()._connect(w1, w2, buffer)
68+
5369
def getFixedIfSampleRate(self):
5470
return 200000
5571

@@ -66,6 +82,30 @@ def setSampleRate(self, sampleRate: int) -> None:
6682
self.replace(2, FractionalDecimator(Format.FLOAT, 200000.0 / self.sampleRate, prefilter=True))
6783
self.replace(3, WfmDeemphasis(self.sampleRate, self.tau))
6884

85+
def setMetaWriter(self, writer: Writer) -> None:
86+
if not FeatureDetector().is_available("rds"):
87+
return
88+
if self.metaChain is None:
89+
self.metaChain = Redsea(self.getFixedIfSampleRate(), self.rdsRbds)
90+
self.metaChain.setReader(self.metaTapBuffer.getReader())
91+
self.metaWriter = writer
92+
self.metaChain.setWriter(self.metaWriter)
93+
94+
def stop(self):
95+
super().stop()
96+
if self.metaChain is not None:
97+
self.metaChain.stop()
98+
self.metaChain = None
99+
self.metaWriter = None
100+
101+
def setRdsRbds(self, rdsRbds: bool) -> None:
102+
self.rdsRbds = rdsRbds
103+
if self.metaChain is not None:
104+
self.metaChain.stop()
105+
self.metaChain = Redsea(self.getFixedIfSampleRate(), self.rdsRbds)
106+
self.metaChain.setReader(self.metaTapBuffer.getReader())
107+
self.metaChain.setWriter(self.metaWriter)
108+
69109

70110
class Ssb(BaseDemodulatorChain):
71111
def __init__(self):

csdr/chain/dablin.py

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, \
2+
MetaProvider, DabServiceSelector, DialFrequencyReceiver
3+
from csdr.module import PickleModule
4+
from csdreti.modules import EtiDecoder
5+
from owrx.dab.dablin import DablinModule
6+
from pycsdr.modules import Downmix, Buffer, Shift, Writer
7+
from pycsdr.types import Format
8+
from typing import Optional
9+
from random import random
10+
11+
import logging
12+
13+
logger = logging.getLogger(__name__)
14+
15+
16+
class MetaProcessor(PickleModule):
17+
def __init__(self, shifter: Shift):
18+
self.shifter = shifter
19+
self.shift = 0.0
20+
self.coarse_increment = -32 / 2048000
21+
self.fine_increment = - (1/3) / 2048000
22+
# carrier spacing is 1kHz, don't drift further than that.
23+
self.max_shift = 1000 / 2048000
24+
super().__init__()
25+
26+
def process(self, data):
27+
result = {}
28+
for key, value in data.items():
29+
if key == "coarse_frequency_shift":
30+
if value > 0:
31+
self._nudgeShift(random() * self.coarse_increment)
32+
else:
33+
self._nudgeShift(random() * -self.coarse_increment)
34+
elif key == "fine_frequency_shift":
35+
if abs(value) > 10:
36+
logger.debug("ffs: %f", value)
37+
self._nudgeShift(self.fine_increment * value)
38+
else:
39+
# pass through everything else
40+
result[key] = value
41+
# don't send out data if there was nothing interesting for the client
42+
if not result:
43+
return
44+
result["mode"] = "DAB"
45+
return result
46+
47+
def _nudgeShift(self, amount):
48+
self.shift += amount
49+
if self.shift > self.max_shift:
50+
self.shift = self.max_shift
51+
elif self.shift < -self.max_shift:
52+
self.shift = -self.max_shift
53+
logger.debug("new shift: %f", self.shift)
54+
self.shifter.setRate(self.shift)
55+
56+
def resetShift(self):
57+
logger.debug("resetting shift")
58+
self.shift = 0
59+
self.shifter.setRate(0)
60+
61+
62+
class Dablin(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, MetaProvider, DabServiceSelector, DialFrequencyReceiver):
63+
def __init__(self):
64+
shift = Shift(0)
65+
decoder = EtiDecoder()
66+
67+
metaBuffer = Buffer(Format.CHAR)
68+
decoder.setMetaWriter(metaBuffer)
69+
self.processor = MetaProcessor(shift)
70+
self.processor.setReader(metaBuffer.getReader())
71+
# use a dummy to start with. it won't run without.
72+
# will be replaced by setMetaWriter().
73+
self.processor.setWriter(Buffer(Format.CHAR))
74+
75+
self.dablin = DablinModule()
76+
77+
workers = [
78+
shift,
79+
decoder,
80+
self.dablin,
81+
Downmix(Format.FLOAT),
82+
]
83+
super().__init__(workers)
84+
85+
def _connect(self, w1, w2, buffer: Optional[Buffer] = None) -> None:
86+
if isinstance(w2, EtiDecoder):
87+
# eti decoder needs big chunks of data
88+
buffer = Buffer(w1.getOutputFormat(), size=2097152)
89+
super()._connect(w1, w2, buffer)
90+
91+
def getFixedIfSampleRate(self) -> int:
92+
return 2048000
93+
94+
def getFixedAudioRate(self) -> int:
95+
return 48000
96+
97+
def stop(self):
98+
self.processor.stop()
99+
100+
def setMetaWriter(self, writer: Writer) -> None:
101+
self.processor.setWriter(writer)
102+
103+
def setDabServiceId(self, serviceId: int) -> None:
104+
self.dablin.setDabServiceId(serviceId)
105+
106+
def setDialFrequency(self, frequency: int) -> None:
107+
self.processor.resetShift()

csdr/chain/demodulator.py

+12
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ def setDeemphasisTau(self, tau: float) -> None:
4949
pass
5050

5151

52+
class RdsChain(ABC):
53+
@abstractmethod
54+
def setRdsRbds(self, rdsRbds: bool) -> None:
55+
pass
56+
57+
58+
class DabServiceSelector(ABC):
59+
@abstractmethod
60+
def setDabServiceId(self, serviceId: int) -> None:
61+
pass
62+
63+
5264
class BaseDemodulatorChain(Chain):
5365
def supportsSquelch(self) -> bool:
5466
return True

csdr/chain/drm.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
class Drm(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain):
88
def __init__(self):
9-
workers = [Convert(Format.COMPLEX_FLOAT, Format.COMPLEX_SHORT), DrmModule(), Downmix()]
9+
workers = [
10+
Convert(Format.COMPLEX_FLOAT, Format.COMPLEX_SHORT),
11+
DrmModule(),
12+
Downmix(Format.SHORT),
13+
]
1014
super().__init__(workers)
1115

1216
def supportsSquelch(self) -> bool:

csdr/chain/redsea.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from csdr.chain import Chain
2+
from pycsdr.modules import Convert
3+
from pycsdr.types import Format
4+
from csdr.module.toolbox import RedseaModule
5+
from csdr.module import JsonParser
6+
7+
8+
class Redsea(Chain):
9+
def __init__(self, sampleRate: int, rbds: bool):
10+
super().__init__([
11+
Convert(Format.FLOAT, Format.SHORT),
12+
RedseaModule(sampleRate, rbds),
13+
JsonParser("WFM"),
14+
])

csdr/module/__init__.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ def stop(self):
8686
self.reader.stop()
8787

8888
def start(self):
89+
# don't start twice.
90+
if self.is_alive():
91+
return
8992
Thread.start(self)
9093

9194

@@ -164,7 +167,7 @@ def process(self, line):
164167
logger.debug(msg)
165168
return msg
166169
except json.JSONDecodeError:
167-
logger.exception("error parsing rtl433 json")
170+
logger.exception("error parsing decoder json")
168171

169172

170173
class PopenModule(AutoStartModule, metaclass=ABCMeta):
@@ -222,7 +225,7 @@ def run(self) -> None:
222225

223226
# log all completed lines
224227
for line in lines[0:-1]:
225-
self.logger.info("{}: {}".format("STDOUT", line.strip(b'\n').decode()))
228+
self.logger.info("{}: {}".format("STDOUT", line.decode(errors="replace")))
226229

227230
def stop(self):
228231
self.reader.stop()

csdr/module/toolbox.py

+18
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,21 @@ def getCommand(self):
167167

168168
def getOutputFormat(self) -> Format:
169169
return Format.CHAR
170+
171+
172+
class DablinModule(ExecModule):
173+
def __init__(self):
174+
self.serviceId = 0
175+
super().__init__(
176+
Format.CHAR,
177+
Format.FLOAT,
178+
self._buildArgs()
179+
)
180+
181+
def _buildArgs(self):
182+
return ["dablin", "-p", "-s", "{:#06x}".format(self.serviceId)]
183+
184+
def setDabServiceId(self, serviceId: int) -> None:
185+
self.serviceId = serviceId
186+
self.setArgs(self._buildArgs())
187+
self.restart()

debian/control

+39-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,51 @@ Source: openwebrx
22
Maintainer: Marat Fayzullin <[email protected]>
33
Section: hamradio
44
Priority: optional
5+
Rules-Requires-Root: no
56
Standards-Version: 4.2.0
6-
Build-Depends: debhelper (>= 11), dh-python, python3-all (>= 3.5), python3-setuptools
7+
Build-Depends: debhelper (>= 11),
8+
dh-python,
9+
python3-all (>= 3.5),
10+
python3-setuptools
711
Homepage: https://www.openwebrx.de/
812
Vcs-Browser: https://github.com/luarvique/openwebrx
913
Vcs-Git: https://github.com/luarvique/openwebrx.git
1014

1115
Package: openwebrx
1216
Architecture: all
13-
Depends: adduser, python3 (>= 3.5), python3-pkg-resources, owrx-connector (>= 0.6), soapysdr-tools, python3-csdr (>= 0.18.17), ${python3:Depends}, ${misc:Depends}
14-
Recommends: python3-digiham (>= 0.6), direwolf (>= 1.4), wsjtx, js8call, runds-connector (>= 0.2), hpsdrconnector, aprs-symbols, m17-demod, js8call, python3-js8py (>= 0.1), nmux (>= 0.18), codecserver (>= 0.1), msk144decoder, multimon-ng, rtl-433, dumphfdl, dumpvdl2, dump1090-fa-minimal, imagemagick
17+
Depends: adduser,
18+
python3 (>= 3.5),
19+
python3-pkg-resources,
20+
owrx-connector (>= 0.6),
21+
python3-csdr (>= 0.18.17),
22+
soapysdr-tools,
23+
${python3:Depends},
24+
${misc:Depends}
25+
Recommends: python3-digiham (>= 0.6),
26+
direwolf (>= 1.4),
27+
wsjtx,
28+
js8call,
29+
runds-connector (>= 0.2),
30+
hpsdrconnector,
31+
aprs-symbols,
32+
m17-demod,
33+
js8call,
34+
python3-js8py (>= 0.1),
35+
nmux (>= 0.18),
36+
codecserver (>= 0.1),
37+
msk144decoder,
38+
dump1090-fa-minimal,
39+
dumphfdl,
40+
dumpvdl2,
41+
rtl-433,
42+
extra-sdr-drivers,
43+
perseus-tools,
44+
dream-headless,
45+
codec2,
46+
redsea,
47+
python3-csdr-eti,
48+
dablin,
49+
multimon-ng,
50+
imagemagick
1551
Description: multi-user web sdr
1652
Open source, multi-user SDR receiver with a web interface

debian/openwebrx.config

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
db_get openwebrx/admin_user_configured
55
if [ "${1:-}" = "reconfigure" ] || [ "${RET}" != true ]; then
6+
db_settitle openwebrx/title
67
db_input high openwebrx/admin_user_password || true
78
db_go
89
fi

debian/openwebrx.desktop

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Desktop Entry]
2+
Version=1.0
3+
Name=OpenWebRX
4+
Type=Application
5+
Comment=Web-based software defined radio receiver
6+
Icon=openwebrx
7+
Exec=xdg-open http://localhost:8073/
8+
Categories=Network;HamRadio

debian/openwebrx.install

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ bookmarks.json etc/openwebrx/
33
bookmarks.d etc/openwebrx/
44
openwebrx.conf etc/openwebrx/
55
systemd/openwebrx.service lib/systemd/system/
6+
debian/openwebrx.svg usr/share/icons/hicolor/scalable/apps
7+
debian/openwebrx.desktop usr/share/applications

debian/openwebrx.postinst

+8-4
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,29 @@ case "$1" in
1414
adduser --system --group --no-create-home --home /nonexistent --quiet "${OWRX_USER}"
1515
usermod -aG plugdev "${OWRX_USER}"
1616

17+
# ensure group exists first (dependency is optional)
18+
addgroup --system --quiet perseususb
19+
usermod -aG perseususb "${OWRX_USER}"
20+
1721
# create OpenWebRX data directory and set the correct permissions
1822
if [ ! -d "${OWRX_DATADIR}" ] && [ ! -L "${OWRX_DATADIR}" ]; then mkdir "${OWRX_DATADIR}"; fi
19-
chown "${OWRX_USER}". ${OWRX_DATADIR}
23+
chown "${OWRX_USER}": ${OWRX_DATADIR}
2024

2125
# create empty config files now to avoid permission problems later
2226
if [ ! -e "${OWRX_USERS_FILE}" ]; then
2327
echo "[]" > "${OWRX_USERS_FILE}"
24-
chown "${OWRX_USER}". "${OWRX_USERS_FILE}"
28+
chown "${OWRX_USER}": "${OWRX_USERS_FILE}"
2529
chmod 0600 "${OWRX_USERS_FILE}"
2630
fi
2731

2832
if [ ! -e "${OWRX_SETTINGS_FILE}" ]; then
2933
echo "{}" > "${OWRX_SETTINGS_FILE}"
30-
chown "${OWRX_USER}". "${OWRX_SETTINGS_FILE}"
34+
chown "${OWRX_USER}": "${OWRX_SETTINGS_FILE}"
3135
fi
3236

3337
if [ ! -e "${OWRX_BOOKMARKS_FILE}" ]; then
3438
touch "${OWRX_BOOKMARKS_FILE}"
35-
chown "${OWRX_USER}". "${OWRX_BOOKMARKS_FILE}"
39+
chown "${OWRX_USER}": "${OWRX_BOOKMARKS_FILE}"
3640
fi
3741

3842
db_get openwebrx/admin_user_password

debian/openwebrx.svg

+1
Loading

0 commit comments

Comments
 (0)