Merging the rest of owrx folder.

This commit is contained in:
Marat Fayzullin 2023-09-09 23:29:46 -04:00 committed by Marat Fayzullin
parent 5117e27129
commit c4afe1e594
8 changed files with 197 additions and 82 deletions

View File

@ -37,7 +37,7 @@ It has the following features:
- supports a wide range of [SDR hardware](https://github.com/jketterl/openwebrx/wiki/Supported-Hardware#sdr-devices) - supports a wide range of [SDR hardware](https://github.com/jketterl/openwebrx/wiki/Supported-Hardware#sdr-devices)
- Multiple SDR devices can be used simultaneously - Multiple SDR devices can be used simultaneously
- [digiham](https://github.com/jketterl/digiham) based demodularors (DMR, YSF, Pocsag, D-Star, NXDN) - [digiham](https://github.com/jketterl/digiham) based demodularors (DMR, YSF, Pocsag, D-Star, NXDN)
- [wsjt-x](https://physics.princeton.edu/pulsar/k1jt/wsjtx.html) based demodulators (FT8, FT4, WSPR, JT65, JT9, FST4, - [wsjt-x](https://wsjt.sourceforge.io/) based demodulators (FT8, FT4, WSPR, JT65, JT9, FST4,
FST4W) FST4W)
- [direwolf](https://github.com/wb2osz/direwolf) based demodulation of APRS packets - [direwolf](https://github.com/wb2osz/direwolf) based demodulation of APRS packets
- [JS8Call](http://js8call.com/) support - [JS8Call](http://js8call.com/) support

View File

@ -393,31 +393,40 @@
"upper_bound": 446200000, "upper_bound": 446200000,
"tags": ["public"] "tags": ["public"]
}, },
{
"name": "ADS-B Reporting",
"lower_bound": 960000000,
"upper_bound": 1215000000,
"tags": ["service"],
"frequencies": {
"adsb": 1090000000
}
},
{ {
"name": "LPD433", "name": "LPD433",
"lower_bound": 433050000, "lower_bound": 433050000,
"upper_bound": 434790000, "upper_bound": 434790000,
"tags": ["public"],
"frequencies": { "frequencies": {
"ism": 433920000 "ism": 433920000
}, }
"tags": ["public"]
}, },
{ {
"name": "VHF Air", "name": "VHF Air",
"lower_bound": 108000000, "lower_bound": 108000000,
"upper_bound": 137000000, "upper_bound": 137000000,
"tags": ["service"],
"frequencies": { "frequencies": {
"vdl2": 136975000 "vdl2": 136975000
}, },
"tags": ["service"]
}, },
{ {
"name": "VHF Marine", "name": "VHF Marine",
"lower_bound": 156000000, "lower_bound": 156000000,
"upper_bound": 174000000, "upper_bound": 174000000,
"tags": ["service"],
"frequencies": { "frequencies": {
"ais": [161975000, 162025000] "ais": [161975000, 162025000]
}, }
"tags": ["service"]
} }
] ]

View File

@ -5,7 +5,7 @@ from owrx.aprs.kiss import KissDeframer
from owrx.aprs import Ax25Parser, AprsParser from owrx.aprs import Ax25Parser, AprsParser
from pycsdr.modules import Convert, FmDemod, Agc, TimingRecovery, DBPskDecoder, VaricodeDecoder, JKRttyDecoder, BaudotDecoder, Lowpass, RttyDecoder, CwDecoder, SstvDecoder, FaxDecoder, Shift from pycsdr.modules import Convert, FmDemod, Agc, TimingRecovery, DBPskDecoder, VaricodeDecoder, JKRttyDecoder, BaudotDecoder, Lowpass, RttyDecoder, CwDecoder, SstvDecoder, FaxDecoder, Shift
from pycsdr.types import Format from pycsdr.types import Format
from owrx.aprs.module import DirewolfModule from owrx.aprs.direwolf import DirewolfModule
from owrx.sstv import SstvParser from owrx.sstv import SstvParser
from owrx.fax import FaxParser from owrx.fax import FaxParser
from owrx.config import Config from owrx.config import Config

View File

@ -1,6 +1,12 @@
import random from pycsdr.types import Format
from pycsdr.modules import Writer, TcpSource, ExecModule, CallbackWriter
from csdr.module import LogWriter
from owrx.config.core import CoreConfig
from owrx.config import Config from owrx.config import Config
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import time
import os
import random
import socket import socket
import logging import logging
@ -136,3 +142,71 @@ IGLOGIN {callsign} {password}
) )
return config return config
class DirewolfModule(ExecModule, DirewolfConfigSubscriber):
def __init__(self, service: bool = False, ais: bool = False):
self.tcpSource = None
self.writer = None
self.service = service
self.ais = ais
self.direwolfConfigPath = "{tmp_dir}/openwebrx_direwolf_{myid}.conf".format(
tmp_dir=CoreConfig().get_temporary_directory(), myid=id(self)
)
self.direwolfConfig = DirewolfConfig()
self.direwolfConfig.wire(self)
self.__writeConfig()
# compose command line
cmdLine = ["direwolf", "-c", self.direwolfConfigPath, "-r", "48000", "-t", "0", "-q", "d", "-q", "h"]
# for AIS mode, add -B AIS -A
if self.ais:
cmdLine += ["-B", "AIS", "-A"]
super().__init__(Format.SHORT, Format.CHAR, cmdLine)
# direwolf supplies the data via a socket which we tap into in start()
# the output on its STDOUT is informative, but we still want to log it
super().setWriter(LogWriter(__name__))
self.start()
def __writeConfig(self):
file = open(self.direwolfConfigPath, "w")
file.write(self.direwolfConfig.getConfig(self.service))
file.close()
def setWriter(self, writer: Writer) -> None:
self.writer = writer
if self.tcpSource is not None:
self.tcpSource.setWriter(writer)
def start(self):
delay = 0.5
retries = 0
while True:
try:
self.tcpSource = TcpSource(self.direwolfConfig.getPort(), Format.CHAR)
if self.writer:
self.tcpSource.setWriter(self.writer)
break
except ConnectionError:
if retries > 20:
logger.error("maximum number of connection attempts reached. did direwolf start up correctly?")
raise
retries += 1
time.sleep(delay)
def restart(self):
self.__writeConfig()
super().restart()
self.start()
def onConfigChanged(self):
self.restart()
def stop(self) -> None:
super().stop()
os.unlink(self.direwolfConfigPath)
self.direwolfConfig.unwire(self)
self.direwolfConfig = None

View File

@ -36,7 +36,7 @@ class Bookmark(object):
} }
class BookmakrSubscription(object): class BookmarkSubscription(object):
def __init__(self, subscriptee, range, subscriber: callable): def __init__(self, subscriptee, range, subscriber: callable):
self.subscriptee = subscriptee self.subscriptee = subscriptee
self.range = range self.range = range
@ -67,11 +67,7 @@ class Bookmarks(object):
self.bookmarks = [] self.bookmarks = []
self.subscriptions = [] self.subscriptions = []
# Known bookmark files, starting with the main file # Known bookmark files, starting with the main file
self.fileList = [ self.fileList = [Bookmarks._getBookmarksFile(), "/etc/openwebrx/bookmarks.json", "bookmarks.json"]
Bookmarks._getBookmarksFile(),
"bookmarks.json",
"/etc/openwebrx/bookmarks.json",
]
# Find additional bookmark files in the bookmarks.d folder # Find additional bookmark files in the bookmarks.d folder
try: try:
bookmarksDir = "/etc/openwebrx/bookmarks.d" bookmarksDir = "/etc/openwebrx/bookmarks.d"
@ -166,9 +162,11 @@ class Bookmarks(object):
logger.exception("Error while calling bookmark subscriptions") logger.exception("Error while calling bookmark subscriptions")
def subscribe(self, range, callback): def subscribe(self, range, callback):
self.subscriptions.append(BookmakrSubscription(self, range, callback)) sub = BookmarkSubscription(self, range, callback)
self.subscriptions.append(BookmarkSubscription(self, range, callback))
return sub
def unsubscribe(self, subscriptions: BookmakrSubscription): def unsubscribe(self, subscription: BookmarkSubscription):
if subscriptions not in self.subscriptions: if subscription not in self.subscriptions:
return return
self.subscriptions.remove(subscriptions) self.subscriptions.remove(subscription)

View File

@ -13,7 +13,6 @@ from datetime import datetime, timedelta
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
class UnknownFeatureException(Exception): class UnknownFeatureException(Exception):
@ -76,22 +75,22 @@ class FeatureDetector(object):
# optional features and their requirements # optional features and their requirements
"digital_voice_digiham": ["digiham", "codecserver_ambe"], "digital_voice_digiham": ["digiham", "codecserver_ambe"],
"digital_voice_freedv": ["freedv_rx"], "digital_voice_freedv": ["freedv_rx"],
"digital_voice_m17": ["m17_demod", "digiham"], "digital_voice_m17": ["m17_demod"],
"wsjt-x": ["wsjtx"], "wsjt-x": ["wsjtx"],
"wsjt-x-2-3": ["wsjtx_2_3"], "wsjt-x-2-3": ["wsjtx_2_3"],
"wsjt-x-2-4": ["wsjtx_2_4"], "wsjt-x-2-4": ["wsjtx_2_4"],
"msk144": ["msk144decoder"], "msk144": ["msk144decoder"],
"packet": ["direwolf"], "packet": ["direwolf"],
"pocsag": ["digiham"], "pocsag": ["digiham"],
"page": ["multimon"],
"selcall": ["multimon"],
"ism": ["rtl433"],
"hfdl": ["dumphfdl"],
"vdl2": ["dumpvdl2"],
"adsb": ["dump1090"],
"acars": ["acarsdec"],
"js8call": ["js8", "js8py"], "js8call": ["js8", "js8py"],
"drm": ["dream"], "drm": ["dream"],
"adsb": ["dump1090"],
"ism": ["rtl_433"],
"hfdl": ["dumphfdl"],
"vdl2": ["dumpvdl2"],
"acars": ["acarsdec"],
"page": ["multimon"],
"selcall": ["multimon"],
"png": ["imagemagick"], "png": ["imagemagick"],
} }
@ -145,14 +144,12 @@ class FeatureDetector(object):
if cache.has(requirement): if cache.has(requirement):
return cache.get(requirement) return cache.get(requirement)
logger.debug("performing feature check for %s", requirement)
method = self._get_requirement_method(requirement) method = self._get_requirement_method(requirement)
result = False result = False
if method is not None: if method is not None:
result = method() result = method()
else: else:
logger.error("detection of requirement {0} not implement. please fix in code!".format(requirement)) logger.error("detection of requirement {0} not implement. please fix in code!".format(requirement))
logger.debug("feature check for %s complete. result: %s", requirement, result)
cache.set(requirement, result) cache.set(requirement, result)
return result return result
@ -175,7 +172,14 @@ class FeatureDetector(object):
cwd=tmp_dir, cwd=tmp_dir,
env=env, env=env,
) )
rc = process.wait() while True:
try:
rc = process.wait(10)
break
except subprocess.TimeoutExpired:
logger.warning("feature check command \"%s\" did not return after 10 seconds!", command)
process.kill()
if expected_result is None: if expected_result is None:
return rc != 32512 return rc != 32512
else: else:
@ -418,7 +422,7 @@ class FeatureDetector(object):
You can find more information [here](https://github.com/mobilinkd/m17-cxx-demod) You can find more information [here](https://github.com/mobilinkd/m17-cxx-demod)
""" """
return self.command_is_runnable("m17-demod") return self.command_is_runnable("m17-demod", 0)
def has_direwolf(self): def has_direwolf(self):
""" """
@ -437,7 +441,7 @@ class FeatureDetector(object):
def has_wsjtx(self): def has_wsjtx(self):
""" """
To decode FT8 and other digimodes, you need to install the WSJT-X software suite. Please check the To decode FT8 and other digimodes, you need to install the WSJT-X software suite. Please check the
[WSJT-X homepage](https://physics.princeton.edu/pulsar/k1jt/wsjtx.html) for ready-made packages or instructions [WSJT-X homepage](https://wsjt.sourceforge.io/) for ready-made packages or instructions
on how to build from source. on how to build from source.
""" """
return reduce(and_, map(self.command_is_runnable, ["jt9", "wsprd"]), True) return reduce(and_, map(self.command_is_runnable, ["jt9", "wsprd"]), True)
@ -562,6 +566,9 @@ class FeatureDetector(object):
Codecserver is used to decode audio data from digital voice modes using the AMBE codec. Codecserver is used to decode audio data from digital voice modes using the AMBE codec.
You can find more information [here](https://github.com/jketterl/codecserver). You can find more information [here](https://github.com/jketterl/codecserver).
NOTE: this feature flag checks both the availability of codecserver as well as the availability of the AMBE
codec in the configured codecserver instance.
""" """
config = Config.get() config = Config.get()
@ -576,6 +583,65 @@ class FeatureDetector(object):
return False return False
except ConnectionError: except ConnectionError:
return False return False
except RuntimeError as e:
logger.exception("Codecserver error while checking for AMBE support:")
return False
def has_dump1090(self):
"""
To be able to decode Mode-S and ADS-B traffic originating from airplanes, you need to install the dump1090
decoder. There is a number of forks available, any version that supports the `--ifile` and `--iformat` arguments
should work.
Recommended fork: [dump1090 by Flightaware](https://github.com/flightaware/dump1090)
If you are using the OpenWebRX Debian or Ubuntu repository, you should be able to install the package
`dump1090-fa-minimal`.
If you are running a different fork, please make sure that the command `dump1090` (without suffixes) runs the
version you would like to use. You can use symbolic links or the
[Debian alternatives system](https://wiki.debian.org/DebianAlternatives) to achieve this.
"""
return self.command_is_runnable("dump1090 --version")
def has_rtl_433(self):
"""
OpenWebRX can make use of the `rtl_433` software to decode various signals in the ISM bands.
You can find more information [here](https://github.com/merbanan/rtl_433).
Debian and Ubuntu based systems should be able to install the package `rtl-433` from the package manager.
"""
return self.command_is_runnable("rtl_433 -h")
def has_dumphfdl(self):
"""
OpenWebRX supports decoding HFDL airplane communications using the `dumphfdl` decoder.
You can find more information [here](https://github.com/szpajder/dumphfdl)
If you are using the OpenWebRX Debian or Ubuntu repository, you should be able to install the package
`dumphfdl`.
"""
return self.command_is_runnable("dumphfdl --version")
def has_dumpvdl2(self):
"""
OpenWebRX supports decoding VDL Mode 2 airplane communications using the `dumpvdl2` decoder.
You can find more information [here](https://github.com/szpajder/dumpvdl2)
If you are using the OpenWebRX Debian or Ubuntu repository, you should be able to install the package
`dumpvdl2`.
"""
return self.command_is_runnable("dumpvdl2 --version")
def has_acarsdec(self):
"""
OpenWebRX uses the [acarsdec](https://github.com/TLeconte/acarsdec) tool to decode ACARS
traffic. You will have to compile it from source.
"""
return self.command_is_runnable("acarsdec --help")
def has_imagemagick(self): def has_imagemagick(self):
""" """
@ -592,42 +658,3 @@ class FeatureDetector(object):
""" """
return self.command_is_runnable("multimon-ng --help") return self.command_is_runnable("multimon-ng --help")
def has_rtl433(self):
"""
OpenWebRX uses the [rtl-433](https://github.com/merbanan/rtl_433) decoder suite to decode various
instrumentation signals. Rtl_433 is available from the package manager on many
distributions, or you can compile it from source.
"""
return self.command_is_runnable("rtl_433 --help")
def has_dumphfdl(self):
"""
OpenWebRX uses the [DumpHFDL](https://github.com/szpajder/dumphfdl) tool to decode HFDL
aircraft communications. The latest DumpHFDL package is available from the OpenWebRX
repository as "dumphfdl", or you can compile it from source.
"""
return self.command_is_runnable("dumphfdl --help")
def has_dumpvdl2(self):
"""
OpenWebRX uses the [DumpVDL2](https://github.com/szpajder/dumpvdl2) tool to decode VDL2
aircraft communications. The latest DumpVDL2 package is available from the OpenWebRX
repository as "dumpvdl2", or you can compile it from source.
"""
return self.command_is_runnable("dumpvdl2 --help")
def has_dump1090(self):
"""
OpenWebRX uses the [dump1090](https://github.com/antirez/dump1090) tool to decode ADSB
traffic. The latest Dump1090 package is available from the OpenWebRX repository as
"dump1090-fa-minimal", or you can compile it from source.
"""
return self.command_is_runnable("dump1090 --help")
def has_acarsdec(self):
"""
OpenWebRX uses the [acarsdec](https://github.com/TLeconte/acarsdec) tool to decode ACARS
traffic. You will have to compile it from source.
"""
return self.command_is_runnable("acarsdec --help")

View File

@ -134,7 +134,7 @@ 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"]),
# Using current RTTY decoder for now # Testing jketterl's RTTY decoder
DigitalMode("jkrtty170", "RTTY 45/170", underlying=["usb", "lsb"]), DigitalMode("jkrtty170", "RTTY 45/170", underlying=["usb", "lsb"]),
DigitalMode("jkrtty450", "RTTY 50N/450", underlying=["lsb", "usb"]), DigitalMode("jkrtty450", "RTTY 50N/450", underlying=["lsb", "usb"]),
DigitalMode("jkrtty85", "RTTY 50N/85", underlying=["lsb", "usb"]), DigitalMode("jkrtty85", "RTTY 50N/85", underlying=["lsb", "usb"]),

View File

@ -27,7 +27,7 @@ class PskReporter(Reporter):
Supports all valid MODE and SUBMODE values from the ADIF standard. Supports all valid MODE and SUBMODE values from the ADIF standard.
Current version at the time of the last change: Current version at the time of the last change:
https://www.adif.org/312/ADIF_312.htm#Mode_Enumeration https://www.adif.org/314/ADIF_314.htm#Mode_Enumeration
""" """
return ["FT8", "FT4", "JT9", "JT65", "FST4", "JS8", "Q65", "WSPR", "FST4W", "MSK144"] return ["FT8", "FT4", "JT9", "JT65", "FST4", "JS8", "Q65", "WSPR", "FST4W", "MSK144"]
@ -105,27 +105,34 @@ class Uploader(object):
# filter out any erroneous encodes # filter out any erroneous encodes
encoded = [e for e in encoded if e is not None] encoded = [e for e in encoded if e is not None]
def chunks(l, n): def chunks(block, max_size):
"""Yield successive n-sized chunks from l.""" size = 0
for i in range(0, len(l), n): current = []
yield l[i : i + n] for r in block:
if size + len(r) > max_size:
yield current
current = []
size = 0
size += len(r)
current.append(r)
yield current
rHeader = self.getReceiverInformationHeader() rHeader = self.getReceiverInformationHeader()
rInfo = self.getReceiverInformation() rInfo = self.getReceiverInformation()
sHeader = self.getSenderInformationHeader() sHeader = self.getSenderInformationHeader()
packets = [] packets = []
# 50 seems to be a safe bet # 1200 bytes of sender data should keep the packet size below MTU for most cases
for chunk in chunks(encoded, 50): for chunk in chunks(encoded, 1200):
sInfo = self.getSenderInformation(chunk) sInfo = self.getSenderInformation(chunk)
length = 16 + len(rHeader) + len(sHeader) + len(rInfo) + len(sInfo) length = 16 + len(rHeader) + len(sHeader) + len(rInfo) + len(sInfo)
header = self.getHeader(length) header = self.getHeader(length)
packets.append(header + rHeader + sHeader + rInfo + sInfo) packets.append(header + rHeader + sHeader + rInfo + sInfo)
self.sequence = (self.sequence + len(chunk)) % (1 << 32)
return packets return packets
def getHeader(self, length): def getHeader(self, length):
self.sequence += 1
return bytes( return bytes(
# protocol version # protocol version
[0x00, 0x0A] [0x00, 0x0A]
@ -142,7 +149,7 @@ class Uploader(object):
try: try:
return bytes( return bytes(
self.encodeString(spot["callsign"]) self.encodeString(spot["callsign"])
+ list(int(spot["freq"]).to_bytes(4, "big")) + list(int(spot["freq"]).to_bytes(5, "big"))
+ list(int(spot["db"]).to_bytes(1, "big", signed=True)) + list(int(spot["db"]).to_bytes(1, "big", signed=True))
+ self.encodeString(spot["mode"]) + self.encodeString(spot["mode"])
+ self.encodeString(spot["locator"]) + self.encodeString(spot["locator"])
@ -208,7 +215,7 @@ class Uploader(object):
# senderCallsign # senderCallsign
+ [0x80, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] + [0x80, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F]
# frequency # frequency
+ [0x80, 0x05, 0x00, 0x04, 0x00, 0x00, 0x76, 0x8F] + [0x80, 0x05, 0x00, 0x05, 0x00, 0x00, 0x76, 0x8F]
# sNR # sNR
+ [0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x76, 0x8F] + [0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x76, 0x8F]
# mode # mode