Compare commits

..

1 Commits

Author SHA1 Message Date
Jakob Ketterl abf7caf8cf remove mbelib and dependencies 2021-05-31 22:36:41 +02:00
199 changed files with 3069 additions and 10483 deletions

View File

@ -1,24 +1,3 @@
**unreleased**
- SDR device log messages are now available in the web configuration to simplify troubleshooting
- Added support for the MSK144 digimode
**1.2.1**
- FifiSDR support fixed (pipeline formats now line up correctly)
- Added "Device" input for FifiSDR devices for sound card selection
**1.2.0**
- Major rewrite of all demodulation components to make use of the new csdr/pycsdr and digiham/pydigiham demodulator
modules
- Preliminary display of M17 callsign information
- New devices supported:
- Blade RF
**1.1.0**
- Reworked most graphical elements as SVGs for faster loadtimes and crispier display on hi-dpi displays
- Updated pipelines to match changes in digiham
- Changed D-Star and NXDN integrations to use new decoders from digiham
- Added D-Star and NXDN metadata display
**1.0.0**
- 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

View File

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

View File

@ -42,7 +42,7 @@
"frequencies": {
"bpsk31": 3580000,
"ft8": 3573000,
"wspr": 3568600,
"wspr": 3592600,
"jt65": 3570000,
"jt9": 3572000,
"ft4": [3568000, 3575000],
@ -56,7 +56,7 @@
"upper_bound": 5366500,
"frequencies": {
"ft8": 5357000,
"wspr": [5287200, 5364700]
"wspr": 5364700
},
"tags": ["hamradio"]
},
@ -177,8 +177,7 @@
"jt9": 50312000,
"ft4": 50318000,
"js8": 50318000,
"q65": [50211000, 50275000],
"msk144": 50260000
"q65": [50211000, 50275000]
},
"tags": ["hamradio"]
},
@ -187,8 +186,7 @@
"lower_bound": 70150000,
"upper_bound": 70200000,
"frequencies": {
"wspr": 70091000,
"msk144": 70230000
"wspr": 70091000
},
"tags": ["hamradio"]
},
@ -202,8 +200,7 @@
"ft4": 144170000,
"jt65": 144120000,
"packet": 144800000,
"q65": 144116000,
"msk144": 144360000
"q65": 144116000
},
"tags": ["hamradio"]
},
@ -213,8 +210,7 @@
"upper_bound": 440000000,
"frequencies": {
"pocsag": 439987500,
"q65": 432065000,
"msk144": 432360000
"q65": 432065000
},
"tags": ["hamradio"]
},
@ -368,4 +364,4 @@
"upper_bound": 446200000,
"tags": ["public"]
}
]
]

389
config_webrx.py Normal file
View File

@ -0,0 +1,389 @@
# -*- coding: utf-8 -*-
"""
config_webrx: configuration options for OpenWebRX
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
Copyright (c) 2019-2021 by Jakob Ketterl <dd5jfk@darc.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
In addition, as a special exception, the copyright holders
state that config_rtl.py and config_webrx.py are not part of the
Corresponding Source defined in GNU AGPL version 3 section 1.
(It means that you do not have to redistribute config_rtl.py and
config_webrx.py if you make any changes to these two configuration files,
and use them for running your web service with OpenWebRX.)
"""
"""
DEPRECATION notice
As of OpenWebRX 0.21, the configuration system has been completely overhauled.
The configuration of OpenWebRX should now be done in the new web-based
configuration interface exclusively.
Existing configurations can still be used, but their values will be migrated
to the new storage infrastructure as soon as the web configuration is used to
edit them.
The new configuration storage is not intended to be edited manually.
"""
# configuration version. please only modify if you're able to perform the associated migration steps.
version = 7
# NOTE: you can find additional information about configuring OpenWebRX in the Wiki:
# https://github.com/jketterl/openwebrx/wiki/Configuration-guide
# ==== Server settings ====
#max_clients = 20
# ==== Web GUI configuration ====
#receiver_name = "[Callsign]"
#receiver_location = "Budapest, Hungary"
#receiver_asl = 200
#receiver_admin = "example@example.com"
#receiver_gps = {"lat": 47.000000, "lon": 19.000000}
#photo_title = "Panorama of Budapest from Schönherz Zoltán Dormitory"
# photo_desc allows you to put pretty much any HTML you like into the receiver description.
# The lines below should give you some examples of what's possible.
#photo_desc = """
#You can add your own background photo and receiver information.<br />
#Receiver is operated by: <a href="mailto:openwebrx@localhost" target="_blank">Receiver Operator</a><br/>
#Device: Receiver Device<br />
#Antenna: Receiver Antenna<br />
#Website: <a href="http://localhost" target="_blank">http://localhost</a>
#"""
# ==== Public receiver listings ====
# You can publish your receiver on online receiver directories, like https://www.receiverbook.de
# You will receive a receiver key from the directory that will authenticate you as the operator of this receiver.
# Please note that you not share your receiver keys publicly since anyone that obtains your receiver key can take over
# your public listing.
# Your receiver keys should be placed into this array:
#receiver_keys = []
# If you list your receiver on multiple sites, you can place all your keys into the array above, or you can append
# keys to the arraylike this:
# receiver_keys += ["my-receiver-key"]
# If you're not sure, simply copy & paste the code you received from your listing site below this line:
# ==== DSP/RX settings ====
#fft_fps = 9
#fft_size = 4096 # Should be power of 2
#fft_voverlap_factor = (
# 0.3 # If fft_voverlap_factor is above 0, multiple FFTs will be used for creating a line on the diagram.
#)
#audio_compression = "adpcm" # valid values: "adpcm", "none"
#fft_compression = "adpcm" # valid values: "adpcm", "none"
# Tau setting for WFM (broadcast FM) deemphasis\
# Quote from wikipedia https://en.wikipedia.org/wiki/FM_broadcasting#Pre-emphasis_and_de-emphasis
# "In most of the world a 50 µs time constant is used. In the Americas and South Korea, 75 µs is used"
# Enable one of the following lines, depending on your location:
# wfm_deemphasis_tau = 75e-6 # for US and South Korea
#wfm_deemphasis_tau = 50e-6 # for the rest of the world
#digimodes_fft_size = 2048
# determines the quality, and thus the cpu usage, for the ambe codec used by digital voice modes
# if you're running on a Raspi (up to 3B+) you'll want to leave this on 1
#digital_voice_unvoiced_quality = 1
# enables lookup of DMR ids using the radioid api
#digital_voice_dmr_id_lookup = True
"""
Note: if you experience audio underruns while CPU usage is 100%, you can:
- decrease `samp_rate`,
- set `fft_voverlap_factor` to 0,
- decrease `fft_fps` and `fft_size`,
- limit the number of users by decreasing `max_clients`.
"""
# ==== I/Q sources ====
# (Uncomment the appropriate by removing # characters at the beginning of the corresponding lines.)
###############################################################################
# Is my SDR hardware supported? #
# Check here: https://github.com/jketterl/openwebrx/wiki/Supported-Hardware #
###############################################################################
# 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", "uhd",
# "radioberry", "fcdpp", "rtl_tcp", "sddc", "runds"
# For more details on specific types, please checkout the wiki:
# https://github.com/jketterl/openwebrx/wiki/Supported-Hardware#sdr-devices
#sdrs = {
# "rtlsdr": {
# "name": "RTL-SDR USB Stick",
# "type": "rtl_sdr",
# "ppm": 0,
# # you can change this if you use an upconverter. formula is:
# # center_freq + lfo_offset = actual frequency on the sdr
# # "lfo_offset": 0,
# "profiles": {
# "70cm": {
# "name": "70cm Relais",
# "center_freq": 438800000,
# "rf_gain": 29,
# "samp_rate": 2400000,
# "start_freq": 439275000,
# "start_mod": "nfm",
# },
# "2m": {
# "name": "2m komplett",
# "center_freq": 145000000,
# "rf_gain": 29,
# "samp_rate": 2048000,
# "start_freq": 145725000,
# "start_mod": "nfm",
# },
# },
# },
# "airspy": {
# "name": "Airspy HF+",
# "type": "airspyhf",
# "ppm": 0,
# "rf_gain": "auto",
# "profiles": {
# "20m": {
# "name": "20m",
# "center_freq": 14150000,
# "samp_rate": 384000,
# "start_freq": 14070000,
# "start_mod": "usb",
# },
# "30m": {
# "name": "30m",
# "center_freq": 10125000,
# "samp_rate": 192000,
# "start_freq": 10142000,
# "start_mod": "usb",
# },
# "40m": {
# "name": "40m",
# "center_freq": 7100000,
# "samp_rate": 256000,
# "start_freq": 7070000,
# "start_mod": "lsb",
# },
# "80m": {
# "name": "80m",
# "center_freq": 3650000,
# "samp_rate": 384000,
# "start_freq": 3570000,
# "start_mod": "lsb",
# },
# "49m": {
# "name": "49m Broadcast",
# "center_freq": 6050000,
# "samp_rate": 384000,
# "start_freq": 6070000,
# "start_mod": "am",
# },
# },
# },
# "sdrplay": {
# "name": "SDRPlay RSP2",
# "type": "sdrplay",
# "ppm": 0,
# "antenna": "Antenna A",
# "profiles": {
# "20m": {
# "name": "20m",
# "center_freq": 14150000,
# "rf_gain": 0,
# "samp_rate": 500000,
# "start_freq": 14070000,
# "start_mod": "usb",
# },
# "30m": {
# "name": "30m",
# "center_freq": 10125000,
# "rf_gain": 0,
# "samp_rate": 250000,
# "start_freq": 10142000,
# "start_mod": "usb",
# },
# "40m": {
# "name": "40m",
# "center_freq": 7100000,
# "rf_gain": 0,
# "samp_rate": 500000,
# "start_freq": 7070000,
# "start_mod": "lsb",
# },
# "80m": {
# "name": "80m",
# "center_freq": 3650000,
# "rf_gain": 0,
# "samp_rate": 500000,
# "start_freq": 3570000,
# "start_mod": "lsb",
# },
# "49m": {
# "name": "49m Broadcast",
# "center_freq": 6000000,
# "rf_gain": 0,
# "samp_rate": 500000,
# "start_freq": 6070000,
# "start_mod": "am",
# },
# },
# },
#}
# ==== Color themes ====
### google turbo colormap (see: https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html)
#waterfall_scheme = "GoogleTurboWaterfall"
### original theme by teejez:
#waterfall_scheme = "TeejeezWaterfall"
### old theme by HA7ILM:
#waterfall_scheme = "Ha7ilmWaterfall"
##For the old colors, you might also want to set [fft_voverlap_factor] to 0.
### custom waterfall schemes can be configured like this:
#waterfall_scheme = "CustomWaterfall"
#waterfall_colors = [0x0000FF, 0x00FF00, 0xFF0000]
### Waterfall calibration
#waterfall_levels = {"min": -88, "max": -20} # in dB
#waterfall_auto_levels = {"min": 3, "max": 10}
#waterfall_auto_min_range = 50
# Note: When the auto waterfall level button is clicked, the following happens:
# [waterfall_levels.min] = [current_min_power_level] - [waterfall_auto_levels["min"]]
# [waterfall_levels.max] = [current_max_power_level] + [waterfall_auto_levels["max"]]
#
# ___|__________________________________|____________________________________|__________________________________|___> signal power
# \_waterfall_auto_levels["min"]_/ |__ current_min_power_level | \_waterfall_auto_levels["max"]_/
# current_max_power_level __|
# This setting allows you to modify the precision of the frequency displays in OpenWebRX.
# Set this to exponent of 10 to select the most precise digit in Hz you'd like to see
# examples:
# a value of 2 selects 10^2 = 100Hz tuning precision (default):
#tuning_precision = 2
# a value of 1 selects 10^1 = 10Hz tuning precision:
#tuning_precision = 1
# This setting tells the auto-squelch the offset to add to the current signal level to use as the new squelch level.
# Lowering this setting will give you a more sensitive squelch, but it may also cause unwanted squelch openings when
# using the auto squelch.
#squelch_auto_margin = 10 # in dB
#google_maps_api_key = ""
# how long should positions be visible on the map?
# they will start fading out after half of that
# in seconds; default: 2 hours
#map_position_retention_time = 2 * 60 * 60
# decoder queue configuration
# due to the nature of some operating modes (ft8, ft8, jt9, jt65, wspr and js8), the data is recorded for a given amount
# of time (6 seconds up to 2 minutes) and decoded at the end. this can lead to very high peak loads.
# to mitigate this, the recordings will be queued and processed in sequence.
# the number of workers will limit the total amount of work (one worker will losely occupy one cpu / thread)
#decoding_queue_workers = 2
# the maximum queue length will cause decodes to be dumped if the workers cannot keep up
# if you are running background services, make sure this number is high enough to accept the task influx during peaks
# i.e. this should be higher than the number of decoding services running at the same time
#decoding_queue_length = 10
# wsjt decoding depth will allow more results, but will also consume more cpu
#wsjt_decoding_depth = 3
# can also be set for each mode separately
# 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]
# Q65 allows many combinations of intervals and submodes. This setting determines which combinations will be decoded.
# Please use the mode letter followed by the decode interval in seconds to specify the combinations. For example:
#q65_enabled_combinations = ["A30", "E120", "C60"]
# 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
#js8_decoding_depth = 3
# Enable background service for decoding digital data. You can find more information at:
# https://github.com/jketterl/openwebrx/wiki/Background-decoding
#services_enabled = False
#services_decoders = ["ft8", "ft4", "wspr", "packet"]
# === aprs igate settings ===
# If you want to share your APRS decodes with the aprs network, configure these settings accordingly.
# Make sure that you have set services_enabled to true and customize services_decoders to your needs.
#aprs_callsign = "N0CALL"
#aprs_igate_enabled = False
#aprs_igate_server = "euro.aprs2.net"
#aprs_igate_password = ""
# beacon uses the receiver_gps setting, so if you enable this, make sure the location is correct there
#aprs_igate_beacon = False
# Uncomment the following to customize gateway beacon details reported to the aprs network
# Plese see Dire Wolf's documentation on PBEACON configuration for complete details:
# https://github.com/wb2osz/direwolf/raw/master/doc/User-Guide.pdf
# Symbol in its two-character form as specified by the APRS spec at http://www.aprs.org/symbols/symbols-new.txt
# Default: Receive only IGate (do not send msgs back to RF)
# aprs_igate_symbol = "R&"
# Custom comment about igate
# Default: OpenWebRX APRS gateway
# aprs_igate_comment = "OpenWebRX APRS gateway"
# Antenna Height and Gain details
# Unspecified by default
# Antenna height above average terrain (HAAT) in meters
# aprs_igate_height = "5"
# Antenna gain in dBi
# aprs_igate_gain = "0"
# Antenna direction (N, NE, E, SE, S, SW, W, NW). Omnidirectional by default
# aprs_igate_dir = "NE"
# === PSK Reporter settings ===
# enable this if you want to upload all ft8, ft4 etc spots to pskreporter.info
# this also uses the receiver_gps setting from above, so make sure it contains a correct locator
#pskreporter_enabled = False
#pskreporter_callsign = "N0CALL"
# optional antenna information, uncomment to enable
#pskreporter_antenna_information = "Dipole"
# === WSPRNet reporting settings
# enable this if you want to upload WSPR spots to wsprnet.ort
# in addition to these settings also make sure that receiver_gps contains your correct location
#wsprnet_enabled = False
#wsprnet_callsign = "N0CALL"

View File

@ -0,0 +1,836 @@
"""
OpenWebRX csdr plugin: do the signal processing with csdr
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
Copyright (c) 2019-2021 by Jakob Ketterl <dd5jfk@darc.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import subprocess
import os
import signal
import threading
import math
from functools import partial
from csdr.output import Output
from owrx.kiss import KissClient, DirewolfConfig, DirewolfConfigSubscriber
from owrx.audio.chopper import AudioChopper
from csdr.pipe import Pipe
import logging
logger = logging.getLogger(__name__)
class Dsp(DirewolfConfigSubscriber):
def __init__(self, output: Output):
self.samp_rate = 250000
self.output_rate = 11025
self.hd_output_rate = 44100
self.fft_size = 1024
self.fft_fps = 5
self.center_freq = 0
self.offset_freq = 0
self.low_cut = -4000
self.high_cut = 4000
self.bpf_transition_bw = 320 # Hz, and this is a constant
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
self.running = False
self.secondary_processes_running = False
self.audio_compression = "none"
self.fft_compression = "none"
self.demodulator = "nfm"
self.name = "csdr"
self.base_bufsize = 512
self.decimation = None
self.last_decimation = None
self.nc_port = None
self.squelch_level = -150
self.fft_averages = 50
self.wfm_deemphasis_tau = 50e-6
self.iqtee = False
self.iqtee2 = False
self.secondary_demodulator = None
self.secondary_fft_size = 1024
self.secondary_process_fft = None
self.secondary_process_demod = None
self.pipe_names = {
"bpf_pipe": Pipe.WRITE,
"shift_pipe": Pipe.WRITE,
"squelch_pipe": Pipe.WRITE,
"smeter_pipe": Pipe.READ,
"meta_pipe": Pipe.READ,
"iqtee_pipe": Pipe.NONE,
"iqtee2_pipe": Pipe.NONE,
"dmr_control_pipe": Pipe.WRITE,
}
self.pipes = {}
self.secondary_pipe_names = {"secondary_shift_pipe": Pipe.WRITE}
self.secondary_offset_freq = 1000
self.unvoiced_quality = 1
self.modification_lock = threading.Lock()
self.output = output
self.temporary_directory = None
self.pipe_base_path = None
self.set_temporary_directory("/tmp")
self.is_service = False
self.direwolf_config = None
self.direwolf_config_path = None
self.process = None
def set_service(self, flag=True):
self.is_service = flag
def set_temporary_directory(self, what):
self.temporary_directory = what
self.pipe_base_path = "{tmp_dir}/openwebrx_pipe_".format(tmp_dir=self.temporary_directory)
def chain(self, which):
chain = ["nc -v 127.0.0.1 {nc_port}"]
if which == "fft":
chain += [
"csdr fft_cc {fft_size} {fft_block_size}",
"csdr logpower_cf -70"
if self.fft_averages == 0
else "csdr logaveragepower_cf -70 {fft_size} {fft_averages}",
"csdr fft_exchange_sides_ff {fft_size}",
]
if self.fft_compression == "adpcm":
chain += ["csdr compress_fft_adpcm_f_u8 {fft_size}"]
return chain
chain += ["csdr shift_addfast_cc --fifo {shift_pipe}"]
if self.decimation > 1:
chain += ["csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING"]
chain += ["csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING"]
if self.output.supports_type("smeter"):
chain += [
"csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 {smeter_report_every}"
]
if self.secondary_demodulator:
if self.output.supports_type("secondary_fft"):
chain += ["csdr tee {iqtee_pipe}"]
chain += ["csdr tee {iqtee2_pipe}"]
# early exit if we don't want audio
if not self.output.supports_type("audio"):
return chain
# safe some cpu cycles... no need to decimate if decimation factor is 1
last_decimation_block = []
if self.last_decimation >= 2.0:
# activate prefilter if signal has been oversampled, e.g. WFM
last_decimation_block = ["csdr fractional_decimator_ff {last_decimation} 12 --prefilter"]
elif self.last_decimation != 1.0:
last_decimation_block = ["csdr fractional_decimator_ff {last_decimation}"]
if which == "nfm":
chain += ["csdr fmdemod_quadri_cf", "csdr limit_ff"]
chain += last_decimation_block
chain += [
"csdr deemphasis_nfm_ff {audio_rate}",
"csdr agc_ff --profile slow --max 3",
]
if self.get_audio_rate() != self.get_output_rate():
chain += [
"sox -t raw -r {audio_rate} -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - "
]
else:
chain += ["csdr convert_f_s16"]
elif which == "wfm":
chain += [
"csdr fmdemod_quadri_cf",
"csdr limit_ff",
]
chain += last_decimation_block
chain += ["csdr deemphasis_wfm_ff {audio_rate} {wfm_deemphasis_tau}", "csdr convert_f_s16"]
elif self.isDigitalVoice(which):
chain += ["csdr fmdemod_quadri_cf"]
chain += last_decimation_block
# dsd modes
if which in ["dstar", "nxdn"]:
chain += ["dc_block", "csdr limit_ff", "csdr convert_f_s16"]
if which == "dstar":
chain += ["dsd -fd -i - -o - -u {unvoiced_quality} -g -1 "]
elif which == "nxdn":
chain += ["dsd -fi -i - -o - -u {unvoiced_quality} -g -1 "]
chain += [
"digitalvoice_filter",
"CSDR_FIXED_BUFSIZE=32 csdr agc_s16 --max 30 --initial 3",
"sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
# m17
elif which == "m17":
chain += [
"dc_block",
"csdr limit_ff",
"csdr convert_f_s16",
"m17-demod",
"CSDR_FIXED_BUFSIZE=32 csdr agc_s16 --max 30 --initial 3",
"sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
# digiham modes
else:
chain += ["dc_block", "rrc_filter", "gfsk_demodulator"]
if which == "dmr":
chain += [
"dmr_decoder --fifo {meta_pipe} --control-fifo {dmr_control_pipe}",
"mbe_synthesizer -f -u {unvoiced_quality}",
]
elif which == "ysf":
chain += ["ysf_decoder --fifo {meta_pipe}", "mbe_synthesizer -y -f -u {unvoiced_quality}"]
max_gain = 0.005
chain += [
"digitalvoice_filter -f",
"CSDR_FIXED_BUFSIZE=32 csdr agc_ff --max 0.005 --initial 0.0005",
"sox -t raw -r 8000 -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
elif which == "am":
chain += ["csdr amdemod_cf", "csdr fastdcblock_ff"]
chain += last_decimation_block
chain += [
"csdr agc_ff --profile slow --initial 200",
"csdr convert_f_s16",
]
elif self.isFreeDV(which):
chain += ["csdr realpart_cf"]
chain += last_decimation_block
chain += [
"csdr agc_ff",
"csdr convert_f_s16",
"freedv_rx 1600 - -",
"csdr agc_s16 --max 30 --initial 3",
"sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
elif self.isDrm(which):
if self.last_decimation != 1.0:
# we are still dealing with complex samples here, so the regular last_decimation_block doesn't fit
chain += ["csdr fractional_decimator_cc {last_decimation}"]
chain += [
"csdr convert_f_s16",
"dream -c 6 --sigsrate 48000 --audsrate 48000 -I - -O -",
"sox -t raw -r 48000 -e signed-integer -b 16 -c 2 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
elif which == "ssb":
chain += ["csdr realpart_cf"]
chain += last_decimation_block
chain += ["csdr agc_ff"]
# fixed sample rate necessary for the wsjt-x tools. fix with sox...
if self.get_audio_rate() != self.get_output_rate():
chain += [
"sox -t raw -r {audio_rate} -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - "
]
else:
chain += ["csdr convert_f_s16"]
if self.audio_compression == "adpcm":
chain += ["csdr encode_ima_adpcm_i16_u8"]
return chain
def secondary_chain(self, which):
chain = ["cat {input_pipe}"]
if which == "fft":
chain += [
"csdr fft_cc {secondary_fft_input_size} {secondary_fft_block_size}",
"csdr logpower_cf -70"
if self.fft_averages == 0
else "csdr logaveragepower_cf -70 {secondary_fft_size} {fft_averages}",
"csdr fft_exchange_sides_ff {secondary_fft_input_size}",
]
if self.fft_compression == "adpcm":
chain += ["csdr compress_fft_adpcm_f_u8 {secondary_fft_size}"]
return chain
elif which == "bpsk31" or which == "bpsk63":
return chain + [
"csdr shift_addfast_cc --fifo {secondary_shift_pipe}",
"csdr bandpass_fir_fft_cc -{secondary_bpf_cutoff} {secondary_bpf_cutoff} {secondary_bpf_cutoff}",
"csdr simple_agc_cc 0.001 0.5",
"csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q",
"CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8",
"CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8",
]
elif self.isWsjtMode(which) or self.isJs8(which):
chain += ["csdr realpart_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["csdr agc_ff", "csdr convert_f_s16"]
elif which == "packet":
chain += ["csdr fmdemod_quadri_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["csdr convert_f_s16", "direwolf -c {direwolf_config} -r {audio_rate} -t 0 -q d -q h 1>&2"]
elif which == "pocsag":
chain += ["csdr fmdemod_quadri_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["fsk_demodulator -i", "pocsag_decoder"]
def set_secondary_demodulator(self, what):
if self.get_secondary_demodulator() == what:
return
self.secondary_demodulator = what
self.calculate_decimation()
self.restart()
def secondary_fft_block_size(self):
base = (self.samp_rate / self.decimation) / (self.fft_fps * 2)
if self.fft_averages == 0:
return base
return base / self.fft_averages
def secondary_decimation(self):
return 1 # currently unused
def secondary_bpf_cutoff(self):
if self.secondary_demodulator == "bpsk31":
return 31.25 / self.if_samp_rate()
elif self.secondary_demodulator == "bpsk63":
return 62.5 / self.if_samp_rate()
return 0
def secondary_bpf_transition_bw(self):
if self.secondary_demodulator == "bpsk31":
return 31.25 / self.if_samp_rate()
elif self.secondary_demodulator == "bpsk63":
return 62.5 / self.if_samp_rate()
return 0
def secondary_samples_per_bits(self):
if self.secondary_demodulator == "bpsk31":
return int(round(self.if_samp_rate() / 31.25)) & ~3
elif self.secondary_demodulator == "bpsk63":
return int(round(self.if_samp_rate() / 62.5)) & ~3
return 0
def secondary_bw(self):
if self.secondary_demodulator == "bpsk31":
return 31.25
elif self.secondary_demodulator == "bpsk63":
return 62.5
def start_secondary_demodulator(self):
if not self.secondary_demodulator:
return
logger.debug("starting secondary demodulator from IF input sampled at %d" % self.if_samp_rate())
secondary_command_demod = " | ".join(self.secondary_chain(self.secondary_demodulator))
self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod)
self.try_create_configs(secondary_command_demod)
secondary_command_demod = secondary_command_demod.format(
input_pipe=self.pipes["iqtee2_pipe"],
secondary_shift_pipe=self.pipes["secondary_shift_pipe"],
secondary_decimation=self.secondary_decimation(),
secondary_samples_per_bits=self.secondary_samples_per_bits(),
secondary_bpf_cutoff=self.secondary_bpf_cutoff(),
secondary_bpf_transition_bw=self.secondary_bpf_transition_bw(),
if_samp_rate=self.if_samp_rate(),
last_decimation=self.last_decimation,
audio_rate=self.get_audio_rate(),
direwolf_config=self.direwolf_config_path,
)
logger.debug("secondary command (demod) = %s", secondary_command_demod)
if self.output.supports_type("secondary_fft"):
secondary_command_fft = " | ".join(self.secondary_chain("fft"))
secondary_command_fft = secondary_command_fft.format(
input_pipe=self.pipes["iqtee_pipe"],
secondary_fft_input_size=self.secondary_fft_size,
secondary_fft_size=self.secondary_fft_size,
secondary_fft_block_size=self.secondary_fft_block_size(),
fft_averages=self.fft_averages,
)
logger.debug("secondary command (fft) = %s", secondary_command_fft)
self.secondary_process_fft = subprocess.Popen(
secondary_command_fft, stdout=subprocess.PIPE, shell=True, start_new_session=True
)
self.output.send_output(
"secondary_fft",
partial(self.secondary_process_fft.stdout.read, int(self.get_secondary_fft_bytes_to_read())),
)
# direwolf does not provide any meaningful data on stdout
# more specifically, it doesn't provide any data. if however, for any strange reason, it would start to do so,
# it would block if not read. by piping it to devnull, we avoid a potential pitfall here.
secondary_output = subprocess.DEVNULL if self.isPacket() else subprocess.PIPE
self.secondary_process_demod = subprocess.Popen(
secondary_command_demod, stdout=secondary_output, shell=True, start_new_session=True
)
self.secondary_processes_running = True
if self.isWsjtMode() or self.isJs8():
chopper = AudioChopper(self, self.get_secondary_demodulator())
chopper.send_output("audio", self.secondary_process_demod.stdout.read)
output_type = "js8_demod" if self.isJs8() else "wsjt_demod"
self.output.send_output(output_type, chopper.read)
elif self.isPacket():
# we best get the ax25 packets from the kiss socket
kiss = KissClient(self.direwolf_config.getPort())
self.output.send_output("packet_demod", kiss.read)
elif self.isPocsag():
self.output.send_output("pocsag_demod", self.secondary_process_demod.stdout.readline)
else:
self.output.send_output("secondary_demod", partial(self.secondary_process_demod.stdout.read, 1))
# open control pipes for csdr and send initialization data
if self.has_pipe("secondary_shift_pipe"): # TODO digimodes
self.set_secondary_offset_freq(self.secondary_offset_freq) # TODO digimodes
def set_secondary_offset_freq(self, value):
self.secondary_offset_freq = value
if self.secondary_processes_running and self.has_pipe("secondary_shift_pipe"):
self.pipes["secondary_shift_pipe"].write(
"%g\n" % (-float(self.secondary_offset_freq) / self.if_samp_rate())
)
def stop_secondary_demodulator(self):
if not self.secondary_processes_running:
return
self.try_delete_pipes(self.secondary_pipe_names)
self.try_delete_configs()
if self.secondary_process_fft:
try:
os.killpg(os.getpgid(self.secondary_process_fft.pid), signal.SIGTERM)
# drain any leftover data to free file descriptors
self.secondary_process_fft.communicate()
self.secondary_process_fft = None
except ProcessLookupError:
# been killed by something else, ignore
pass
if self.secondary_process_demod:
try:
os.killpg(os.getpgid(self.secondary_process_demod.pid), signal.SIGTERM)
# drain any leftover data to free file descriptors
self.secondary_process_demod.communicate()
self.secondary_process_demod = None
except ProcessLookupError:
# been killed by something else, ignore
pass
self.secondary_processes_running = False
def get_secondary_demodulator(self):
return self.secondary_demodulator
def set_secondary_fft_size(self, secondary_fft_size):
if self.secondary_fft_size == secondary_fft_size:
return
self.secondary_fft_size = secondary_fft_size
self.restart()
def set_audio_compression(self, what):
if self.audio_compression == what:
return
self.audio_compression = what
self.restart()
def get_audio_bytes_to_read(self):
# desired latency: 5ms
# uncompressed audio has 16 bits = 2 bytes per sample
base = self.output_rate * 0.005 * 2
# adpcm compresses the bitstream by 4
if self.audio_compression == "adpcm":
base = base / 4
return int(base)
def set_fft_compression(self, what):
if self.fft_compression == what:
return
self.fft_compression = what
self.restart()
def get_fft_bytes_to_read(self):
if self.fft_compression == "none":
return self.fft_size * 4
if self.fft_compression == "adpcm":
return int((self.fft_size / 2) + (10 / 2))
def get_secondary_fft_bytes_to_read(self):
if self.fft_compression == "none":
return self.secondary_fft_size * 4
if self.fft_compression == "adpcm":
return (self.secondary_fft_size / 2) + (10 / 2)
def set_samp_rate(self, samp_rate):
self.samp_rate = samp_rate
self.calculate_decimation()
if self.running:
self.restart()
def calculate_decimation(self):
(self.decimation, self.last_decimation) = self.get_decimation(self.samp_rate, self.get_audio_rate())
def get_decimation(self, input_rate, output_rate):
if output_rate <= 0:
raise ValueError("invalid output rate: {rate}".format(rate=output_rate))
decimation = 1
target_rate = output_rate
# wideband fm has a much higher frequency deviation (75kHz).
# we cannot cover this if we immediately decimate to the sample rate the audio will have later on, so we need
# to compensate here.
if self.get_demodulator() == "wfm" and output_rate < 200000:
target_rate = 200000
while input_rate / (decimation + 1) >= target_rate:
decimation += 1
fraction = float(input_rate / decimation) / output_rate
return decimation, fraction
def if_samp_rate(self):
return self.samp_rate / self.decimation
def get_name(self):
return self.name
def get_output_rate(self):
return self.output_rate
def get_hd_output_rate(self):
return self.hd_output_rate
def get_audio_rate(self):
if self.isDigitalVoice() or self.isPacket() or self.isPocsag() or self.isDrm():
return 48000
elif self.isWsjtMode() or self.isJs8():
return 12000
elif self.isFreeDV():
return 8000
elif self.isHdAudio():
return self.get_hd_output_rate()
return self.get_output_rate()
def isDigitalVoice(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator in ["dmr", "dstar", "nxdn", "ysf", "m17"]
def isWsjtMode(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w", "q65"]
def isJs8(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "js8"
def isPacket(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "packet"
def isPocsag(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "pocsag"
def isFreeDV(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator == "freedv"
def isHdAudio(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator == "wfm"
def isDrm(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator == "drm"
def set_output_rate(self, output_rate):
if self.output_rate == output_rate:
return
self.output_rate = output_rate
self.calculate_decimation()
self.restart()
def set_hd_output_rate(self, hd_output_rate):
if self.hd_output_rate == hd_output_rate:
return
self.hd_output_rate = hd_output_rate
self.calculate_decimation()
self.restart()
def set_demodulator(self, demodulator):
if demodulator in ["usb", "lsb", "cw"]:
demodulator = "ssb"
if self.demodulator == demodulator:
return
self.demodulator = demodulator
self.calculate_decimation()
self.restart()
def get_demodulator(self):
return self.demodulator
def set_fft_size(self, fft_size):
if self.fft_size == fft_size:
return
self.fft_size = fft_size
self.restart()
def set_fft_fps(self, fft_fps):
self.fft_fps = fft_fps
self.restart()
def set_fft_averages(self, fft_averages):
self.fft_averages = fft_averages
self.restart()
def fft_block_size(self):
if self.fft_averages == 0:
return self.samp_rate / self.fft_fps
else:
return self.samp_rate / self.fft_fps / self.fft_averages
def set_offset_freq(self, offset_freq):
if offset_freq is None:
return
self.offset_freq = offset_freq
if self.running:
self.pipes["shift_pipe"].write("%g\n" % (-float(self.offset_freq) / self.samp_rate))
def set_center_freq(self, center_freq):
# dsp only needs to know this to be able to pass it to decoders in the form of get_operating_freq()
self.center_freq = center_freq
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
if self.running:
self.pipes["bpf_pipe"].write(
"%g %g\n" % (float(self.low_cut) / self.if_samp_rate(), float(self.high_cut) / self.if_samp_rate())
)
def get_bpf(self):
return [self.low_cut, self.high_cut]
def convertToLinear(self, db):
return float(math.pow(10, db / 10))
def set_squelch_level(self, squelch_level):
self.squelch_level = squelch_level
# no squelch required on digital voice modes
actual_squelch = (
-150
if self.isDigitalVoice() or self.isPacket() or self.isPocsag() or self.isFreeDV()
else self.squelch_level
)
if self.running:
self.pipes["squelch_pipe"].write("%g\n" % (self.convertToLinear(actual_squelch)))
def set_unvoiced_quality(self, q):
self.unvoiced_quality = q
self.restart()
def get_unvoiced_quality(self):
return self.unvoiced_quality
def set_dmr_filter(self, filter):
if self.has_pipe("dmr_control_pipe"):
self.pipes["dmr_control_pipe"].write("{0}\n".format(filter))
def set_wfm_deemphasis_tau(self, tau):
if self.wfm_deemphasis_tau == tau:
return
self.wfm_deemphasis_tau = tau
self.restart()
def ddc_transition_bw(self):
return self.ddc_transition_bw_rate * (self.if_samp_rate() / float(self.samp_rate))
def try_create_pipes(self, pipe_names, command_base):
for pipe_name, pipe_type in pipe_names.items():
if self.has_pipe(pipe_name):
logger.warning("{pipe_name} is still in use", pipe_name=pipe_name)
self.pipes[pipe_name].close()
if "{" + pipe_name + "}" in command_base:
p = self.pipe_base_path + pipe_name
encoding = None
# TODO make digiham output unicode and then change this here
# the whole pipe enoding feature onlye exists because of this
if pipe_name == "meta_pipe":
encoding = "cp437"
self.pipes[pipe_name] = Pipe.create(p, pipe_type, encoding=encoding)
else:
self.pipes[pipe_name] = None
def has_pipe(self, name):
return name in self.pipes and self.pipes[name] is not None
def try_delete_pipes(self, pipe_names):
for pipe_name in pipe_names:
if self.has_pipe(pipe_name):
self.pipes[pipe_name].close()
self.pipes[pipe_name] = None
def try_create_configs(self, command):
if "{direwolf_config}" in command:
self.direwolf_config_path = "{tmp_dir}/openwebrx_direwolf_{myid}.conf".format(
tmp_dir=self.temporary_directory, myid=id(self)
)
self.direwolf_config = DirewolfConfig()
self.direwolf_config.wire(self)
file = open(self.direwolf_config_path, "w")
file.write(self.direwolf_config.getConfig(self.is_service))
file.close()
else:
self.direwolf_config = None
self.direwolf_config_path = None
def try_delete_configs(self):
if self.direwolf_config is not None:
self.direwolf_config.unwire(self)
self.direwolf_config = None
if self.direwolf_config_path is not None:
try:
os.unlink(self.direwolf_config_path)
except FileNotFoundError:
# result suits our expectations. fine :)
pass
except Exception:
logger.exception("try_delete_configs()")
self.direwolf_config_path = None
def onConfigChanged(self):
self.restart()
def start(self):
with self.modification_lock:
if self.running:
return
self.running = True
command_base = " | ".join(self.chain(self.demodulator))
# create control pipes for csdr
self.try_create_pipes(self.pipe_names, command_base)
# send initial config through the pipes
if self.has_pipe("bpf_pipe"):
self.set_bpf(self.low_cut, self.high_cut)
if self.has_pipe("shift_pipe"):
self.set_offset_freq(self.offset_freq)
if self.has_pipe("squelch_pipe"):
self.set_squelch_level(self.squelch_level)
if self.has_pipe("dmr_control_pipe"):
self.set_dmr_filter(3)
# run the command
command = command_base.format(
bpf_pipe=self.pipes["bpf_pipe"],
shift_pipe=self.pipes["shift_pipe"],
squelch_pipe=self.pipes["squelch_pipe"],
smeter_pipe=self.pipes["smeter_pipe"],
meta_pipe=self.pipes["meta_pipe"],
iqtee_pipe=self.pipes["iqtee_pipe"],
iqtee2_pipe=self.pipes["iqtee2_pipe"],
dmr_control_pipe=self.pipes["dmr_control_pipe"],
decimation=self.decimation,
last_decimation=self.last_decimation,
fft_size=self.fft_size,
fft_block_size=self.fft_block_size(),
fft_averages=self.fft_averages,
bpf_transition_bw=float(self.bpf_transition_bw) / self.if_samp_rate(),
ddc_transition_bw=self.ddc_transition_bw(),
flowcontrol=int(self.samp_rate * 2),
start_bufsize=self.base_bufsize * self.decimation,
nc_port=self.nc_port,
output_rate=self.get_output_rate(),
smeter_report_every=int(self.if_samp_rate() / 6000),
unvoiced_quality=self.get_unvoiced_quality(),
audio_rate=self.get_audio_rate(),
wfm_deemphasis_tau=self.wfm_deemphasis_tau,
)
logger.debug("Command = %s", command)
out = subprocess.PIPE if self.output.supports_type("audio") else subprocess.DEVNULL
self.process = subprocess.Popen(command, stdout=out, shell=True, start_new_session=True)
def watch_thread():
rc = self.process.wait()
logger.debug("dsp thread ended with rc=%d", rc)
if rc == 0 and self.running and not self.modification_lock.locked():
logger.debug("restarting since rc = 0, self.running = true, and no modification")
self.restart()
threading.Thread(target=watch_thread, name="csdr_watch_thread").start()
audio_type = "hd_audio" if self.isHdAudio() else "audio"
if self.output.supports_type(audio_type):
self.output.send_output(
audio_type,
partial(
self.process.stdout.read,
self.get_fft_bytes_to_read() if self.demodulator == "fft" else self.get_audio_bytes_to_read(),
),
)
self.start_secondary_demodulator()
if self.has_pipe("smeter_pipe"):
def read_smeter():
raw = self.pipes["smeter_pipe"].readline()
if len(raw) == 0:
return None
else:
return float(raw.rstrip("\n"))
self.output.send_output("smeter", read_smeter)
if self.has_pipe("meta_pipe"):
def read_meta():
raw = self.pipes["meta_pipe"].readline()
if len(raw) == 0:
return None
else:
return raw.rstrip("\n")
self.output.send_output("meta", read_meta)
def stop(self):
with self.modification_lock:
self.running = False
if self.process is not None:
try:
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
# drain any leftover data to free file descriptors
self.process.communicate()
self.process = None
except ProcessLookupError:
# been killed by something else, ignore
pass
self.stop_secondary_demodulator()
self.try_delete_pipes(self.pipe_names)
self.try_delete_configs()
def restart(self):
if not self.running:
return
self.stop()
self.start()

View File

@ -1,142 +0,0 @@
from csdr.module import Module
from pycsdr.modules import Buffer
from pycsdr.types import Format
from typing import Union, Callable, Optional
class Chain(Module):
def __init__(self, workers):
super().__init__()
self.workers = workers
for i in range(1, len(self.workers)):
self._connect(self.workers[i - 1], self.workers[i])
def empty(self):
return not self.workers
def _connect(self, w1, w2, buffer: Optional[Buffer] = None) -> None:
if buffer is None:
buffer = Buffer(w1.getOutputFormat())
w1.setWriter(buffer)
w2.setReader(buffer.getReader())
def setReader(self, reader):
if self.reader is reader:
return
super().setReader(reader)
if self.workers:
self.workers[0].setReader(reader)
def setWriter(self, writer):
if self.writer is writer:
return
super().setWriter(writer)
if self.workers:
self.workers[-1].setWriter(writer)
def indexOf(self, search: Union[Callable, object]) -> int:
def searchFn(x):
if callable(search):
return search(x)
else:
return x is search
try:
return next(i for i, v in enumerate(self.workers) if searchFn(v))
except StopIteration:
return -1
def replace(self, index, newWorker):
if index >= len(self.workers):
raise IndexError("Index {} does not exist".format(index))
self.workers[index].stop()
self.workers[index] = newWorker
error = None
if index == 0:
if self.reader is not None:
newWorker.setReader(self.reader)
else:
try:
previousWorker = self.workers[index - 1]
self._connect(previousWorker, newWorker)
except ValueError as e:
# store error for later raising, but still attempt the second connection
error = e
if index == len(self.workers) - 1:
if self.writer is not None:
newWorker.setWriter(self.writer)
else:
try:
nextWorker = self.workers[index + 1]
self._connect(newWorker, nextWorker)
except ValueError as e:
error = e
if error is not None:
raise error
def append(self, newWorker):
previousWorker = None
if self.workers:
previousWorker = self.workers[-1]
self.workers.append(newWorker)
if previousWorker:
self._connect(previousWorker, newWorker)
elif self.reader is not None:
newWorker.setReader(self.reader)
if self.writer is not None:
newWorker.setWriter(self.writer)
def insert(self, newWorker):
nextWorker = None
if self.workers:
nextWorker = self.workers[0]
self.workers.insert(0, newWorker)
if nextWorker:
self._connect(newWorker, nextWorker)
elif self.writer is not None:
newWorker.setWriter(self.writer)
if self.reader is not None:
newWorker.setReader(self.reader)
def remove(self, index):
removedWorker = self.workers[index]
self.workers.remove(removedWorker)
removedWorker.stop()
if index == 0:
if self.reader is not None and len(self.workers):
self.workers[0].setReader(self.reader)
elif index == len(self.workers):
if self.writer is not None:
self.workers[-1].setWriter(self.writer)
else:
previousWorker = self.workers[index - 1]
nextWorker = self.workers[index]
self._connect(previousWorker, nextWorker)
def stop(self):
for w in self.workers:
w.stop()
def getInputFormat(self) -> Format:
if self.workers:
return self.workers[0].getInputFormat()
else:
raise BufferError("getInputFormat on empty chain")
def getOutputFormat(self) -> Format:
if self.workers:
return self.workers[-1].getOutputFormat()
else:
raise BufferError("getOutputFormat on empty chain")

View File

@ -1,76 +0,0 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, HdAudio, DeemphasisTauChain
from pycsdr.modules import AmDemod, DcBlock, FmDemod, Limit, NfmDeemphasis, Agc, WfmDeemphasis, FractionalDecimator, RealPart
from pycsdr.types import Format, AgcProfile
class Am(BaseDemodulatorChain):
def __init__(self):
agc = Agc(Format.FLOAT)
agc.setProfile(AgcProfile.SLOW)
agc.setInitialGain(200)
workers = [
AmDemod(),
DcBlock(),
agc,
]
super().__init__(workers)
class NFm(BaseDemodulatorChain):
def __init__(self, sampleRate: int):
self.sampleRate = sampleRate
agc = Agc(Format.FLOAT)
agc.setProfile(AgcProfile.SLOW)
agc.setMaxGain(3)
workers = [
FmDemod(),
Limit(),
NfmDeemphasis(sampleRate),
agc,
]
super().__init__(workers)
def setSampleRate(self, sampleRate: int) -> None:
if sampleRate == self.sampleRate:
return
self.sampleRate = sampleRate
self.replace(2, NfmDeemphasis(sampleRate))
class WFm(BaseDemodulatorChain, FixedIfSampleRateChain, DeemphasisTauChain, HdAudio):
def __init__(self, sampleRate: int, tau: float):
self.sampleRate = sampleRate
self.tau = tau
workers = [
FmDemod(),
Limit(),
FractionalDecimator(Format.FLOAT, 200000.0 / self.sampleRate, prefilter=True),
WfmDeemphasis(self.sampleRate, self.tau),
]
super().__init__(workers)
def getFixedIfSampleRate(self):
return 200000
def setDeemphasisTau(self, tau: float) -> None:
if tau == self.tau:
return
self.tau = tau
self.replace(3, WfmDeemphasis(self.sampleRate, self.tau))
def setSampleRate(self, sampleRate: int) -> None:
if sampleRate == self.sampleRate:
return
self.sampleRate = sampleRate
self.replace(2, FractionalDecimator(Format.FLOAT, 200000.0 / self.sampleRate, prefilter=True))
self.replace(3, WfmDeemphasis(self.sampleRate, self.tau))
class Ssb(BaseDemodulatorChain):
def __init__(self):
workers = [
RealPart(),
Agc(Format.FLOAT),
]
super().__init__(workers)

View File

@ -1,72 +0,0 @@
from csdr.chain import Chain
from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, Limit
from pycsdr.types import Format
class Converter(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int):
workers = []
if inputRate != clientRate:
# we only have an audio resampler for float ATM so if we need to resample, we need to convert
if format != Format.FLOAT:
workers += [Convert(format, Format.FLOAT)]
workers += [AudioResampler(inputRate, clientRate), Limit(), Convert(Format.FLOAT, Format.SHORT)]
elif format != Format.SHORT:
workers += [Convert(format, Format.SHORT)]
super().__init__(workers)
class ClientAudioChain(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int, compression: str):
self.format = format
self.inputRate = inputRate
self.clientRate = clientRate
workers = []
converter = self._buildConverter()
if not converter.empty():
workers += [converter]
if compression == "adpcm":
workers += [AdpcmEncoder(sync=True)]
super().__init__(workers)
def _buildConverter(self):
return Converter(self.format, self.inputRate, self.clientRate)
def _updateConverter(self):
converter = self._buildConverter()
index = self.indexOf(lambda x: isinstance(x, Converter))
if converter.empty():
if index >= 0:
self.remove(index)
else:
if index >= 0:
self.replace(index, converter)
else:
self.insert(converter)
def setFormat(self, format: Format) -> None:
if format == self.format:
return
self.format = format
self._updateConverter()
def setInputRate(self, inputRate: int) -> None:
if inputRate == self.inputRate:
return
self.inputRate = inputRate
self._updateConverter()
def setClientRate(self, clientRate: int) -> None:
if clientRate == self.clientRate:
return
self.clientRate = clientRate
self._updateConverter()
def setAudioCompression(self, compression: str) -> None:
index = self.indexOf(lambda x: isinstance(x, AdpcmEncoder))
if compression == "adpcm":
if index < 0:
self.append(AdpcmEncoder(sync=True))
else:
if index >= 0:
self.remove(index)

View File

@ -1,73 +0,0 @@
from csdr.chain import Chain
from abc import ABC, ABCMeta, abstractmethod
from pycsdr.modules import Writer
class FixedAudioRateChain(ABC):
@abstractmethod
def getFixedAudioRate(self) -> int:
pass
class FixedIfSampleRateChain(ABC):
@abstractmethod
def getFixedIfSampleRate(self) -> int:
pass
class DialFrequencyReceiver(ABC):
@abstractmethod
def setDialFrequency(self, frequency: int) -> None:
pass
# marker interface
class HdAudio:
pass
class MetaProvider(ABC):
@abstractmethod
def setMetaWriter(self, writer: Writer) -> None:
pass
class SlotFilterChain(ABC):
@abstractmethod
def setSlotFilter(self, filter: int) -> None:
pass
class SecondarySelectorChain(ABC):
def getBandwidth(self) -> float:
pass
class DeemphasisTauChain(ABC):
@abstractmethod
def setDeemphasisTau(self, tau: float) -> None:
pass
class BaseDemodulatorChain(Chain):
def supportsSquelch(self) -> bool:
return True
def setSampleRate(self, sampleRate: int) -> None:
pass
class SecondaryDemodulator(Chain):
def supportsSquelch(self) -> bool:
return True
def setSampleRate(self, sampleRate: int) -> None:
pass
class ServiceDemodulator(SecondaryDemodulator, FixedAudioRateChain, metaclass=ABCMeta):
pass
class DemodulatorError(Exception):
pass

View File

@ -1,133 +0,0 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedAudioRateChain, FixedIfSampleRateChain, DialFrequencyReceiver, MetaProvider, SlotFilterChain, DemodulatorError, ServiceDemodulator
from pycsdr.modules import FmDemod, Agc, Writer, Buffer
from pycsdr.types import Format
from digiham.modules import DstarDecoder, DcBlock, FskDemodulator, GfskDemodulator, DigitalVoiceFilter, MbeSynthesizer, NarrowRrcFilter, NxdnDecoder, DmrDecoder, WideRrcFilter, YsfDecoder, PocsagDecoder
from digiham.ambe import Modes, ServerError
from owrx.meta import MetaParser
from owrx.pocsag import PocsagParser
class DigihamChain(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, DialFrequencyReceiver, MetaProvider):
def __init__(self, fskDemodulator, decoder, mbeMode, filter=None, codecserver: str = ""):
self.decoder = decoder
if codecserver is None:
codecserver = ""
agc = Agc(Format.SHORT)
agc.setMaxGain(30)
agc.setInitialGain(3)
workers = [FmDemod(), DcBlock()]
if filter is not None:
workers += [filter]
try:
mbeSynthesizer = MbeSynthesizer(mbeMode, codecserver)
except ConnectionError as ce:
raise DemodulatorError("Connection to codecserver failed: {}".format(ce))
except ServerError as se:
raise DemodulatorError("Codecserver error: {}".format(se))
workers += [
fskDemodulator,
decoder,
mbeSynthesizer,
DigitalVoiceFilter(),
agc
]
self.metaParser = None
self.dialFrequency = None
super().__init__(workers)
def getFixedIfSampleRate(self):
return 48000
def getFixedAudioRate(self):
return 8000
def setMetaWriter(self, writer: Writer) -> None:
if self.metaParser is None:
self.metaParser = MetaParser()
buffer = Buffer(Format.CHAR)
self.decoder.setMetaWriter(buffer)
self.metaParser.setReader(buffer.getReader())
if self.dialFrequency is not None:
self.metaParser.setDialFrequency(self.dialFrequency)
self.metaParser.setWriter(writer)
def supportsSquelch(self):
return False
def setDialFrequency(self, frequency: int) -> None:
self.dialFrequency = frequency
if self.metaParser is None:
return
self.metaParser.setDialFrequency(frequency)
def stop(self):
if self.metaParser is not None:
self.metaParser.stop()
super().stop()
class Dstar(DigihamChain):
def __init__(self, codecserver: str = ""):
super().__init__(
fskDemodulator=FskDemodulator(samplesPerSymbol=10),
decoder=DstarDecoder(),
mbeMode=Modes.DStarMode,
codecserver=codecserver
)
class Nxdn(DigihamChain):
def __init__(self, codecserver: str = ""):
super().__init__(
fskDemodulator=GfskDemodulator(samplesPerSymbol=20),
decoder=NxdnDecoder(),
mbeMode=Modes.NxdnMode,
filter=NarrowRrcFilter(),
codecserver=codecserver
)
class Dmr(DigihamChain, SlotFilterChain):
def __init__(self, codecserver: str = ""):
super().__init__(
fskDemodulator=GfskDemodulator(samplesPerSymbol=10),
decoder=DmrDecoder(),
mbeMode=Modes.DmrMode,
filter=WideRrcFilter(),
codecserver=codecserver,
)
def setSlotFilter(self, slotFilter: int) -> None:
self.decoder.setSlotFilter(slotFilter)
class Ysf(DigihamChain):
def __init__(self, codecserver: str = ""):
super().__init__(
fskDemodulator=GfskDemodulator(samplesPerSymbol=10),
decoder=YsfDecoder(),
mbeMode=Modes.YsfMode,
filter=WideRrcFilter(),
codecserver=codecserver
)
class PocsagDemodulator(ServiceDemodulator, DialFrequencyReceiver):
def __init__(self):
self.parser = PocsagParser()
workers = [
FmDemod(),
FskDemodulator(samplesPerSymbol=40, invert=True),
PocsagDecoder(),
self.parser,
]
super().__init__(workers)
def supportsSquelch(self) -> bool:
return False
def getFixedAudioRate(self) -> int:
return 48000
def setDialFrequency(self, frequency: int) -> None:
self.parser.setDialFrequency(frequency)

View File

@ -1,86 +0,0 @@
from csdr.chain.demodulator import ServiceDemodulator, SecondaryDemodulator, DialFrequencyReceiver, SecondarySelectorChain
from csdr.module.msk144 import Msk144Module, ParserAdapter
from owrx.audio.chopper import AudioChopper, AudioChopperParser
from owrx.aprs.kiss import KissDeframer
from owrx.aprs import Ax25Parser, AprsParser
from pycsdr.modules import Convert, FmDemod, Agc, TimingRecovery, DBPskDecoder, VaricodeDecoder
from pycsdr.types import Format
from owrx.aprs.module import DirewolfModule
class AudioChopperDemodulator(ServiceDemodulator, DialFrequencyReceiver):
def __init__(self, mode: str, parser: AudioChopperParser):
self.chopper = AudioChopper(mode, parser)
workers = [Convert(Format.FLOAT, Format.SHORT), self.chopper]
super().__init__(workers)
def getFixedAudioRate(self):
return 12000
def setDialFrequency(self, frequency: int) -> None:
self.chopper.setDialFrequency(frequency)
class Msk144Demodulator(ServiceDemodulator, DialFrequencyReceiver):
def __init__(self):
self.parser = ParserAdapter()
workers = [
Convert(Format.FLOAT, Format.SHORT),
Msk144Module(),
self.parser,
]
super().__init__(workers)
def getFixedAudioRate(self) -> int:
return 12000
def setDialFrequency(self, frequency: int) -> None:
self.parser.setDialFrequency(frequency)
class PacketDemodulator(ServiceDemodulator, DialFrequencyReceiver):
def __init__(self, service: bool = False):
self.parser = AprsParser()
workers = [
FmDemod(),
Convert(Format.FLOAT, Format.SHORT),
DirewolfModule(service=service),
KissDeframer(),
Ax25Parser(),
self.parser,
]
super().__init__(workers)
def supportsSquelch(self) -> bool:
return False
def getFixedAudioRate(self) -> int:
return 48000
def setDialFrequency(self, frequency: int) -> None:
self.parser.setDialFrequency(frequency)
class PskDemodulator(SecondaryDemodulator, SecondarySelectorChain):
def __init__(self, baudRate: float):
self.baudRate = baudRate
# this is an assumption, we will adjust in setSampleRate
self.sampleRate = 12000
secondary_samples_per_bits = int(round(self.sampleRate / self.baudRate)) & ~3
workers = [
Agc(Format.COMPLEX_FLOAT),
TimingRecovery(secondary_samples_per_bits, 0.5, 2, useQ=True),
DBPskDecoder(),
VaricodeDecoder(),
]
super().__init__(workers)
def getBandwidth(self):
return self.baudRate
def setSampleRate(self, sampleRate: int) -> None:
if sampleRate == self.sampleRate:
return
self.sampleRate = sampleRate
secondary_samples_per_bits = int(round(self.sampleRate / self.baudRate)) & ~3
self.replace(1, TimingRecovery(secondary_samples_per_bits, 0.5, 2, useQ=True))

View File

@ -1,19 +0,0 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain
from pycsdr.modules import Convert, Downmix
from pycsdr.types import Format
from csdr.module.drm import DrmModule
class Drm(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain):
def __init__(self):
workers = [Convert(Format.COMPLEX_FLOAT, Format.COMPLEX_SHORT), DrmModule(), Downmix()]
super().__init__(workers)
def supportsSquelch(self) -> bool:
return False
def getFixedIfSampleRate(self) -> int:
return 48000
def getFixedAudioRate(self) -> int:
return 48000

View File

@ -1,14 +0,0 @@
from pycsdr.types import Format
from csdr.chain import Module
class DummyDemodulator(Module):
def __init__(self, outputFormat: Format):
self.outputFormat = outputFormat
super().__init__()
def getInputFormat(self) -> Format:
return Format.COMPLEX_FLOAT
def getOutputFormat(self) -> Format:
return self.outputFormat

View File

@ -1,96 +0,0 @@
from csdr.chain import Chain
from pycsdr.modules import Fft, LogPower, LogAveragePower, FftSwap, FftAdpcm
class FftAverager(Chain):
def __init__(self, fft_size, fft_averages):
self.fftSize = fft_size
self.fftAverages = fft_averages
workers = [self._getWorker()]
super().__init__(workers)
def setFftAverages(self, fft_averages):
if self.fftAverages == fft_averages:
return
self.fftAverages = fft_averages
self.replace(0, self._getWorker())
def _getWorker(self):
if self.fftAverages == 0:
return LogPower(add_db=-70)
else:
return LogAveragePower(add_db=-70, fft_size=self.fftSize, avg_number=self.fftAverages)
class FftChain(Chain):
def __init__(self, samp_rate, fft_size, fft_v_overlap_factor, fft_fps, fft_compression):
self.sampleRate = samp_rate
self.vOverlapFactor = fft_v_overlap_factor
self.fps = fft_fps
self.size = fft_size
self.blockSize = 0
self.fft = Fft(size=self.size, every_n_samples=self.blockSize)
self.averager = FftAverager(fft_size=self.size, fft_averages=10)
self.fftExchangeSides = FftSwap(fft_size=self.size)
workers = [
self.fft,
self.averager,
self.fftExchangeSides,
]
self.compressFftAdpcm = None
if fft_compression == "adpcm":
self.compressFftAdpcm = FftAdpcm(fft_size=self.size)
workers += [self.compressFftAdpcm]
self._updateParameters()
super().__init__(workers)
def _setBlockSize(self, fft_block_size):
if self.blockSize == int(fft_block_size):
return
self.blockSize = int(fft_block_size)
self.fft.setEveryNSamples(self.blockSize)
def setVOverlapFactor(self, fft_v_overlap_factor):
if self.vOverlapFactor == fft_v_overlap_factor:
return
self.vOverlapFactor = fft_v_overlap_factor
self._updateParameters()
def setFps(self, fft_fps):
if self.fps == fft_fps:
return
self.fps = fft_fps
self._updateParameters()
def setSampleRate(self, samp_rate):
if self.sampleRate == samp_rate:
return
self.sampleRate = samp_rate
self._updateParameters()
def _updateParameters(self):
fftAverages = 0
if self.vOverlapFactor > 0:
fftAverages = int(round(1.0 * self.sampleRate / self.size / self.fps / (1.0 - self.vOverlapFactor)))
self.averager.setFftAverages(fftAverages)
if fftAverages == 0:
self._setBlockSize(self.sampleRate / self.fps)
else:
self._setBlockSize(self.sampleRate / self.fps / fftAverages)
def setCompression(self, compression: str) -> None:
if compression == "adpcm" and not self.compressFftAdpcm:
self.compressFftAdpcm = FftAdpcm(self.size)
# should always be at the end
self.append(self.compressFftAdpcm)
elif compression == "none" and self.compressFftAdpcm:
self.compressFftAdpcm.stop()
self.compressFftAdpcm = None
# should always be at that position (right?)
self.remove(3)

View File

@ -1,28 +0,0 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain
from csdr.module.freedv import FreeDVModule
from pycsdr.modules import RealPart, Agc, Convert
from pycsdr.types import Format
class FreeDV(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain):
def __init__(self):
agc = Agc(Format.SHORT)
agc.setMaxGain(30)
agc.setInitialGain(3)
workers = [
RealPart(),
Agc(Format.FLOAT),
Convert(Format.FLOAT, Format.SHORT),
FreeDVModule(),
agc,
]
super().__init__(workers)
def getFixedIfSampleRate(self) -> int:
return 8000
def getFixedAudioRate(self) -> int:
return 8000
def supportsSquelch(self) -> bool:
return False

View File

@ -1,30 +0,0 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, MetaProvider
from csdr.module.m17 import M17Module
from pycsdr.modules import FmDemod, Limit, Convert, Writer
from pycsdr.types import Format
from digiham.modules import DcBlock
class M17(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, MetaProvider):
def __init__(self):
self.module = M17Module()
workers = [
FmDemod(),
DcBlock(),
Limit(),
Convert(Format.FLOAT, Format.SHORT),
self.module,
]
super().__init__(workers)
def getFixedIfSampleRate(self) -> int:
return 48000
def getFixedAudioRate(self) -> int:
return 8000
def supportsSquelch(self) -> bool:
return False
def setMetaWriter(self, writer: Writer) -> None:
self.module.setMetaWriter(writer)

View File

@ -1,160 +0,0 @@
from csdr.chain import Chain
from pycsdr.modules import Shift, FirDecimate, Bandpass, Squelch, FractionalDecimator, Writer
from pycsdr.types import Format
import math
class Decimator(Chain):
def __init__(self, inputRate: int, outputRate: int):
if outputRate > inputRate:
raise ValueError("impossible decimation: cannot upsample {} to {}".format(inputRate, outputRate))
self.inputRate = inputRate
self.outputRate = outputRate
decimation, fraction = self._getDecimation(outputRate)
transition = 0.15 * (outputRate / float(self.inputRate))
# set the cutoff on the fist decimation stage lower so that the resulting output
# is already prepared for the second (fractional) decimation stage.
# this spares us a second filter.
cutoff = 0.5 * decimation / (self.inputRate / outputRate)
workers = [
FirDecimate(decimation, transition, cutoff),
]
if fraction != 1.0:
workers += [FractionalDecimator(Format.COMPLEX_FLOAT, fraction)]
super().__init__(workers)
def _getDecimation(self, outputRate: int) -> (int, float):
d = self.inputRate / outputRate
dInt = int(d)
dFloat = float(self.inputRate / dInt) / outputRate
return dInt, dFloat
def _reconfigure(self):
decimation, fraction = self._getDecimation(self.outputRate)
transition = 0.15 * (self.outputRate / float(self.inputRate))
cutoff = 0.5 * decimation / (self.inputRate / self.outputRate)
self.replace(0, FirDecimate(decimation, transition, cutoff))
index = self.indexOf(lambda x: isinstance(x, FractionalDecimator))
if fraction != 1.0:
decimator = FractionalDecimator(Format.COMPLEX_FLOAT, fraction)
if index >= 0:
self.replace(index, decimator)
else:
self.append(decimator)
elif index >= 0:
self.remove(index)
def setOutputRate(self, outputRate: int) -> None:
if outputRate == self.outputRate:
return
self.outputRate = outputRate
self._reconfigure()
def setInputRate(self, inputRate: int) -> None:
if inputRate == self.inputRate:
return
self.inputRate = inputRate
self._reconfigure()
class Selector(Chain):
def __init__(self, inputRate: int, outputRate: int, withSquelch: bool = True):
self.inputRate = inputRate
self.outputRate = outputRate
self.frequencyOffset = 0
self.shift = Shift(0.0)
self.decimation = Decimator(inputRate, outputRate)
self.bandpass = self._buildBandpass()
self.bandpassCutoffs = None
self.setBandpass(-4000, 4000)
workers = [self.shift, self.decimation, self.bandpass]
if withSquelch:
self.readings_per_second = 4
# s-meter readings are available every 1024 samples
# the reporting interval is measured in those 1024-sample blocks
self.squelch = Squelch(5, int(outputRate / (self.readings_per_second * 1024)))
workers += [self.squelch]
super().__init__(workers)
def _buildBandpass(self) -> Bandpass:
bp_transition = 320.0 / self.outputRate
return Bandpass(transition=bp_transition, use_fft=True)
def setFrequencyOffset(self, offset: int) -> None:
if offset == self.frequencyOffset:
return
self.frequencyOffset = offset
self._updateShift()
def _updateShift(self):
shift = -self.frequencyOffset / self.inputRate
self.shift.setRate(shift)
def _convertToLinear(self, db: float) -> float:
return float(math.pow(10, db / 10))
def setSquelchLevel(self, level: float) -> None:
self.squelch.setSquelchLevel(self._convertToLinear(level))
def setBandpass(self, lowCut: float, highCut: float) -> None:
self.bandpassCutoffs = [lowCut, highCut]
scaled = [x / self.outputRate for x in self.bandpassCutoffs]
self.bandpass.setBandpass(*scaled)
def setLowCut(self, lowCut: float) -> None:
self.bandpassCutoffs[0] = lowCut
self.setBandpass(*self.bandpassCutoffs)
def setHighCut(self, highCut: float) -> None:
self.bandpassCutoffs[1] = highCut
self.setBandpass(*self.bandpassCutoffs)
def setPowerWriter(self, writer: Writer) -> None:
self.squelch.setPowerWriter(writer)
def setOutputRate(self, outputRate: int) -> None:
if outputRate == self.outputRate:
return
self.outputRate = outputRate
self.decimation.setOutputRate(outputRate)
self.squelch.setReportInterval(int(outputRate / (self.readings_per_second * 1024)))
self.bandpass = self._buildBandpass()
self.setBandpass(*self.bandpassCutoffs)
self.replace(2, self.bandpass)
def setInputRate(self, inputRate: int) -> None:
if inputRate == self.inputRate:
return
self.inputRate = inputRate
self.decimation.setInputRate(inputRate)
self._updateShift()
class SecondarySelector(Chain):
def __init__(self, sampleRate: int, bandwidth: float):
self.sampleRate = sampleRate
self.frequencyOffset = 0
self.shift = Shift(0.0)
cutoffRate = bandwidth / sampleRate
self.bandpass = Bandpass(-cutoffRate, cutoffRate, cutoffRate, use_fft=True)
workers = [self.shift, self.bandpass]
super().__init__(workers)
def setFrequencyOffset(self, offset: int) -> None:
if offset == self.frequencyOffset:
return
self.frequencyOffset = offset
if self.frequencyOffset is None:
return
self.shift.setRate(-offset / self.sampleRate)

View File

@ -1,136 +0,0 @@
from pycsdr.modules import Module as BaseModule
from pycsdr.modules import Reader, Writer
from pycsdr.types import Format
from abc import ABCMeta, abstractmethod
from threading import Thread
from io import BytesIO
from subprocess import Popen, PIPE
from functools import partial
import pickle
class Module(BaseModule, metaclass=ABCMeta):
def __init__(self):
self.reader = None
self.writer = None
super().__init__()
def setReader(self, reader: Reader) -> None:
self.reader = reader
def setWriter(self, writer: Writer) -> None:
self.writer = writer
@abstractmethod
def getInputFormat(self) -> Format:
pass
@abstractmethod
def getOutputFormat(self) -> Format:
pass
def pump(self, read, write):
def copy():
while True:
data = None
try:
data = read()
except ValueError:
pass
except BrokenPipeError:
break
if data is None or isinstance(data, bytes) and len(data) == 0:
break
write(data)
return copy
class AutoStartModule(Module, metaclass=ABCMeta):
def _checkStart(self) -> None:
if self.reader is not None and self.writer is not None:
self.start()
def setReader(self, reader: Reader) -> None:
super().setReader(reader)
self._checkStart()
def setWriter(self, writer: Writer) -> None:
super().setWriter(writer)
self._checkStart()
@abstractmethod
def start(self):
pass
class ThreadModule(AutoStartModule, Thread, metaclass=ABCMeta):
def __init__(self):
self.doRun = True
super().__init__()
Thread.__init__(self)
@abstractmethod
def run(self):
pass
def stop(self):
self.doRun = False
self.reader.stop()
def start(self):
Thread.start(self)
class PickleModule(ThreadModule):
def getInputFormat(self) -> Format:
return Format.CHAR
def getOutputFormat(self) -> Format:
return Format.CHAR
def run(self):
while self.doRun:
data = self.reader.read()
if data is None:
self.doRun = False
break
io = BytesIO(data.tobytes())
try:
while True:
output = self.process(pickle.load(io))
if output is not None:
self.writer.write(pickle.dumps(output))
except EOFError:
pass
@abstractmethod
def process(self, input):
pass
class PopenModule(AutoStartModule, metaclass=ABCMeta):
def __init__(self):
self.process = None
super().__init__()
@abstractmethod
def getCommand(self):
pass
def _getProcess(self):
return Popen(self.getCommand(), stdin=PIPE, stdout=PIPE)
def start(self):
self.process = self._getProcess()
# resume in case the reader has been stop()ed before
self.reader.resume()
Thread(target=self.pump(self.reader.read, self.process.stdin.write)).start()
Thread(target=self.pump(partial(self.process.stdout.read1, 1024), self.writer.write)).start()
def stop(self):
if self.process is not None:
self.process.terminate()
self.process.wait()
self.process = None
self.reader.stop()

View File

@ -1,14 +0,0 @@
from csdr.module import PopenModule
from pycsdr.types import Format
class DrmModule(PopenModule):
def getInputFormat(self) -> Format:
return Format.COMPLEX_FLOAT
def getOutputFormat(self) -> Format:
return Format.SHORT
def getCommand(self):
# dream -c 6 --sigsrate 48000 --audsrate 48000 -I - -O -
return ["dream", "-c", "6", "--sigsrate", "48000", "--audsrate", "48000", "-I", "-", "-O", "-"]

View File

@ -1,13 +0,0 @@
from pycsdr.types import Format
from csdr.module import PopenModule
class FreeDVModule(PopenModule):
def getInputFormat(self) -> Format:
return Format.SHORT
def getOutputFormat(self) -> Format:
return Format.SHORT
def getCommand(self):
return ["freedv_rx", "1600", "-", "-"]

View File

@ -1,58 +0,0 @@
from csdr.module import PopenModule
from pycsdr.types import Format
from pycsdr.modules import Writer
from subprocess import Popen, PIPE
from threading import Thread
import re
import pickle
class M17Module(PopenModule):
lsfRegex = re.compile("SRC: ([a-zA-Z0-9]+), DEST: ([a-zA-Z0-9]+)")
def __init__(self):
super().__init__()
self.metawriter = None
def getInputFormat(self) -> Format:
return Format.SHORT
def getOutputFormat(self) -> Format:
return Format.SHORT
def getCommand(self):
return ["m17-demod", "-l"]
def _getProcess(self):
return Popen(self.getCommand(), stdin=PIPE, stdout=PIPE, stderr=PIPE)
def start(self):
super().start()
Thread(target=self._readOutput).start()
def _readOutput(self):
while True:
line = self.process.stderr.readline()
if not line:
break
self.parseOutput(line.decode())
def parseOutput(self, line):
if self.metawriter is None:
return
matches = self.lsfRegex.match(line)
msg = {"protocol": "M17"}
if matches:
# fake sync
msg["sync"] = "voice"
msg["source"] = matches.group(1)
msg["destination"] = matches.group(2)
elif line.startswith("EOS"):
pass
else:
return
self.metawriter.write(pickle.dumps(msg))
def setMetaWriter(self, writer: Writer) -> None:
self.metawriter = writer

View File

@ -1,57 +0,0 @@
from pycsdr.types import Format
from csdr.module import PopenModule, ThreadModule
from owrx.wsjt import WsjtParser, Msk144Profile
import pickle
import logging
logger = logging.getLogger(__name__)
class Msk144Module(PopenModule):
def getCommand(self):
return ["msk144decoder"]
def getInputFormat(self) -> Format:
return Format.SHORT
def getOutputFormat(self) -> Format:
return Format.CHAR
class ParserAdapter(ThreadModule):
def __init__(self):
self.retained = bytes()
self.parser = WsjtParser()
self.dialFrequency = 0
super().__init__()
def run(self):
profile = Msk144Profile()
while self.doRun:
data = self.reader.read()
if data is None:
self.doRun = False
else:
self.retained += data
lines = self.retained.split(b"\n")
# keep the last line
# this should either be empty if the last char was \n
# or an incomplete line if the read returned early
self.retained = lines[-1]
# parse all completed lines
for line in lines[0:-1]:
# actual messages from msk144decoder should start with "*** "
if line[0:4] == b"*** ":
self.writer.write(pickle.dumps(self.parser.parse(profile, self.dialFrequency, line[4:])))
def getInputFormat(self) -> Format:
return Format.CHAR
def getOutputFormat(self) -> Format:
return Format.CHAR
def setDialFrequency(self, frequency: int) -> None:
self.dialFrequency = frequency

36
csdr/output.py Normal file
View File

@ -0,0 +1,36 @@
import threading
import logging
logger = logging.getLogger(__name__)
class Output(object):
def send_output(self, t, read_fn):
if not self.supports_type(t):
# TODO rewrite the output mechanism in a way that avoids producing unnecessary data
logger.warning("dumping output of type %s since it is not supported.", t)
threading.Thread(target=self.pump(read_fn, lambda x: None), name="csdr_pump_thread").start()
return
self.receive_output(t, read_fn)
def receive_output(self, t, read_fn):
pass
def pump(self, read, write):
def copy():
run = True
while run:
data = None
try:
data = read()
except ValueError:
pass
if data is None or (isinstance(data, bytes) and len(data) == 0):
run = False
else:
write(data)
return copy
def supports_type(self, t):
return True

156
csdr/pipe.py Normal file
View File

@ -0,0 +1,156 @@
import os
import select
import time
import threading
import logging
logger = logging.getLogger(__name__)
class Pipe(object):
READ = "r"
WRITE = "w"
NONE = None
@staticmethod
def create(path, t, encoding=None):
if t == Pipe.READ:
return ReadingPipe(path, encoding=encoding)
elif t == Pipe.WRITE:
return WritingPipe(path, encoding=encoding)
elif t == Pipe.NONE:
return Pipe(path, None, encoding=encoding)
def __init__(self, path, direction, encoding=None):
self.doOpen = True
self.path = "{base}_{myid}".format(base=path, myid=id(self))
self.direction = direction
self.encoding = encoding
self.file = None
os.mkfifo(self.path)
def open(self):
"""
this method opens the file descriptor with an added O_NONBLOCK flag. This gives us a special behaviour for
FIFOS, when they are not opened by the opposing side:
- opening a pipe for writing will throw an OSError with errno = 6 (ENXIO). This is handled specially in the
WritingPipe class.
- opening a pipe for reading will pass through this method instantly, even if the opposing end has not been
opened yet, but the resulting file descriptor will behave as if O_NONBLOCK is set (even if we remove it
immediately here), resulting in empty reads until data is available. This is handled specially in the
ReadingPipe class.
"""
def opener(path, flags):
fd = os.open(path, flags | os.O_NONBLOCK)
os.set_blocking(fd, True)
return fd
self.file = open(self.path, self.direction, encoding=self.encoding, opener=opener)
def close(self):
self.doOpen = False
try:
if self.file is not None:
self.file.close()
os.unlink(self.path)
except FileNotFoundError:
# it seems like we keep calling this twice. no idea why, but we don't need the resulting error.
pass
except Exception:
logger.exception("Pipe.close()")
def __str__(self):
return self.path
class WritingPipe(Pipe):
def __init__(self, path, encoding=None):
self.queue = []
self.queueLock = threading.Lock()
super().__init__(path, "w", encoding=encoding)
self.open()
def open_and_dequeue(self):
"""
This method implements a retry loop that can be interrupted in case the Pipe gets shutdown before actually
being connected.
After the pipe is opened successfully, all data that has been queued is sent in the order it was passed into
write().
"""
retries = 0
while self.file is None and self.doOpen and retries < 10:
try:
super().open()
except OSError as error:
# ENXIO = FIFO has not been opened for reading
if error.errno == 6:
time.sleep(0.1)
retries += 1
else:
raise
# if doOpen is false, opening has been canceled, so no warning in that case.
if self.file is None:
if self.doOpen:
logger.warning("could not open FIFO %s", self.path)
return
with self.queueLock:
for i in self.queue:
self.file.write(i)
self.file.flush()
self.queue = None
def open(self):
"""
This sends the opening operation off to a background thread. If we were to block the thread here, another pipe
may be waiting in the queue to be opened on the opposing side, resulting in a deadlock
"""
threading.Thread(target=self.open_and_dequeue, name="csdr_pipe_thread").start()
def write(self, data):
"""
This method queues all data to be written until the file is actually opened. As soon as a file is available,
it becomes a passthrough.
"""
if self.file is None:
with self.queueLock:
self.queue.append(data)
return
r = self.file.write(data)
self.file.flush()
return r
class ReadingPipe(Pipe):
def __init__(self, path, encoding=None):
super().__init__(path, "r", encoding=encoding)
def open(self):
"""
This method implements an interruptible loop that waits for the file descriptor to be opened and the first
batch of data coming in using repeated select() calls.
:return:
"""
if not self.doOpen:
return
super().open()
while self.doOpen:
(read, _, _) = select.select([self.file], [], [], 1)
if self.file in read:
break
def read(self):
if self.file is None:
self.open()
return self.file.read()
def readline(self):
if self.file is None:
self.open()
return self.file.readline()

34
debian/changelog vendored
View File

@ -1,37 +1,3 @@
openwebrx (1.3.0) UNRELEASED; urgency=low
* SDR device log messages are now available in the web configuration to
simplify troubleshooting
* Added support for the MSK144 digimode
-- Jakob Ketterl <jakob.ketterl@gmx.de> Fri, 30 Sep 2022 16:47:00 +0000
openwebrx (1.2.1) bullseye jammy; urgency=low
* FifiSDR support fixed (pipeline formats now line up correctly)
* Added "Device" input for FifiSDR devices for sound card selection
-- Jakob Ketterl <jakob.ketterl@gmx.de> Tue, 20 Sep 2022 16:01:00 +0000
openwebrx (1.2.0) bullseye jammy; urgency=low
* Major rewrite of all demodulation components to make use of the new
csdr/pycsdr and digiham/pydigiham demodulator modules
* Preliminary display of M17 callsign information
* New devices supported:
- Blade RF
-- Jakob Ketterl <jakob.ketterl@gmx.de> Wed, 15 Jun 2022 16:20:00 +0000
openwebrx (1.1.0) buster hirsute; urgency=low
* Reworked most graphical elements as SVGs for faster loadtimes and crispier
display on hi-dpi displays
* Updated pipelines to match changes in digiham
* Changed D-Star and NXDN integrations to use new decoder from digiham
* Added D-Star and NXDN metadata display
-- Jakob Ketterl <jakob.ketterl@gmx.de> Mon, 02 Aug 2021 16:24:00 +0000
openwebrx (1.0.0) buster hirsute; urgency=low
* Introduced `squelch_auto_margin` config option that allows configuring the
auto squelch level

4
debian/control vendored
View File

@ -10,7 +10,7 @@ Vcs-Git: https://github.com/jketterl/openwebrx.git
Package: openwebrx
Architecture: all
Depends: adduser, python3 (>= 3.5), python3-pkg-resources, owrx-connector (>= 0.5), soapysdr-tools, python3-csdr (>= 0.18), ${python3:Depends}, ${misc:Depends}
Recommends: python3-digiham (>= 0.6), direwolf (>= 1.4), wsjtx, js8call, runds-connector (>= 0.2), hpsdrconnector, aprs-symbols, m17-demod, js8call, python3-js8py (>= 0.2), nmux (>= 0.18), codecserver (>= 0.1), msk144decoder
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.4), dsd (>= 1.7), sox, direwolf (>= 1.4), wsjtx, runds-connector, hpsdrconnector, aprs-symbols, m17-demod, js8call
Description: multi-user web sdr
Open source, multi-user SDR receiver with a web interface

View File

@ -12,7 +12,7 @@ OWRX_BOOKMARKS_FILE="${OWRX_DATADIR}/bookmarks.json"
case "$1" in
configure|reconfigure)
adduser --system --group --no-create-home --home /nonexistent --quiet "${OWRX_USER}"
usermod -aG plugdev "${OWRX_USER}"
usermod -aG plugdev openwebrx
# create OpenWebRX data directory and set the correct permissions
if [ ! -d "${OWRX_DATADIR}" ] && [ ! -L "${OWRX_DATADIR}" ]; then mkdir "${OWRX_DATADIR}"; fi

View File

@ -2,10 +2,10 @@
set -euo pipefail
ARCH=$(uname -m)
IMAGES="openwebrx-rtlsdr openwebrx-sdrplay openwebrx-hackrf openwebrx-airspy openwebrx-rtlsdr-soapy openwebrx-plutosdr openwebrx-limesdr openwebrx-soapyremote openwebrx-perseus openwebrx-fcdpp openwebrx-radioberry openwebrx-uhd openwebrx-rtltcp openwebrx-runds openwebrx-hpsdr openwebrx-bladerf openwebrx-full openwebrx"
IMAGES="openwebrx-rtlsdr openwebrx-sdrplay openwebrx-hackrf openwebrx-airspy openwebrx-rtlsdr-soapy openwebrx-plutosdr openwebrx-limesdr openwebrx-soapyremote openwebrx-perseus openwebrx-fcdpp openwebrx-radioberry openwebrx-uhd openwebrx-rtltcp openwebrx-runds openwebrx-hpsdr openwebrx-full openwebrx"
ALL_ARCHS="x86_64 armv7l aarch64"
TAG=${TAG:-"latest"}
ARCHTAG="${TAG}-${ARCH}"
ARCHTAG="$TAG-$ARCH"
usage () {
echo "Usage: ${0} [command]"
@ -36,7 +36,7 @@ build () {
push () {
for image in ${IMAGES}; do
docker push jketterl/${image}:${ARCHTAG}
docker push jketterl/$image:$ARCHTAG
done
}
@ -45,11 +45,11 @@ manifest () {
# there's no docker manifest rm command, and the create --amend does not work, so we have to clean up manually
rm -rf "${HOME}/.docker/manifests/docker.io_jketterl_${image}-${TAG}"
IMAGE_LIST=""
for a in ${ALL_ARCHS}; do
IMAGE_LIST="${IMAGE_LIST} jketterl/${image}:${TAG}-${a}"
for a in $ALL_ARCHS; do
IMAGE_LIST="$IMAGE_LIST jketterl/$image:$TAG-$a"
done
docker manifest create jketterl/${image}:${TAG} ${IMAGE_LIST}
docker manifest push --purge jketterl/${image}:${TAG}
docker manifest create jketterl/$image:$TAG $IMAGE_LIST
docker manifest push --purge jketterl/$image:$TAG
done
}

View File

@ -1,4 +1,4 @@
FROM debian:bullseye-slim
FROM debian:buster-slim
COPY docker/files/js8call/js8call-hamlib.patch \
docker/files/wsjtx/wsjtx.patch \
@ -13,8 +13,6 @@ COPY docker/scripts/install-owrx-tools.sh /
RUN /install-owrx-tools.sh && \
rm /install-owrx-tools.sh
COPY docker/files/services/codecserver /etc/services.d/codecserver
ENTRYPOINT ["/init"]
WORKDIR /opt/openwebrx

View File

@ -1,8 +0,0 @@
ARG ARCHTAG
FROM openwebrx-soapysdr-base:$ARCHTAG
COPY docker/scripts/install-dependencies-bladerf.sh /
RUN /install-dependencies-bladerf.sh &&\
rm /install-dependencies-bladerf.sh
COPY . /opt/openwebrx

View File

@ -19,7 +19,6 @@ RUN /install-dependencies-rtlsdr.sh &&\
/install-dependencies-radioberry.sh &&\
/install-dependencies-uhd.sh &&\
/install-dependencies-hpsdr.sh &&\
/install-dependencies-bladerf.sh &&\
/install-connectors.sh &&\
/install-dependencies-runds.sh &&\
rm /install-dependencies-*.sh &&\

View File

@ -1,2 +0,0 @@
#!/usr/bin/execlineb -P
/usr/local/bin/codecserver

View File

@ -1,5 +1,5 @@
--- CMakeLists.txt.orig 2021-09-28 14:33:14.329598412 +0200
+++ CMakeLists.txt 2021-09-28 14:34:23.052345270 +0200
--- CMakeLists.txt.orig 2021-03-30 15:28:36.956587995 +0200
+++ CMakeLists.txt 2021-03-30 15:29:45.719326832 +0200
@@ -106,24 +106,6 @@

View File

@ -1,341 +1,183 @@
diff -ur wsjtx-orig/CMake/Modules/Findhamlib.cmake wsjtx/CMake/Modules/Findhamlib.cmake
--- wsjtx-orig/CMake/Modules/Findhamlib.cmake 2021-02-01 20:38:00.947536514 +0100
+++ wsjtx/CMake/Modules/Findhamlib.cmake 2021-02-01 20:39:06.273680932 +0100
@@ -85,4 +85,4 @@
# Handle the QUIETLY and REQUIRED arguments and set HAMLIB_FOUND to
# TRUE if all listed variables are TRUE
include (FindPackageHandleStandardArgs)
-find_package_handle_standard_args (hamlib DEFAULT_MSG hamlib_INCLUDE_DIRS hamlib_LIBRARIES hamlib_LIBRARY_DIRS)
+find_package_handle_standard_args (hamlib DEFAULT_MSG hamlib_INCLUDE_DIRS hamlib_LIBRARIES)
diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
--- wsjtx-orig/CMakeLists.txt 2023-01-28 17:43:05.586124507 +0100
+++ wsjtx/CMakeLists.txt 2023-01-28 17:56:07.108634912 +0100
--- wsjtx-orig/CMakeLists.txt 2021-02-01 20:38:00.947536514 +0100
+++ wsjtx/CMakeLists.txt 2021-02-01 23:02:22.503027275 +0100
@@ -122,7 +122,7 @@
option (WSJT_QDEBUG_TO_FILE "Redirect Qt debuging messages to a trace file.")
option (WSJT_SOFT_KEYING "Apply a ramp to CW keying envelope to reduce transients." ON)
option (WSJT_SKIP_MANPAGES "Skip *nix manpage generation.")
-option (WSJT_GENERATE_DOCS "Generate documentation files." ON)
+option (WSJT_GENERATE_DOCS "Generate documentation files.")
option (WSJT_RIG_NONE_CAN_SPLIT "Allow split operation with \"None\" as rig.")
option (WSJT_TRACE_UDP "Debugging option that turns on UDP message protocol diagnostics.")
option (WSJT_BUILD_UTILS "Build simulators and code demonstrators." ON)
@@ -170,77 +170,7 @@
)
set (wsjt_qt_CXXSRCS
- helper_functions.cpp
- qt_helpers.cpp
- widgets/MessageBox.cpp
- MetaDataRegistry.cpp
- Network/NetworkServerLookup.cpp
revision_utils.cpp
- L10nLoader.cpp
- WFPalette.cpp
- Radio.cpp
- RadioMetaType.cpp
- NonInheritingProcess.cpp
- models/IARURegions.cpp
- models/Bands.cpp
- models/Modes.cpp
- models/FrequencyList.cpp
- models/StationList.cpp
- widgets/FrequencyLineEdit.cpp
- widgets/FrequencyDeltaLineEdit.cpp
- item_delegates/CandidateKeyFilter.cpp
- item_delegates/ForeignKeyDelegate.cpp
- item_delegates/MessageItemDelegate.cpp
- validators/LiveFrequencyValidator.cpp
- GetUserId.cpp
- Audio/AudioDevice.cpp
- Transceiver/Transceiver.cpp
- Transceiver/TransceiverBase.cpp
- Transceiver/EmulateSplitTransceiver.cpp
- Transceiver/TransceiverFactory.cpp
- Transceiver/PollingTransceiver.cpp
- Transceiver/HamlibTransceiver.cpp
- Transceiver/HRDTransceiver.cpp
- Transceiver/DXLabSuiteCommanderTransceiver.cpp
- Network/NetworkMessage.cpp
- Network/MessageClient.cpp
- widgets/LettersSpinBox.cpp
- widgets/HintedSpinBox.cpp
- widgets/RestrictedSpinBox.cpp
- widgets/HelpTextWindow.cpp
- SampleDownloader.cpp
- SampleDownloader/DirectoryDelegate.cpp
- SampleDownloader/Directory.cpp
- SampleDownloader/FileNode.cpp
- SampleDownloader/RemoteFile.cpp
- DisplayManual.cpp
- MultiSettings.cpp
- validators/MaidenheadLocatorValidator.cpp
- validators/CallsignValidator.cpp
- widgets/SplashScreen.cpp
- EqualizationToolsDialog.cpp
- widgets/DoubleClickablePushButton.cpp
- widgets/DoubleClickableRadioButton.cpp
- Network/LotWUsers.cpp
- models/DecodeHighlightingModel.cpp
- widgets/DecodeHighlightingListView.cpp
- models/FoxLog.cpp
- widgets/AbstractLogWindow.cpp
- widgets/FoxLogWindow.cpp
- widgets/CabrilloLogWindow.cpp
- item_delegates/CallsignDelegate.cpp
- item_delegates/MaidenheadLocatorDelegate.cpp
- item_delegates/FrequencyDelegate.cpp
- item_delegates/FrequencyDeltaDelegate.cpp
- item_delegates/SQLiteDateTimeDelegate.cpp
- models/CabrilloLog.cpp
- logbook/AD1CCty.cpp
- logbook/WorkedBefore.cpp
- logbook/Multiplier.cpp
- Network/NetworkAccessManager.cpp
- widgets/LazyFillComboBox.cpp
- widgets/CheckableItemComboBox.cpp
- widgets/BandComboBox.cpp
)
set (wsjt_qtmm_CXXSRCS
@@ -1089,9 +1019,6 @@
if (WSJT_GENERATE_DOCS)
add_subdirectory (doc)
endif (WSJT_GENERATE_DOCS)
-if (EXISTS ${CMAKE_SOURCE_DIR}/tests AND IS_DIRECTORY ${CMAKE_SOURCE_DIR}/tests)
- add_subdirectory (tests)
-endif ()
# build a library of package functionality (without and optionally with OpenMP support)
add_library (wsjt_cxx STATIC ${wsjt_CSRCS} ${wsjt_CXXSRCS})
@@ -1357,10 +1284,7 @@
add_library (wsjt_qt STATIC ${wsjt_qt_CXXSRCS} ${wsjt_qt_GENUISRCS} ${GENAXSRCS})
# set wsjtx_udp exports to static variants
target_compile_definitions (wsjt_qt PUBLIC UDP_STATIC_DEFINE)
-target_link_libraries (wsjt_qt Hamlib::Hamlib Boost::log qcp Qt5::Widgets Qt5::Network Qt5::Sql)
-if (WIN32)
- target_link_libraries (wsjt_qt Qt5::AxContainer Qt5::AxBase)
-endif (WIN32)
+target_link_libraries (wsjt_qt Qt5::Core)
# build a library of package Qt functionality used in Fortran utilities
add_library (fort_qt STATIC ${fort_qt_CXXSRCS})
@@ -1425,90 +1349,6 @@
add_subdirectory (map65)
endif ()
-# build the main application
-generate_version_info (wsjtx_VERSION_RESOURCES
- NAME wsjtx
- BUNDLE ${PROJECT_BUNDLE_NAME}
- ICON ${WSJTX_ICON_FILE}
- )
-
-add_executable (wsjtx MACOSX_BUNDLE
- ${wsjtx_CXXSRCS}
- ${wsjtx_GENUISRCS}
- ${WSJTX_ICON_FILE}
- ${wsjtx_RESOURCES_RCC}
- ${wsjtx_VERSION_RESOURCES}
- )
-
-if (WSJT_CREATE_WINMAIN)
- set_target_properties (wsjtx PROPERTIES WIN32_EXECUTABLE ON)
-endif (WSJT_CREATE_WINMAIN)
-
-set_target_properties (wsjtx PROPERTIES
- MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Darwin/Info.plist.in"
- MACOSX_BUNDLE_INFO_STRING "${PROJECT_DESCRIPTION}"
- MACOSX_BUNDLE_ICON_FILE "${WSJTX_ICON_FILE}"
- MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}
- MACOSX_BUNDLE_SHORT_VERSION_STRING "v${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}"
- MACOSX_BUNDLE_LONG_VERSION_STRING "Version ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${SCS_VERSION_STR}"
- MACOSX_BUNDLE_BUNDLE_NAME "${PROJECT_BUNDLE_NAME}"
- MACOSX_BUNDLE_BUNDLE_EXECUTABLE_NAME "${PROJECT_NAME}"
- MACOSX_BUNDLE_COPYRIGHT "${PROJECT_COPYRIGHT}"
- MACOSX_BUNDLE_GUI_IDENTIFIER "org.k1jt.wsjtx"
- )
-
-target_include_directories (wsjtx PRIVATE ${FFTW3_INCLUDE_DIRS})
-if ((NOT ${OPENMP_FOUND}) OR APPLE)
- target_link_libraries (wsjtx wsjt_fort)
-else ()
- target_link_libraries (wsjtx wsjt_fort_omp)
- if (OpenMP_C_FLAGS)
- set_target_properties (wsjtx PROPERTIES
- COMPILE_FLAGS "${OpenMP_C_FLAGS}"
- LINK_FLAGS "${OpenMP_C_FLAGS}"
- )
- endif ()
- set_target_properties (wsjtx PROPERTIES
- Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/fortran_modules_omp
- )
- if (WIN32)
- set_target_properties (wsjtx PROPERTIES
- LINK_FLAGS -Wl,--stack,0x1000000,--heap,0x20000000
- )
- endif ()
-endif ()
-target_link_libraries (wsjtx Qt5::SerialPort wsjt_cxx wsjt_qt wsjt_qtmm ${FFTW3_LIBRARIES} ${LIBM_LIBRARIES})
-
-# make a library for WSJT-X UDP servers
-# add_library (wsjtx_udp SHARED ${UDP_library_CXXSRCS})
-add_library (wsjtx_udp-static STATIC ${UDP_library_CXXSRCS})
-#target_include_directories (wsjtx_udp
-# INTERFACE
-# $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/wsjtx>
-# )
-target_include_directories (wsjtx_udp-static
- INTERFACE
- $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/wsjtx>
- )
-#set_target_properties (wsjtx_udp PROPERTIES
-# PUBLIC_HEADER "${UDP_library_HEADERS}"
-# )
-set_target_properties (wsjtx_udp-static PROPERTIES
- OUTPUT_NAME wsjtx_udp
- )
-target_compile_definitions (wsjtx_udp-static PUBLIC UDP_STATIC_DEFINE)
-target_link_libraries (wsjtx_udp-static Qt5::Network Qt5::Gui)
-generate_export_header (wsjtx_udp-static BASE_NAME udp)
-
-generate_version_info (udp_daemon_VERSION_RESOURCES
- NAME udp_daemon
- BUNDLE ${PROJECT_BUNDLE_NAME}
- ICON ${WSJTX_ICON_FILE}
- FILE_DESCRIPTION "Example WSJT-X UDP Message Protocol daemon"
- )
-add_executable (udp_daemon UDPExamples/UDPDaemon.cpp ${udp_daemon_VERSION_RESOURCES})
-target_link_libraries (udp_daemon wsjtx_udp-static)
-
generate_version_info (wsjtx_app_version_VERSION_RESOURCES
NAME wsjtx_app_version
BUNDLE ${PROJECT_BUNDLE_NAME}
@@ -1518,47 +1358,9 @@
add_executable (wsjtx_app_version AppVersion/AppVersion.cpp ${wsjtx_app_version_VERSION_RESOURCES})
target_link_libraries (wsjtx_app_version wsjt_qt)
-generate_version_info (message_aggregator_VERSION_RESOURCES
- NAME message_aggregator
- BUNDLE ${PROJECT_BUNDLE_NAME}
- ICON ${WSJTX_ICON_FILE}
- FILE_DESCRIPTION "Example WSJT-X UDP Message Protocol application"
- )
-add_resources (message_aggregator_RESOURCES /qss ${message_aggregator_STYLESHEETS})
-configure_file (UDPExamples/message_aggregator.qrc.in message_aggregator.qrc @ONLY)
-qt5_add_resources (message_aggregator_RESOURCES_RCC
- ${CMAKE_CURRENT_BINARY_DIR}/message_aggregator.qrc
- contrib/QDarkStyleSheet/qdarkstyle/style.qrc
- )
-add_executable (message_aggregator
- ${message_aggregator_CXXSRCS}
- ${message_aggregator_RESOURCES_RCC}
- ${message_aggregator_VERSION_RESOURCES}
- )
-target_link_libraries (message_aggregator wsjt_qt Qt5::Widgets wsjtx_udp-static)
-
-if (WSJT_CREATE_WINMAIN)
- set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
-endif (WSJT_CREATE_WINMAIN)
-
-if (UNIX)
- if (NOT WSJT_SKIP_MANPAGES)
- add_subdirectory (manpages)
- add_dependencies (wsjtx manpages)
- endif (NOT WSJT_SKIP_MANPAGES)
- if (NOT APPLE)
- add_subdirectory (debian)
- add_dependencies (wsjtx debian)
- endif (NOT APPLE)
-endif (UNIX)
-
#
# installation
#
-install (TARGETS wsjtx
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- BUNDLE DESTINATION . COMPONENT runtime
- )
# install (TARGETS wsjtx_udp EXPORT udp
# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -1577,12 +1379,7 @@
# DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/wsjtx
# )
-install (TARGETS udp_daemon message_aggregator wsjtx_app_version
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- )
-
-install (TARGETS jt9 wsprd fmtave fcal fmeasure
+install (TARGETS wsjtx_app_version jt9 wsprd
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
)
@@ -1595,38 +1392,6 @@
)
endif(WSJT_BUILD_UTILS)
-install (PROGRAMS
- ${RIGCTL_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctl-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (PROGRAMS
- ${RIGCTLD_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctld-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (PROGRAMS
- ${RIGCTLCOM_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctlcom-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (FILES
- README
- COPYING
- AUTHORS
- THANKS
- NEWS
- BUGS
- DESTINATION ${CMAKE_INSTALL_DOCDIR}
- #COMPONENT runtime
- )
-
install (FILES
cty.dat
cty.dat_copyright.txt
@@ -1635,13 +1400,6 @@
#COMPONENT runtime
)
-install (DIRECTORY
- example_log_configurations
- DESTINATION ${CMAKE_INSTALL_DOCDIR}
- FILES_MATCHING REGEX "^.*[^~]$"
- #COMPONENT runtime
- )
-
#
# Mac installer files
#
@@ -1693,22 +1451,6 @@
"${CMAKE_CURRENT_BINARY_DIR}/wsjtx_config.h"
)
-
-if (NOT WIN32 AND NOT APPLE)
- # install a desktop file so wsjtx appears in the application start
- # menu with an icon
- install (
- FILES wsjtx.desktop message_aggregator.desktop
- DESTINATION share/applications
- #COMPONENT runtime
- )
- install (
- FILES icons/Unix/wsjtx_icon.png
- DESTINATION share/pixmaps
- #COMPONENT runtime
- )
-endif (NOT WIN32 AND NOT APPLE)
-
if (APPLE)
set (CMAKE_POSTFLIGHT_SCRIPT
"${wsjtx_BINARY_DIR}/postflight.sh")
option (WSJT_QDEBUG_TO_FILE "Redirect Qt debuging messages to a trace file.")
option (WSJT_SOFT_KEYING "Apply a ramp to CW keying envelope to reduce transients." ON)
option (WSJT_SKIP_MANPAGES "Skip *nix manpage generation.")
-option (WSJT_GENERATE_DOCS "Generate documentation files." ON)
+option (WSJT_GENERATE_DOCS "Generate documentation files.")
option (WSJT_RIG_NONE_CAN_SPLIT "Allow split operation with \"None\" as rig.")
option (WSJT_TRACE_UDP "Debugging option that turns on UDP message protocol diagnostics.")
option (WSJT_BUILD_UTILS "Build simulators and code demonstrators." ON)
@@ -856,7 +856,7 @@
#
# libhamlib setup
#
-set (hamlib_STATIC 1)
+set (hamlib_STATIC 0)
find_package (hamlib 3 REQUIRED)
find_program (RIGCTL_EXE rigctl)
find_program (RIGCTLD_EXE rigctld)
@@ -1376,60 +1376,6 @@
target_link_libraries (jt9 wsjt_fort wsjt_cxx fort_qt)
endif (${OPENMP_FOUND} OR APPLE)
-# build the main application
-generate_version_info (wsjtx_VERSION_RESOURCES
- NAME wsjtx
- BUNDLE ${PROJECT_BUNDLE_NAME}
- ICON ${WSJTX_ICON_FILE}
- )
-
-add_executable (wsjtx MACOSX_BUNDLE
- ${wsjtx_CXXSRCS}
- ${wsjtx_GENUISRCS}
- ${WSJTX_ICON_FILE}
- ${wsjtx_RESOURCES_RCC}
- ${wsjtx_VERSION_RESOURCES}
- )
-
-if (WSJT_CREATE_WINMAIN)
- set_target_properties (wsjtx PROPERTIES WIN32_EXECUTABLE ON)
-endif (WSJT_CREATE_WINMAIN)
-
-set_target_properties (wsjtx PROPERTIES
- MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Darwin/Info.plist.in"
- MACOSX_BUNDLE_INFO_STRING "${PROJECT_DESCRIPTION}"
- MACOSX_BUNDLE_ICON_FILE "${WSJTX_ICON_FILE}"
- MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}
- MACOSX_BUNDLE_SHORT_VERSION_STRING "v${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}"
- MACOSX_BUNDLE_LONG_VERSION_STRING "Version ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${SCS_VERSION_STR}"
- MACOSX_BUNDLE_BUNDLE_NAME "${PROJECT_BUNDLE_NAME}"
- MACOSX_BUNDLE_BUNDLE_EXECUTABLE_NAME "${PROJECT_NAME}"
- MACOSX_BUNDLE_COPYRIGHT "${PROJECT_COPYRIGHT}"
- MACOSX_BUNDLE_GUI_IDENTIFIER "org.k1jt.wsjtx"
- )
-
-target_include_directories (wsjtx PRIVATE ${FFTW3_INCLUDE_DIRS})
-if (APPLE)
- target_link_libraries (wsjtx wsjt_fort)
-else ()
- target_link_libraries (wsjtx wsjt_fort_omp)
- if (OpenMP_C_FLAGS)
- set_target_properties (wsjtx PROPERTIES
- COMPILE_FLAGS "${OpenMP_C_FLAGS}"
- LINK_FLAGS "${OpenMP_C_FLAGS}"
- )
- endif ()
- set_target_properties (wsjtx PROPERTIES
- Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/fortran_modules_omp
- )
- if (WIN32)
- set_target_properties (wsjtx PROPERTIES
- LINK_FLAGS -Wl,--stack,0x1000000,--heap,0x20000000
- )
- endif ()
-endif ()
-target_link_libraries (wsjtx Qt5::SerialPort wsjt_cxx wsjt_qt wsjt_qtmm ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES} ${LIBM_LIBRARIES})
-
# make a library for WSJT-X UDP servers
# add_library (wsjtx_udp SHARED ${UDP_library_CXXSRCS})
add_library (wsjtx_udp-static STATIC ${UDP_library_CXXSRCS})
@@ -1492,24 +1438,9 @@
set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
endif (WSJT_CREATE_WINMAIN)
-if (UNIX)
- if (NOT WSJT_SKIP_MANPAGES)
- add_subdirectory (manpages)
- add_dependencies (wsjtx manpages)
- endif (NOT WSJT_SKIP_MANPAGES)
- if (NOT APPLE)
- add_subdirectory (debian)
- add_dependencies (wsjtx debian)
- endif (NOT APPLE)
-endif (UNIX)
-
#
# installation
#
-install (TARGETS wsjtx
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- BUNDLE DESTINATION . COMPONENT runtime
- )
# install (TARGETS wsjtx_udp EXPORT udp
# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -1528,12 +1459,7 @@
# DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/wsjtx
# )
-install (TARGETS udp_daemon message_aggregator wsjtx_app_version
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- )
-
-install (TARGETS jt9 wsprd fmtave fcal fmeasure
+install (TARGETS wsjtx_app_version jt9 wsprd
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
)
@@ -1546,38 +1472,6 @@
)
endif(WSJT_BUILD_UTILS)
-install (PROGRAMS
- ${RIGCTL_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctl-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (PROGRAMS
- ${RIGCTLD_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctld-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (PROGRAMS
- ${RIGCTLCOM_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctlcom-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (FILES
- README
- COPYING
- AUTHORS
- THANKS
- NEWS
- BUGS
- DESTINATION ${CMAKE_INSTALL_DOCDIR}
- #COMPONENT runtime
- )
-
install (FILES
cty.dat
cty.dat_copyright.txt
@@ -1586,13 +1480,6 @@
#COMPONENT runtime
)
-install (DIRECTORY
- example_log_configurations
- DESTINATION ${CMAKE_INSTALL_DOCDIR}
- FILES_MATCHING REGEX "^.*[^~]$"
- #COMPONENT runtime
- )
-
#
# Mac installer files
#

View File

@ -18,14 +18,13 @@ function cmakebuild() {
cd /tmp
BUILD_PACKAGES="git cmake make gcc g++ libsamplerate-dev libfftw3-dev"
BUILD_PACKAGES="git cmake make gcc g++"
apt-get update
apt-get -y install --no-install-recommends $BUILD_PACKAGES
git clone https://github.com/jketterl/owrx_connector.git
# latest develop as of 2022-12-11 (std::endl implicit flushing)
cmakebuild owrx_connector bca362707131289f91441c8080fd368fdc067b6d
cmakebuild owrx_connector 0.4.0
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean

View File

@ -1,36 +0,0 @@
#!/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="libusb-1.0-0"
BUILD_PACKAGES="git cmake make gcc g++ libusb-1.0-0-dev"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/Nuand/bladeRF.git
cmakebuild bladeRF 2021.10
git clone https://github.com/pothosware/SoapyBladeRF.git
# latest from master as of 2022-01-12
cmakebuild SoapyBladeRF 70505a5cdf8c9deabc4af3eb3384aa82a7b6f021
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -29,7 +29,7 @@ tar xfz $PACKAGE
git clone https://github.com/jancona/hpsdrconnector.git
pushd hpsdrconnector
git checkout v0.6.1
git checkout v0.4.2
/tmp/go/bin/go build
install -m 0755 hpsdrconnector /usr/local/bin

View File

@ -25,8 +25,7 @@ apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/jketterl/runds_connector.git
# latest develop as of 2022-12-11 (std::endl implicit flushing)
cmakebuild runds_connector 06ca993a3c81ddb0a2581b1474895da07752a9e1
cmakebuild runds_connector 0.1.0
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean

View File

@ -38,7 +38,7 @@ case $ARCH in
;;
esac
wget --no-http-keep-alive https://www.sdrplay.com/software/$BINARY
wget https://www.sdrplay.com/software/$BINARY
sh $BINARY --noexec --target sdrplay
patch --verbose -Np0 < /install-lib.$ARCH.patch
@ -48,9 +48,9 @@ cd ..
rm -rf sdrplay
rm $BINARY
git clone https://github.com/pothosware/SoapySDRPlay3.git
# latest from master as of 2021-06-19 (reliability fixes)
cmakebuild SoapySDRPlay3 a869f25364a1f0d5b16169ff908aa21a2ace475d
git clone https://github.com/SDRplay/SoapySDRPlay.git
# latest from master as of 2020-09-04
cmakebuild SoapySDRPlay 105f8a6b3d449982d7ef860790c201aa066b8fa9
SUDO_FORCE_REMOVE=yes apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean

View File

@ -18,16 +18,17 @@ function cmakebuild() {
cd /tmp
STATIC_PACKAGES="libusb-1.0.0 libboost-chrono1.74.0 libboost-date-time1.74.0 libboost-filesystem1.74.0 libboost-program-options1.74.0 libboost-regex1.74.0 libboost-test1.74.0 libboost-serialization1.74.0 libboost-thread1.74.0 libboost-system1.74.0 python3-numpy python3-mako"
STATIC_PACKAGES="libusb-1.0.0 libboost-chrono1.67.0 libboost-date-time1.67.0 libboost-filesystem1.67.0 libboost-program-options1.67.0 libboost-regex1.67.0 libboost-test1.67.0 libboost-serialization1.67.0 libboost-thread1.67.0 libboost-system1.67.0 python3-numpy python3-mako"
BUILD_PACKAGES="git cmake make gcc g++ libusb-1.0-0-dev libboost-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-program-options-dev libboost-regex-dev libboost-test-dev libboost-serialization-dev libboost-thread-dev libboost-system-dev"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/EttusResearch/uhd.git
# 3.15.0.0 Release
mkdir -p uhd/host/build
cd uhd/host/build
git checkout v4.1.0.4
git checkout v3.15.0.0
# see https://github.com/EttusResearch/uhd/issues/350
case `uname -m` in
arm*)

View File

@ -7,9 +7,6 @@ function cmakebuild() {
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
if [[ -f ".gitmodules" ]]; then
git submodule update --init
fi
mkdir build
cd build
cmake ${CMAKE_ARGS:-} ..
@ -21,8 +18,8 @@ function cmakebuild() {
cd /tmp
STATIC_PACKAGES="libfftw3-bin python3 python3-setuptools netcat-openbsd libsndfile1 liblapack3 libusb-1.0-0 libqt5core5a libreadline8 libgfortran5 libgomp1 libasound2 libudev1 ca-certificates libpulse0 libfaad2 libopus0 libboost-program-options1.74.0 libboost-log1.74.0 libcurl4"
BUILD_PACKAGES="wget git libsndfile1-dev libfftw3-dev cmake make gcc g++ liblapack-dev texinfo gfortran libusb-1.0-0-dev qtbase5-dev qtmultimedia5-dev qttools5-dev libqt5serialport5-dev qttools5-dev-tools asciidoctor asciidoc libasound2-dev libudev-dev libhamlib-dev patch xsltproc qt5-qmake libfaad-dev libopus-dev libboost-dev libboost-program-options-dev libboost-log-dev libboost-regex-dev libpulse-dev libcurl4-openssl-dev"
STATIC_PACKAGES="sox libfftw3-bin python3 python3-setuptools netcat-openbsd libsndfile1 liblapack3 libusb-1.0-0 libqt5core5a libreadline7 libgfortran4 libgomp1 libasound2 libudev1 ca-certificates libqt5gui5 libqt5sql5 libqt5printsupport5 libpulse0 libfaad2 libopus0 libboost-program-options1.67.0 libboost-log1.67.0"
BUILD_PACKAGES="wget git libsndfile1-dev libfftw3-dev cmake make gcc g++ liblapack-dev texinfo gfortran libusb-1.0-0-dev qtbase5-dev qtmultimedia5-dev qttools5-dev libqt5serialport5-dev qttools5-dev-tools asciidoctor asciidoc libasound2-dev libudev-dev libhamlib-dev patch xsltproc qt5-default libfaad-dev libopus-dev libgtest-dev libboost-dev libboost-program-options-dev libboost-log-dev libboost-regex-dev"
apt-get update
apt-get -y install auto-apt-proxy
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
@ -51,22 +48,18 @@ tar xfz ${JS8CALL_TGZ}
# patch allows us to build against the packaged hamlib
patch -Np1 -d ${JS8CALL_DIR} < /js8call-hamlib.patch
rm /js8call-hamlib.patch
cmakebuild ${JS8CALL_DIR}
CMAKE_ARGS="-D CMAKE_CXX_FLAGS=-DJS8_USE_HAMLIB_THREE" cmakebuild ${JS8CALL_DIR}
rm ${JS8CALL_TGZ}
WSJT_DIR=wsjtx-2.6.1
WSJT_DIR=wsjtx-2.3.1
WSJT_TGZ=${WSJT_DIR}.tgz
wget https://downloads.sourceforge.net/project/wsjt/${WSJT_DIR}/${WSJT_TGZ}
wget http://physics.princeton.edu/pulsar/k1jt/${WSJT_TGZ}
tar xfz ${WSJT_TGZ}
patch -Np0 -d ${WSJT_DIR} < /wsjtx-hamlib.patch
mv /wsjtx.patch ${WSJT_DIR}
cmakebuild ${WSJT_DIR}
rm ${WSJT_TGZ}
git clone https://github.com/alexander-sholohov/msk144decoder.git
# latest from main as of 2023-02-21
MAKEFLAGS="" cmakebuild msk144decoder fe2991681e455636e258e83c29fd4b2a72d16095
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.
@ -109,7 +102,8 @@ rm -rf dream
rm dream-2.1.1-svn808.tar.gz
git clone https://github.com/mobilinkd/m17-cxx-demod.git
cmakebuild m17-cxx-demod v2.3
# latest master as of 2021-04-20
cmakebuild m17-cxx-demod c1d954fd5e5c53d28a2524e99484f832f9dcb826
git clone https://github.com/hessu/aprs-symbols /usr/share/aprs-symbols
pushd /usr/share/aprs-symbols

View File

@ -18,43 +18,27 @@ function cmakebuild() {
cd /tmp
STATIC_PACKAGES="libfftw3-bin libprotobuf23 libsamplerate0 libicu67 libudev1"
BUILD_PACKAGES="git autoconf automake libtool libfftw3-dev pkg-config cmake make gcc g++ libprotobuf-dev protobuf-compiler libsamplerate-dev libicu-dev libpython3-dev libudev-dev"
STATIC_PACKAGES="libfftw3-bin"
BUILD_PACKAGES="git autoconf automake libtool libfftw3-dev pkg-config cmake make gcc g++"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/jketterl/js8py.git
pushd js8py
# latest develop as of 2022-11-30 (structured callsign data)
git checkout f7e394b7892d26cbdcce5d43c0b4081a2a6a48f6
git checkout 0.1.0
python3 setup.py install
popd
rm -rf js8py
git clone https://github.com/jketterl/csdr.git
cmakebuild csdr 0.18.1
git clone https://github.com/jketterl/pycsdr.git
cd pycsdr
git checkout 0.18.1
./setup.py install install_headers
cd csdr
git checkout 0.17.0
autoreconf -i
./configure
make
make install
cd ..
rm -rf pycsdr
git clone https://github.com/jketterl/codecserver.git
mkdir -p /usr/local/etc/codecserver
cp codecserver/conf/codecserver.conf /usr/local/etc/codecserver
cmakebuild codecserver 0.2.0
git clone https://github.com/jketterl/digiham.git
cmakebuild digiham 0.6.1
git clone https://github.com/jketterl/pydigiham.git
cd pydigiham
git checkout 0.6.1
./setup.py install
cd ..
rm -rf pydigiham
rm -rf csdr
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -159,8 +159,4 @@ h1 {
.imageupload.is-invalid ~ .invalid-feedback {
display: block;
}
.device-log-messages {
max-height: 500px;
}

View File

@ -5,3 +5,21 @@ html, body
height: 100%;
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
}
.sprite {
background-image: url(../gfx/openwebrx-sprites.png);
display: inline-block;
}
.openwebrx-button.highlighted .sprite {
background-image: linear-gradient(rgba(255,127,0,0.5), rgba(255,127,0,0.5)), url(../gfx/openwebrx-sprites.png);
background-blend-mode: overlay;
}
@media only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and (min-device-pixel-ratio: 2) {
.sprite {
background-image: url(../gfx/openwebrx-sprites-2x.png);
background-size: 198px 77px;
}
}

View File

@ -56,9 +56,7 @@
}
.webrx-top-logo {
width: 261px;
padding: 12px;
filter: drop-shadow(0 0 2.5px rgba(0, 0, 0, .9));
/* overwritten by media queries */
display: none;
}
@ -102,6 +100,18 @@
font-size: 10pt;
}
.openwebrx-rx-details-arrow {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 0);
margin: 0;
padding: 0;
line-height: 0;
display: block;
}
.openwebrx-main-buttons .button {
display: block;
width: 55px;
@ -113,10 +123,8 @@
display: none;
}
.openwebrx-main-buttons .button img,
.openwebrx-main-buttons .button svg {
.openwebrx-main-buttons .button img {
height: 38px;
filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.5));
}
.openwebrx-main-buttons a {
@ -195,33 +203,47 @@
}
/*
* RX details arrow up/down switching
* Sprites (images)
*/
.openwebrx-rx-details-arrow {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 0);
margin: 0;
padding: 0;
line-height: 0;
display: block;
.sprite-panel-status {
background-position: 0 0;
width: 44px;
height: 38px;
}
.openwebrx-rx-details-arrow svg {
.sprite-panel-log {
background-position: -44px 0;
width: 38px;
height: 38px;
}
.sprite-panel-receiver {
background-position: -82px 0;
width: 40px;
height: 38px;
}
.sprite-panel-map {
background-position: -122px 0;
width: 38px;
height: 38px;
}
.sprite-panel-settings {
background-position: -160px 0;
width: 38px;
height: 38px;
}
.openwebrx-rx-details-arrow--down .sprite-rx-details-arrow {
background-position: 0 -65px;
width: 43px;
height: 12px;
}
.openwebrx-rx-details-arrow .up {
display: none;
}
.openwebrx-rx-details-arrow--up .down {
display: none;
}
.openwebrx-rx-details-arrow--up .up {
display: initial;
.openwebrx-rx-details-arrow--up .sprite-rx-details-arrow {
background-position: -43px -65px;
width: 43px;
height: 12px;
}

View File

@ -667,6 +667,16 @@ img.openwebrx-mirror-img
text-align: center;
}
#openwebrx-mute-on
{
color: lime;
}
#openwebrx-mute-off
{
color: white;
}
.openwebrx-panel-slider
{
position: relative;
@ -674,6 +684,13 @@ img.openwebrx-mirror-img
width: 95px;
}
.openwebrx-sliderbtn-img
{
width: 14px;
position:relative;
top: 1px;
}
.openwebrx-panel-line
{
padding-top: 5px;
@ -750,19 +767,19 @@ img.openwebrx-mirror-img
}
.openwebrx-overlay {
position: absolute;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
opacity: 0.8;
background-color: #777;
left: 0;
top: 0;
z-index: 1001;
color: white;
font-weight: bold;
font-size: 20pt;
position: fixed;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
opacity: 0.8;
background-color: #777;
left: 0;
top: 0;
z-index: 1001;
color: white;
font-weight: bold;
font-size: 20pt;
}
#openwebrx-autoplay-overlay
@ -771,7 +788,8 @@ img.openwebrx-mirror-img
transition: opacity 0.3s linear;
}
#openwebrx-autoplay-overlay svg {
#openwebrx-autoplay-overlay img
{
width: 150px;
}
@ -917,6 +935,33 @@ img.openwebrx-mirror-img
z-index: 10;
}
#openwebrx-digimode-content .part
{
perspective: 700px;
}
#openwebrx-digimode-content .part
{
animation: new-digimode-data-3d 100ms;
animation-timing-function: linear;
display: inline-block;
perspective-origin: 50% 50%;
transform-origin: 0% 50%;
}
@keyframes new-digimode-data
{
0%{ opacity: 0; }
100%{ opacity: 1; }
}
@keyframes new-digimode-data-3d
{
0%{ transform: rotateX(0deg) rotateY(-90deg) translateX(-5px) scale(1.3); }
100%{ transform: rotateX(0deg) rotateY(0deg) translateX(0) scale(1); }
}
#openwebrx-digimode-select-channel
{
transition: all 500ms;
@ -956,22 +1001,26 @@ img.openwebrx-mirror-img
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.openwebrx-meta-slot > * {
flex: 1 0 0;
flex: 0;
flex-basis: 1.2em;
line-height: 1.2em;
}
.openwebrx-meta-slot, .openwebrx-meta-slot .mute {
.openwebrx-meta-slot, .openwebrx-meta-slot.muted:before {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.openwebrx-meta-slot .mute {
display: none;
.openwebrx-meta-slot.muted:before {
display: block;
content: "";
background-image: url("../gfx/openwebrx-mute.png");
background-position: center;
background-repeat: no-repeat;
cursor: pointer;
position: absolute;
@ -982,17 +1031,6 @@ img.openwebrx-mirror-img
background-color: rgba(0,0,0,.3);
}
.openwebrx-meta-slot .mute svg {
position: absolute;
top: 50%;
left: 0;
transform: translate(0, -50%);
}
.openwebrx-meta-slot.muted .mute {
display: block;
}
.openwebrx-meta-slot.active {
background-color: #95bbdf;
}
@ -1009,30 +1047,18 @@ img.openwebrx-mirror-img
}
.openwebrx-meta-slot .openwebrx-meta-user-image {
flex: 0 1 100%;
flex: 1;
background-position: center;
background-repeat: no-repeat;
line-height: 0;
overflow: hidden;
}
.openwebrx-meta-slot .openwebrx-meta-user-image img {
max-width: 100%;
max-height: 100%;
display: none;
.openwebrx-meta-slot.active.direct .openwebrx-meta-user-image,
#openwebrx-panel-metadata-ysf .openwebrx-meta-slot.active .openwebrx-meta-user-image {
background-image: url("../gfx/openwebrx-directcall.png");
}
.openwebrx-meta-slot.active.direct .openwebrx-meta-user-image .directcall,
.openwebrx-meta-slot.active.individual .openwebrx-meta-user-image .directcall,
#openwebrx-panel-metadata-ysf .openwebrx-meta-slot.active .openwebrx-meta-user-image .directcall,
#openwebrx-panel-metadata-dstar .openwebrx-meta-slot.active .openwebrx-meta-user-image .directcall,
#openwebrx-panel-metadata-m17 .openwebrx-meta-slot.active .openwebrx-meta-user-image .directcall {
display: initial;
}
.openwebrx-meta-slot.active.group .openwebrx-meta-user-image .groupcall,
.openwebrx-meta-slot.active.conference .openwebrx-meta-user-image .groupcall {
display: initial;
.openwebrx-meta-slot.active.group .openwebrx-meta-user-image {
background-image: url("../gfx/openwebrx-groupcall.png");
}
.openwebrx-meta-slot.group .openwebrx-dmr-target:not(:empty):before {
@ -1060,38 +1086,14 @@ img.openwebrx-mirror-img
content: "Down: ";
}
.openwebrx-m17-source:not(:empty):before {
content: "SRC: ";
}
.openwebrx-m17-destination:not(:empty):before {
content: "DEST: ";
}
.openwebrx-dstar-yourcall:not(:empty):before {
content: "UR: ";
}
.openwebrx-dstar-departure:not(:empty):before {
content: "RPT1: ";
}
.openwebrx-dstar-destination:not(:empty):before {
content: "RPT2: ";
}
.openwebrx-meta-slot.individual .openwebrx-nxdn-destination:not(:empty):before {
content: "Direct: ";
}
.openwebrx-meta-slot.conference .openwebrx-nxdn-destination:not(:empty):before {
content: "Conference: ";
}
.openwebrx-maps-pin svg {
.openwebrx-maps-pin {
background-image: url("../gfx/google_maps_pin.svg");
background-position: center;
background-repeat: no-repeat;
width: 15px;
height: 15px;
vertical-align: middle;
background-size: contain;
display: inline-block;
}
.openwebrx-message-panel {
@ -1265,7 +1267,6 @@ img.openwebrx-mirror-img
#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-content-container,
#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-content-container,
#openwebrx-panel-digimodes[data-mode="q65"] #openwebrx-digimode-content-container,
#openwebrx-panel-digimodes[data-mode="msk144"] #openwebrx-digimode-content-container,
#openwebrx-panel-digimodes[data-mode="ft8"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="jt65"] #openwebrx-digimode-select-channel,
@ -1276,8 +1277,7 @@ img.openwebrx-mirror-img
#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="q65"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="msk144"] #openwebrx-digimode-select-channel
#openwebrx-panel-digimodes[data-mode="q65"] #openwebrx-digimode-select-channel
{
display: none;
}
@ -1292,58 +1292,81 @@ img.openwebrx-mirror-img
#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-canvas-container,
#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-canvas-container,
#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-canvas-container,
#openwebrx-panel-digimodes[data-mode="q65"] #openwebrx-digimode-canvas-container,
#openwebrx-panel-digimodes[data-mode="msk144"] #openwebrx-digimode-canvas-container
#openwebrx-panel-digimodes[data-mode="q65"] #openwebrx-digimode-canvas-container
{
height: 200px;
margin: -10px;
}
.openwebrx-zoom-button svg {
.sprite-zoom-in {
background-position: 0 -38px;
width: 27px;
height: 27px;
}
.openwebrx-slider-button svg {
position:relative;
top: 1px;
.sprite-zoom-out {
background-position: -27px -38px;
width: 27px;
height: 27px;
}
.sprite-zoom-in-total {
background-position: -54px -38px;
width: 24px;
height: 27px;
}
.sprite-zoom-out-total {
background-position: -78px -38px;
width: 25px;
height: 27px;
}
.sprite-edit {
background-position: -131px -51px;
width: 14px;
height: 14px;
}
.openwebrx-mute-button svg.muted {
display: none;
}
.openwebrx-mute-button.muted svg.muted {
display: initial;
}
.openwebrx-mute-button.muted svg.unmuted {
display: none;
}
.bookmark .bookmark-actions .openwebrx-button svg {
.sprite-trashcan {
background-position: -145px -38px;
width: 14px;
height: 14px;
}
#openwebrx-waterfall-colors-auto .continuous {
display: none;
.sprite-speaker {
width: 14px;
height: 15px;
}
#openwebrx-waterfall-colors-auto.highlighted .continuous {
display: initial;
.openwebrx-mute-button .sprite-speaker {
background-position: -103px -38px;
}
#openwebrx-waterfall-colors-auto.highlighted .auto {
display: none;
.openwebrx-mute-button.muted .sprite-speaker {
background-position: -117px -38px;
}
.openwebrx-waterfall-container {
flex-grow: 1;
display: flex;
flex-direction: column;
position: relative;
.sprite-squelch {
background-position: -131px -38px;
width: 14px;
height: 13px;
}
.openwebrx-waterfall-container > * {
flex: 0 0 auto;
.sprite-waterfall-auto {
background-position: -103px -53px;
width: 14px;
height: 11px;
}
.sprite-waterfall-default {
background-position: -117px -53px;
width: 14px;
height: 12px;
}
.sprite-bookmark {
background-position: -159px -38px;
width: 21px;
height: 27px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
@ -12,8 +14,8 @@
viewBox="0 0 20 34.892337"
id="svg3455"
version="1.1"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
sodipodi:docname="google_maps_pin.svg">
inkscape:version="0.91 r13725"
sodipodi:docname="Map Pin.svg">
<defs
id="defs3457" />
<sodipodi:namedview
@ -29,16 +31,15 @@
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1381"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-width="1024"
inkscape:window-height="705"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:document-rotation="0" />
fit-margin-bottom="0" />
<metadata
id="metadata3460">
<rdf:RDF>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1 +0,0 @@
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg"><defs><filter id="a" x="-.25" y="-.25" width="1.5" height="1.5" color-interpolation-filters="sRGB"><feFlood flood-color="#000" flood-opacity=".4" result="flood"/><feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1"/><feGaussianBlur in="composite1" result="blur" stdDeviation="66.6"/><feOffset result="offset"/><feComposite in="SourceGraphic" in2="offset" result="composite2"/></filter></defs><path d="M550.98 541.91c-.99-28.904-4.377-57.939-9.421-86.393-6.111-34.469-13.889-85.002-43.983-107.46-17.404-12.988-39.941-17.249-59.865-25.081-9.697-3.81-18.384-7.594-26.537-11.901-27.518 30.176-63.4 45.962-105.19 45.964-41.774 0-77.652-15.786-105.17-45.964-8.153 4.308-16.84 8.093-26.537 11.901-19.924 7.832-42.461 12.092-59.863 25.081-30.096 22.463-37.873 72.996-43.983 107.46-5.045 28.454-8.433 57.489-9.422 86.393-.766 22.387 10.288 25.525 29.017 32.284 23.453 8.458 47.666 14.737 72.041 19.884 47.077 9.941 95.603 17.582 143.92 17.924 48.318-.343 96.844-7.983 143.92-17.924 24.375-5.145 48.59-11.424 72.041-19.884 18.736-6.757 29.789-9.895 29.023-32.284zM306 325.99c90.56-.01 123.15-90.68 131.68-165.17C448.19 69.06 404.8 0 306 0c-98.78 0-142.19 69.055-131.68 160.82C182.86 235.304 215.434 326 306 325.99z" filter="url(#a)" transform="matrix(.42446 0 0 .42484 70.12 69)" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -1 +0,0 @@
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg"><defs><filter id="a" x="-.25" y="-.25" width="1.5" height="1.5" color-interpolation-filters="sRGB"><feFlood flood-color="#000" flood-opacity=".4" result="flood"/><feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1"/><feGaussianBlur in="composite1" result="blur" stdDeviation="66.6"/><feOffset result="offset"/><feComposite in="SourceGraphic" in2="offset" result="composite2"/></filter></defs><g fill="#fff"><g transform="matrix(.42446 0 0 .42484 129.12 42)" filter="url(#a)"><path d="M27.982.002c-98.778 0-142.188 69.056-131.678 160.823 8.54 74.484 41.112 165.183 131.678 165.173 90.558-.01 123.148-90.682 131.678-165.173C170.17 69.063 126.781.002 27.982.002zm277.996 0C207.2.002 163.79 69.058 174.3 160.825c8.54 74.484 41.113 165.183 131.678 165.173 90.559-.01 123.148-90.682 131.678-165.173C448.166 69.063 404.777.002 305.978.002zM-77.209 311.093c-8.153 4.308-16.84 8.09-26.537 11.898-19.924 7.833-42.463 12.095-59.863 25.084-30.095 22.463-37.871 72.996-43.98 107.46-5.045 28.454-8.435 57.492-9.424 86.395-.766 22.388 10.288 25.523 29.015 32.284 23.453 8.458 47.665 14.737 72.04 19.884 47.075 9.941 95.6 17.583 143.916 17.925 46.64-.33 93.461-7.487 138.998-16.923 45.538 9.437 92.359 16.593 138.999 16.923 48.317-.343 96.841-7.984 143.917-17.925 24.374-5.145 48.593-11.424 72.043-19.884 18.736-6.757 29.786-9.894 29.02-32.284h.01c-.99-28.903-4.38-57.941-9.424-86.395-6.111-34.47-13.886-85.002-43.98-107.46-17.404-12.989-39.94-17.252-59.863-25.084-9.697-3.81-18.384-7.59-26.537-11.898-27.517 30.177-63.398 45.962-105.186 45.965-41.773 0-77.65-15.787-105.17-45.965-8.153 4.308-16.84 8.09-26.537 11.898-2.394.941-4.828 1.826-7.284 2.685-2.456-.859-4.89-1.744-7.284-2.685-9.697-3.81-18.383-7.59-26.537-11.898-27.517 30.177-63.397 45.962-105.186 45.965-41.773 0-77.65-15.787-105.17-45.965z"/></g><g transform="matrix(.42446 0 0 .42484 70.12 102)" filter="url(#a)"><path d="M550.98 541.91c-.99-28.904-4.377-57.939-9.421-86.393-6.111-34.469-13.889-85.002-43.983-107.46-17.404-12.988-39.941-17.249-59.865-25.081-9.697-3.81-18.384-7.594-26.537-11.901-27.518 30.176-63.4 45.962-105.19 45.964-41.774 0-77.652-15.786-105.17-45.964-8.153 4.308-16.84 8.093-26.537 11.901-19.924 7.832-42.461 12.092-59.863 25.081-30.096 22.463-37.873 72.996-43.983 107.46-5.045 28.454-8.433 57.489-9.422 86.393-.766 22.387 10.288 25.525 29.017 32.284 23.453 8.458 47.666 14.737 72.041 19.884 47.077 9.941 95.603 17.582 143.92 17.924 48.318-.343 96.844-7.983 143.92-17.924 24.375-5.145 48.59-11.424 72.041-19.884 18.736-6.757 29.789-9.895 29.023-32.284zM306 325.99c90.56-.01 123.15-90.68 131.68-165.17C448.19 69.06 404.8 0 306 0c-98.78 0-142.19 69.055-131.68 160.82C182.86 235.304 215.434 326 306 325.99z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg width="700" height="700" xmlns="http://www.w3.org/2000/svg"><g class="layer"><circle cx="350" cy="350" r="330" stroke="#fff" stroke-width="36" fill="none"/><path d="M195 211v278l366-139-366-139z" fill="#fff"/></g></svg>

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,28 +0,0 @@
<svg width="80" height="80" xmlns="http://www.w3.org/2000/svg">
<defs>
<g id="top-logo"><g fill="none" stroke="#fff" stroke-linecap="square" stroke-linejoin="round" stroke-width="6"><path d="M517.23 68.215a35.5 33.2 0 0 1-11.936-26.458 35.5 33.2 0 0 1 14.593-25.266M561.62 16.49a35.5 33.2 0 0 1 14.593 25.266 35.5 33.2 0 0 1-11.936 26.458M551.57 29.079a18.4 19 0 0 1 7.564 14.46 18.4 19 0 0 1-6.187 15.142M528.56 58.681a18.4 19 0 0 1-6.187-15.142 18.4 19 0 0 1 7.564-14.46"/></g><g fill="none"><path d="M540.75 40.25v41.5h-322v-.5h322" stroke="#fff" stroke-width="4.5"/><path d="M13 81.5h58zm76 0h128z" stroke="#ccc" stroke-width="5"/></g><g aria-label="OpenWebRX"><path d="M207.85 45.906c0-11.755-5.104-17.787-14.771-17.787-5.877 0-10.363 2.475-13.688 7.579v-6.574h-6.96v42.301h6.96V47.53c0-8.352 4.717-13.379 12.141-13.379 6.264 0 9.357 4.022 9.357 11.987v25.288h6.96zm-53.515.619h-24.747c.696-7.966 5.645-12.528 13.301-12.528 6.805 0 11.368 4.794 11.445 12.528zm6.96 2.01c0-12.683-7.192-20.416-18.483-20.416-12.605 0-20.725 8.662-20.725 22.581 0 13.301 8.352 21.808 21.963 21.808 5.336 0 10.595-1.082 15.621-3.248v-6.573c-4.872 2.63-9.821 3.944-15.235 3.944-9.203 0-14.461-5.104-15.08-14.693h31.939zm-54.907 1.78c0 10.44-4.64 16.394-11.832 16.394-7.192 0-11.832-5.955-11.832-16.395s4.64-16.395 11.832-16.395c7.192 0 11.832 5.955 11.832 16.395zm-23.664 14.77c2.939 5.027 7.347 7.424 13.533 7.424 10.053 0 17.323-8.661 17.323-22.195S106.31 28.12 96.257 28.12c-6.187 0-10.595 2.397-13.533 7.424v-6.419h-6.96v58.387h6.96zm-44.389-51.04c-15.699 0-26.139 11.291-26.139 29.232 0 18.02 10.44 29.232 26.139 29.232s26.061-11.29 26.061-29.232-10.363-29.232-26.061-29.232zm0 6.187c10.827 0 18.019 8.506 18.019 23.045s-7.192 23.045-18.019 23.045-18.096-8.507-18.096-23.045 7.27-23.045 18.096-23.045z" fill="#ccc"/><path d="m480.99 42.658 18.792-27.608h-15.235l-12.296 18.173-12.373-18.173h-15.157l18.792 27.608-19.565 28.768h15.235l13.069-19.256 13.147 19.256h15.157zm-76.405 7.424h4.099c5.026 0 7.269 2.01 9.898 7.347l6.883 13.997h15.467l-7.888-16.008c-2.939-5.955-5.8-9.358-10.13-10.363 7.268-2.243 10.826-7.038 10.826-14.461 0-10.904-6.883-15.544-21.499-15.544h-22.195v56.376h14.54zm6.11-10.053h-6.11V25.568h6.11c5.954 0 8.506 1.933 8.506 7.192s-2.552 7.27-8.507 7.27zm-62.873-4.485V12.653h-6.96v58.773h6.96v-6.342c2.939 5.027 7.347 7.424 13.533 7.424 10.053 0 17.323-8.66 17.323-22.195s-7.269-22.195-17.323-22.195c-6.186 0-10.595 2.398-13.533 7.424zm23.664 14.77c0 10.44-4.64 16.396-11.832 16.396s-11.832-5.955-11.832-16.395 4.64-16.395 11.832-16.395 11.832 5.954 11.832 16.395zm-48.72-3.789H298.02c.696-7.965 5.646-12.528 13.301-12.528 6.806 0 11.368 4.795 11.445 12.528zm6.96 2.011c0-12.683-7.192-20.416-18.483-20.416-12.605 0-20.725 8.661-20.725 22.581 0 13.301 8.352 21.808 21.963 21.808 5.336 0 10.595-1.083 15.621-3.248v-6.573c-4.872 2.63-9.821 3.944-15.235 3.944-9.202 0-14.46-5.104-15.08-14.693h31.94zm-112.75-33.485 14.152 56.376h9.59l11.987-48.952 11.909 48.952h9.589l14.152-56.376h-7.733L268.79 62.688l-11.832-47.637h-8.584l-11.832 47.637-11.832-47.637z" fill="#fff"/></g></g>
<g id="panel-log" stroke="#fff"><g fill="none" stroke-linecap="round" stroke-width="3.5"><path d="M21 57h39M21 48.5h39M21 40h34M21 31.5h39M21 23h39"/></g><g fill="#fff" stroke-linejoin="round" stroke-width="2.1"><path d="M13.5 63.5c0 2.5 2.5 5 5 5h45c2.5 0 5-2.5 5-5v-46c0-2.5-2.5-5-5-5H25l-11.5 16z" fill-opacity=".35" style="mix-blend-mode:normal"/><path d="M25 12.5s-1.323 7.847 4 15c-9.294-1.268-15.5 1-15.5 1"/></g></g>
<g id="panel-status" fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="2.1"><g fill-opacity=".35"><path d="M9.683 26.483c-3.13 0-5.666-2.455-5.666-5.483 0-3.029 2.536-5.484 5.666-5.484h48.634c3.13 0 5.666 2.455 5.666 5.484 0 3.028-2.537 5.483-5.666 5.483zM19.113 44.913c-3.13 0-5.666-2.455-5.666-5.483s2.537-5.484 5.666-5.484h48.634c3.13 0 5.666 2.455 5.666 5.484s-2.537 5.483-5.666 5.483zM9.683 63.483c-3.13 0-5.666-2.455-5.666-5.483s2.536-5.484 5.666-5.484h48.634c3.13 0 5.666 2.455 5.666 5.484s-2.537 5.483-5.666 5.483z"/></g><path d="M10.06 26.456c-3.322 0-6.016-2.443-6.016-5.456 0-3.013 2.694-5.456 6.016-5.456h5.932c3.322 0 6.016 2.443 6.016 5.456 0 3.013-2.694 5.456-6.016 5.456zM19.48 44.886c-3.317 0-6.007-2.443-6.007-5.456 0-3.013 2.69-5.456 6.007-5.456h32.516c3.317 0 6.007 2.443 6.007 5.456 0 3.014-2.69 5.456-6.007 5.456zM10.052 63.456c-3.318 0-6.008-2.443-6.008-5.456 0-3.013 2.69-5.456 6.008-5.456h24.944c3.318 0 6.008 2.443 6.008 5.456 0 3.013-2.69 5.456-6.008 5.456z"/></g>
<g id="panel-receiver" stroke="#fff" stroke-width="2.1"><path d="M66 29.05 12 10" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="6.8" y="29.05" width="67.5" height="38.5" rx="5" ry="5" fill="#fff" fill-opacity=".35"/><g fill="#fff" stroke-linecap="round" stroke-linejoin="round" fill-opacity=".35"><rect x="12.3" y="34.05" width="28.5" height="9.5" rx="2" ry="2"/><circle cx="56.55" cy="48.55" r="13.5"/><g transform="translate(0 -.5)"><circle cx="17.05" cy="50.55" r="3"/><circle cx="26.3" cy="50.55" r="3"/><circle cx="35.55" cy="50.55" r="3"/><circle cx="17.05" cy="57.05" r="3"/><circle cx="26.3" cy="57.05" r="3"/><circle cx="35.55" cy="57.05" r="3"/></g></g></g>
<g id="panel-map" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.1"><g stroke-opacity=".989"><path d="m30 11-19 8v50l19-5 19 5 20-5V11l-10.844 4.879a11.93 11.93 0 0 1 2.394 7.172c0 2.111-.55 4.09-1.507 5.814h.004L49 47 38.279 29.256h.013a11.922 11.922 0 0 1-1.742-6.205c0-2.905 1.034-5.568 2.752-7.644z" fill="#fff" fill-opacity=".35"/><g fill="none"><path d="M30 11v53M49 47v22M11 59l19-25 19 18 20-6"/></g></g><circle cx="48.55" cy="23.05" r="6.5" fill="none" stroke-opacity=".989"/><path d="M48.551 11.051c-6.627 0-12 5.373-12 12 0 2.274.643 4.393 1.742 6.205h-.013L49 47l10.048-18.135h-.004a11.935 11.935 0 0 0 1.507-5.814c0-6.628-5.372-12-12-12z" fill="#fff" fill-opacity=".2"/></g>
<g id="panel-settings" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.6"><path d="m39.855 10-1 .021c-1.041.04-2.078.134-3.11.282-.546.078-.853.587-.78 1.135a7.876 7.876 0 0 1-.022 1.841 6.504 6.504 0 0 1-5.428 5.428 7.889 7.889 0 0 1-1.992 0 6.501 6.501 0 0 1-4.156-2.463c-.336-.437-.916-.581-1.358-.25a29.998 29.998 0 0 0-6.017 6.016c-.331.442-.188 1.02.25 1.355a6.505 6.505 0 0 1 2.465 4.158 7.885 7.885 0 0 1 0 1.993 6.504 6.504 0 0 1-5.428 5.427 7.862 7.862 0 0 1-1.992-.002c-.461-.072-.904.256-.983.803a30.012 30.012 0 0 0-.002 8.51c.079.547.588.853 1.135.781a7.876 7.876 0 0 1 1.842.022 6.504 6.504 0 0 1 5.428 5.427 7.889 7.889 0 0 1 0 1.992 6.501 6.501 0 0 1-2.463 4.157c-.438.336-.581.915-.25 1.357a29.998 29.998 0 0 0 6.015 6.018c.442.33 1.02.187 1.356-.25a6.505 6.505 0 0 1 4.158-2.465 7.885 7.885 0 0 1 1.992 0 6.504 6.504 0 0 1 5.428 5.428 7.862 7.862 0 0 1-.002 1.992c-.072.46.256.904.803.982a30.012 30.012 0 0 0 8.51.002c.546-.078.853-.59.78-1.136a7.859 7.859 0 0 1 .022-1.84 6.504 6.504 0 0 1 5.428-5.428 7.889 7.889 0 0 1 1.992 0 6.501 6.501 0 0 1 4.156 2.463c.336.437.916.581 1.358.25a29.998 29.998 0 0 0 6.017-6.016c.331-.442.188-1.02-.25-1.355a6.505 6.505 0 0 1-2.465-4.158 7.885 7.885 0 0 1 0-1.993 6.504 6.504 0 0 1 5.428-5.427 7.862 7.862 0 0 1 1.992.002c.461.072.904-.258.983-.805a29.977 29.977 0 0 0 .002-8.508c-.079-.546-.59-.853-1.137-.781a7.859 7.859 0 0 1-1.84-.022 6.504 6.504 0 0 1-5.427-5.427 7.889 7.889 0 0 1 0-1.992 6.501 6.501 0 0 1 2.462-4.157c.438-.336.582-.915.25-1.357a29.998 29.998 0 0 0-6.015-6.018c-.442-.33-1.02-.187-1.356.25a6.505 6.505 0 0 1-4.158 2.465 7.885 7.885 0 0 1-1.992 0 6.504 6.504 0 0 1-5.428-5.428 7.862 7.862 0 0 1 .002-1.992c.072-.46-.256-.904-.803-.982a30.002 30.002 0 0 0-4.4-.305zM40 26.5A13.5 13.5 0 0 1 53.5 40 13.5 13.5 0 0 1 40 53.5 13.5 13.5 0 0 1 26.5 40 13.5 13.5 0 0 1 40 26.5z" fill="#fff" fill-opacity=".35"/><circle cx="40" cy="40" r="13.5" fill="#fff" fill-opacity=".2"/><circle cx="40" cy="40" r="8" fill="none"/></g>
<g id="zoom-in" stroke="#fff"><circle cx="31.75" cy="32" r="27.5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="8"/><path d="m57.147 47.242 20.644 20.645a4 4 90 0 1 0 5.657l-3.748 3.748a4 4 180 0 1-5.656 0L47.743 56.647" fill="#fff" stroke-width="2.1"/><path d="M31.75 18.5v27M18.25 32h27" fill="none" stroke-linecap="round" stroke-width="10"/></g>
<g id="zoom-out" stroke="#fff"><circle cx="31.75" cy="32" r="27.5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="8"/><path d="m57.147 47.242 20.644 20.645a4 4 90 0 1 0 5.657l-3.748 3.748a4 4 180 0 1-5.656 0L47.743 56.647" fill="#fff" stroke-width="2.1"/><path d="M18.25 32h27" fill="none" stroke-linecap="round" stroke-width="10"/></g>
<g id="zoom-out-total" stroke="#fff"><circle cx="44.001" cy="51.307" r="16.326" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.75"/><path d="M59.079 60.356 71.6 72.877a2 2 90 0 1 0 2.829l-2.755 2.754a2 2 180 0 1-2.828 0L53.496 65.94" fill="#fff" stroke-width="1.247"/><path d="m20.893 1.244-16.199 16.9 16.199 16.9V21.698H59.5v13.346l16.199-16.9L59.5 1.244V14.7H20.893z" fill="#fff"/></g>
<g id="zoom-in-total" stroke="#fff"><circle cx="44.701" cy="51.307" r="16.326" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.75"/><path d="M59.779 60.356 72.3 72.877a2 2 90 0 1 0 2.829l-2.755 2.754a2 2-180 0 1-2.828 0L54.196 65.94" fill="#fff" stroke-width="1.247"/><path d="M4.8 1.244v33.801L17.595 21.7H62.4l12.793 13.346v-33.8L62.296 14.698H17.698z" fill="#fff"/></g>
<g id="speaker" stroke="#fff"><path d="M33.5 7.15 16.74 25.201H2.65v31.7h16.041L33.5 72.848z" fill="#fff" stroke-width="2.1"/><g fill="none"><path d="M46.336 16.041A20 30 0 0 1 54.3 40a20 30 0 0 1-7.964 23.959" stroke-width="3"/><path d="M52.716 9.652A30.6 38 0 0 1 64.9 40a30.6 38 0 0 1-12.184 30.348" stroke-width="4.2"/><path d="M59.035 2.065A41.1 47.5 0 0 1 75.4 40a41.1 47.5 0 0 1-16.365 37.935" stroke-width="5.4"/></g></g>
<g id="speaker-muted"><path d="M33.5 7.15 16.74 25.201H2.65v31.7h16.041L33.5 72.848z" fill="#fff" stroke="#fff" stroke-width="2.1"/></g>
<g id="waterfall-auto"><path d="M33.512 72.05s3.393-64.129 8.24-64.093c4.48.034 4.392 28.286 9.98 28.198 4.775-.074 5.279-13.721 6.33-17.88 1.013-4.004 2.915-5.172 5.321-4.894 2.993.346 8.205 5.925 10.588 13.761 4.16 13.677 5.53 44.907 5.53 44.907z" fill="#ccc" stroke="#ccc"/><path d="M29.3 68.8 18.4 54 7.5 68.8zM7.5 11.2 18.4 26l10.9-14.8zM18.4 26v28" fill="#fff" stroke="#fff" stroke-width="7.5"/></g>
<g id="waterfall-default"><path d="M36.356 75.295s3.187-34.502 7.74-34.482c4.207.018 4.125 15.218 9.374 15.171 4.485-.04 4.958-7.382 5.945-9.62.951-2.154 2.738-2.782 4.998-2.632 2.812.186 7.707 3.187 9.945 7.403 3.906 7.358 5.193 24.16 5.193 24.16z" fill="#ccc" stroke="#ccc" stroke-width=".711"/><path d="M18.4 25.424v29.075m-10.9 0L18.4 69.3l10.9-14.801zm10.9-43.875L7.5 25.425h21.801z" fill="#fff" stroke="#fff" stroke-width="7.5"/></g>
<g id="squelch" fill="#fff" stroke-width=".767"><path d="M33.333 24.7c-.058-14.132-5.913-21.9-16.58-21.9-10.145 0-16 7.673-16 20.963 0 10.575 3.305 15.722 11.884 18.343l5.913 1.872c5.797 1.778 7.942 4.399 7.942 9.92 0 5.71-3.246 9.172-8.521 9.172-5.913 0-9.218-4.024-9.508-11.324H0C.521 66.345 6.724 74.3 17.506 74.3c10.899 0 17.333-8.142 17.333-22.087 0-10.762-3.362-16.378-11.188-18.81l-6.609-2.06c-6.203-1.965-8-3.93-8-8.89 0-5.148 2.782-8.423 7.304-8.423 5.507 0 8.58 3.743 8.87 10.669zM75.362 62.508C78.202 56.893 80 47.534 80 38.363c0-10.014-2.203-19.653-5.913-26.11C70.435 5.887 65.681 2.8 59.653 2.8S48.87 5.888 45.218 12.252C41.45 18.71 39.305 28.35 39.305 38.55s2.203 19.84 5.913 26.298c3.652 6.364 8.406 9.452 14.435 9.452 4.405 0 7.594-1.404 10.956-4.68l4.986 7.581L80 69.621zM64.638 46.13l-4.406 7.58 4.464 6.833c-1.391 1.123-3.246 1.778-5.101 1.778C52.638 62.321 48 52.869 48 38.549c0-14.412 4.58-23.771 11.652-23.771s11.652 9.358 11.652 23.864c0 5.615-.638 10.67-1.913 14.787z"/></g>
<g id="trashcan"><path d="M56.667 13.333V6.666A6.67 6.67 0 0 0 50 0H30a6.67 6.67 0 0 0-6.666 6.666v6.667H6.667V20h6.666v53.333A6.67 6.67 0 0 0 20 79.999h40a6.67 6.67 0 0 0 6.667-6.666V20h6.666v-6.667zM30 6.666h20v6.667H30zm30 66.667H20V20h40zM36.667 26.666H30v40h6.667zm13.333 0h-6.667v40H50z" fill="#fff"/></g>
<g id="edit"><path d="m52.5 7.5-45 45v20h20l45-45zm12.93 20-4.697 4.697-12.93-12.93L52.5 14.57zM15.302 51.768l4.06-4.06c1.533 1.125 3.575 2.002 5.81 2.002 2.148 0 4.453-.8 6.595-2.942l10-10c2.52-2.52 4.622-7.728.968-12.43l1.535-1.535 12.93 12.93L28.23 64.698zm22.93-18.535-10 10c-2.062 2.058-4.007 1.595-5.27.875L39.14 27.93c1.465 2.485-.27 4.643-.907 5.303zM12.5 56.035 23.965 67.5H12.5z" fill="#fff"/></g>
<g id="bookmark"><path d="M62.5 0h-45A7.5 7.5 0 0 0 10 7.5V80l30-17.5L70 80V7.5A7.5 7.5 0 0 0 62.5 0zm0 66.942L40 53.817 17.5 66.942V8.437a.938.938 0 0 1 .938-.938h43.124c.518 0 .938.42.938.937z" fill="#fff"/></g>
<g id="rx-details-arrow-down"><path d="M5 0C2.5 0 0 2.5 0 5v7h43V5c0-2.5-2.5-5-5-5zm8 4h17l-8.5 6.5z" fill="#999" fill-opacity=".196"/><path d="M13 4h17l-8.5 6.5z" fill="#848484" fill-opacity=".592"/></g>
<g id="rx-details-arrow-up"><path d="M5 0C2.5 0 0 2.5 0 5v7h43V5c0-2.5-2.5-5-5-5zm16.5 3L30 9.5H13z" fill="#999" fill-opacity=".196"/><path d="M30 9.5H13L21.5 3z" fill="#848484" fill-opacity=".592"/></g>
<g id="maps-pin" transform="translate(-965.78 -331.788) scale(1.1856)"><path d="M817.11 282.97c-1.258 1.343-2.046 3.299-2.016 5.139.064 3.845 1.797 5.3 4.569 10.592.998 2.328 2.04 4.792 3.031 8.873.138.602.272 1.16.334 1.21.062.048.197-.513.334-1.115.991-4.081 2.033-6.543 3.032-8.872 2.77-5.291 4.504-6.747 4.568-10.592.03-1.84-.76-3.797-2.018-5.14-1.437-1.534-3.605-2.67-5.916-2.717-2.311-.047-4.48 1.088-5.918 2.622z" fill="#ff4646" stroke="#d73534"/><circle cx="823.03" cy="288.25" r="3.035" fill="#590000"/></g>
<g id="play-button"><circle cx="350" cy="350" r="330" fill="none" stroke="#fff" stroke-width="36"/><path d="M195 211v278l366-139z" fill="#fff"/></g>
<g id="meta-mute" stroke="#fff" stroke-width="5"><path stroke-linejoin="round" style="paint-order:fill" fill="none" d="m21.989 47.699 17.4 15.051V13.769L22.235 28.606H6v19.093z" transform="matrix(5.3513 0 0 5.3723 -.73 -1.542)"/><path d="m48.652 50.27 20.743-24.299M69.395 50.27 48.652 25.971" stroke-linecap="round" transform="matrix(5.3513 0 0 5.3723 -.73 -1.542)"/></g>
<g id="waterfall-continuous"><g stroke="#fff" stroke-width="8"><path d="M5 40A35 35 0 0 1 26.606 7.664a35 35 0 0 1 38.143 7.587" fill="none"/><path d="m68.284 11.716 2.828 9.9-9.899-2.829z" fill="#fff"/></g><path d="m48.008 48.144 2.816 8.624h9.035L45.075 14h-9.739L20.141 56.768h8.976l2.875-8.624zm-2.405-7.333H34.456l5.573-16.72z" fill="#fff" aria-label="A"/><g stroke="#fff" stroke-width="8"><path d="M75 40a35 35 0 0 1-21.606 32.336 35 35 0 0 1-38.143-7.587" fill="none"/><path d="m11.716 68.284-2.828-9.9 9.899 2.829z" fill="#fff"/></g></g>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,25 +1,22 @@
<div class="webrx-top-container">
<div class="webrx-top-bar">
<a href="https://www.openwebrx.de/" target="_blank"><svg viewBox="0 0 591 100" class="webrx-top-logo"><title>Visit the OpenWebRX homepage</title><use xlink:href="${document_root}static/gfx/svg-defs.svg#top-logo"></use></svg></a>
<a href="https://www.openwebrx.de/" target="_blank"><img src="${document_root}static/gfx/openwebrx-top-logo.png" class="webrx-top-logo" alt="OpenWebRX Logo"/></a>
<img class="webrx-rx-avatar openwebrx-photo-trigger" src="${document_root}static/gfx/openwebrx-avatar.png" alt="Receiver avatar"/>
<div class="webrx-rx-texts openwebrx-photo-trigger">
<h1 class="webrx-rx-title">${receiver_name}</h1>
<div class="webrx-rx-desc">${receiver_location} | Loc: ${locator}, ASL: ${receiver_asl} m</div>
</div>
<section class="openwebrx-main-buttons">
<div class="button" data-toggle-panel="openwebrx-panel-status"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-status"></use></svg><br/>Status</div>
<div class="button" data-toggle-panel="openwebrx-panel-log"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-log"></use></svg><br/>Log</div>
<div class="button" data-toggle-panel="openwebrx-panel-receiver"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-receiver"></use></svg><br/>Receiver</div>
<a class="button" href="${document_root}map" target="openwebrx-map"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-map"></use></svg><br/>Map</a>
<a class="button" href="${document_root}settings" target="openwebrx-settings"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-settings"></use></svg><br/>Settings</a>
<div class="button" data-toggle-panel="openwebrx-panel-status"><span class="sprite sprite-panel-status"></span><br/>Status</div>
<div class="button" data-toggle-panel="openwebrx-panel-log"><span class="sprite sprite-panel-log"></span><br/>Log</div>
<div class="button" data-toggle-panel="openwebrx-panel-receiver"><span class="sprite sprite-panel-receiver"></span><br/>Receiver</div>
<a class="button" href="${document_root}map" target="openwebrx-map"><span class="sprite sprite-panel-map"></span><br/>Map</a>
<a class="button" href="${document_root}settings" target="openwebrx-settings"><span class="sprite sprite-panel-settings"></span><br/>Settings</a>
</section>
</div>
<div class="openwebrx-description-container">
<div class="webrx-rx-photo-title">${photo_title}</div>
<div class="webrx-rx-photo-desc">${photo_desc}</div>
</div>
<a class="openwebrx-rx-details-arrow openwebrx-rx-details-arrow--down openwebrx-photo-trigger">
<svg class="down" viewBox="0 0 43 12"><use xlink:href="${document_root}static/gfx/svg-defs.svg#rx-details-arrow-down"></use></svg>
<svg class="up" viewBox="0 0 43 12"><use xlink:href="${document_root}static/gfx/svg-defs.svg#rx-details-arrow-up"></use></svg>
</a>
<a class="openwebrx-rx-details-arrow openwebrx-rx-details-arrow--down openwebrx-photo-trigger"><span class="sprite sprite-rx-details-arrow"></span></a>
</div>

View File

@ -23,14 +23,7 @@
<html>
<head>
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" sizes="16x16 32x32" />
<link rel="icon" type="image/png" sizes="32x32" href="static/gfx/favicon32.png" />
<link rel="icon" type="image/png" sizes="44x44" href="static/gfx/favicon44.png" />
<link rel="icon" type="image/png" sizes="64x64" href="static/gfx/favicon64.png" />
<link rel="icon" type="image/png" sizes="96x96" href="static/gfx/favicon96.png" />
<link rel="icon" type="image/png" sizes="128x128" href="static/gfx/favicon128.png" />
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<meta name="msapplication-TileImage" content="mstile-144x144.png">
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" />
<script src="compiled/receiver.js"></script>
<link rel="stylesheet" type="text/css" href="static/lib/nanoscroller.css" />
<link rel="stylesheet" type="text/css" href="static/css/openwebrx.css" />
@ -41,198 +34,130 @@
<body onload="openwebrx_init();">
<div id="webrx-page-container">
${header}
<div class="openwebrx-waterfall-container">
<div id="openwebrx-frequency-container">
<div id="openwebrx-bookmarks-container"></div>
<div id="openwebrx-scale-container">
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
<div id="openwebrx-frequency-container">
<div id="openwebrx-bookmarks-container"></div>
<div id="openwebrx-scale-container">
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
</div>
</div>
<div id="webrx-canvas-background">
<div id="webrx-canvas-container">
<!-- add canvas here by javascript -->
</div>
</div>
<div id="openwebrx-panels-container">
<div id="openwebrx-panels-container-left">
<div class="openwebrx-panel" id="openwebrx-panel-digimodes" style="display: none; width: 619px;" data-panel-name="digimodes">
<div id="openwebrx-digimode-canvas-container">
<div id="openwebrx-digimode-select-channel"></div>
</div>
<div id="openwebrx-digimode-content-container">
<div class="gradient"></div>
<div id="openwebrx-digimode-content">
<span id="openwebrx-cursor-blink"></span>
</div>
</div>
</div>
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-wsjt-message" style="display: none; width: 619px;" data-panel-name="wsjt-message"></div>
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-js8-message" style="display:none; width: 619px;" data-panel-name="js8-message"></div>
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-packet-message" style="display: none; width: 619px;" data-panel-name="aprs-message"></div>
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-pocsag-message" style="display: none; width: 619px;" data-panel-name="pocsag-message"></div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-ysf" style="display: none;" data-panel-name="metadata-ysf">
<div class="openwebrx-meta-slot">
<div class="openwebrx-ysf-mode"></div>
<div class="openwebrx-meta-user-image"></div>
<div class="openwebrx-ysf-source"><span class="location"></span><span class="callsign"></span></div>
<div class="openwebrx-ysf-up"></div>
<div class="openwebrx-ysf-down"></div>
</div>
</div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-dmr" style="display: none;" data-panel-name="metadata-dmr">
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
<div class="openwebrx-dmr-slot">Timeslot 1</div>
<div class="openwebrx-meta-user-image"></div>
<div class="openwebrx-dmr-id"></div>
<div class="openwebrx-dmr-name"></div>
<div class="openwebrx-dmr-target"></div>
</div>
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
<div class="openwebrx-dmr-slot">Timeslot 2</div>
<div class="openwebrx-meta-user-image"></div>
<div class="openwebrx-dmr-id"></div>
<div class="openwebrx-dmr-name"></div>
<div class="openwebrx-dmr-target"></div>
</div>
</div>
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" style="width: 619px;">
<div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
<div class="nano-content">
<div id="openwebrx-client-log-title">OpenWebRX client log</div>
<div>
Author contact: <a href="http://www.justjakob.de/" target="_blank">Jakob Ketterl, DD5JFK</a> |
<a href="https://www.openwebrx.de" target="_blank">OpenWebRX homepage</a>
</div>
<div>Support and information: <a href="https://groups.io/g/openwebrx" target="_blank">Groups.io Mailinglist</a></div>
<div id="openwebrx-debugdiv"></div>
</div>
</div>
</div>
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" style="width: 615px;" data-panel-transparent="true">
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer" data-type="audiobuffer"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output" data-type="audiooutput"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed" data-type="audiospeed"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed" data-type="networkspeed"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu" data-type="cpu"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-clients" data-type="clients"></div>
</div>
</div>
<div id="webrx-canvas-background">
<div id="webrx-canvas-container">
<!-- add canvas here by javascript -->
</div>
</div>
<div id="openwebrx-panels-container">
<div id="openwebrx-panels-container-left">
<div class="openwebrx-panel" data-panel-name="client-under-devel" style="width: 245px; background-color: Red;">
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
<br />We're working on the code right now, so the application might fail.
</div>
<div class="openwebrx-panel" id="openwebrx-panel-digimodes" style="display: none; width: 619px;" data-panel-name="digimodes">
<div id="openwebrx-digimode-canvas-container">
<div id="openwebrx-digimode-select-channel"></div>
<div id="openwebrx-panels-container-right">
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" style="width: 259px;">
<div class="openwebrx-panel-line frequencies-container">
<div class="frequencies">
<div class="webrx-actual-freq"></div>
<div class="webrx-mouse-freq"></div>
</div>
<div id="openwebrx-digimode-content-container">
<div class="gradient"></div>
<div id="openwebrx-digimode-content">
<span id="openwebrx-cursor-blink"></span>
</div>
<div class="openwebrx-button openwebrx-square-button openwebrx-bookmark-button" style="display:none;" title="Add bookmark...">
<span class="sprite sprite-bookmark"></span>
</div>
</div>
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-wsjt-message" style="display: none; width: 619px;" data-panel-name="wsjt-message"></div>
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-js8-message" style="display:none; width: 619px;" data-panel-name="js8-message"></div>
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-packet-message" style="display: none; width: 619px;" data-panel-name="aprs-message"></div>
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-pocsag-message" style="display: none; width: 619px;" data-panel-name="pocsag-message"></div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-m17" style="display: none;" data-panel-name="metadata-m17">
<div class="openwebrx-meta-slot">
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
</div>
<div class="openwebrx-m17-source"></div>
<div class="openwebrx-m17-destination"></div>
<div class="openwebrx-panel-line">
<select id="openwebrx-sdr-profiles-listbox" onchange="sdr_profile_changed();">
</select>
</div>
<div class="openwebrx-modes openwebrx-panel-line"></div>
<div class="openwebrx-panel-line">
<div title="Mute on/off" class="openwebrx-button openwebrx-mute-button" onclick="toggleMute();"><span class="sprite sprite-speaker openwebrx-sliderbtn-img"></span></div>
<input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()">
<div title="Auto-adjust waterfall colors (right-click for continuous)" id="openwebrx-waterfall-colors-auto" class="openwebrx-button"><span class="sprite sprite-waterfall-auto openwebrx-sliderbtn-img"></span></div>
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div title="Auto-set squelch level" class="openwebrx-squelch-auto openwebrx-button"><span class="sprite sprite-squelch openwebrx-sliderbtn-img"></span></div>
<input title="Squelch" class="openwebrx-squelch-slider openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1">
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><span class="sprite sprite-waterfall-default openwebrx-sliderbtn-img"></span></div>
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();" title="Zoom in one step"><span class="sprite sprite-zoom-in"></span></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"><span class="sprite sprite-zoom-out"></span></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><span class="sprite sprite-zoom-in-total"></span></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><span class="sprite sprite-zoom-out-total"></span></div>
<div id="openwebrx-smeter-db">0 dB</div>
</div>
<div class="openwebrx-panel-line">
<div id="openwebrx-smeter">
<div class="openwebrx-smeter-bar"></div>
</div>
</div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-ysf" style="display: none;" data-panel-name="metadata-ysf">
<div class="openwebrx-meta-slot">
<div class="openwebrx-ysf-mode"></div>
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
</div>
<div class="openwebrx-ysf-source"><span class="location"></span><span class="callsign"></span></div>
<div class="openwebrx-ysf-up"></div>
<div class="openwebrx-ysf-down"></div>
</div>
</div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-dstar" style="display: none;" data-panel-name="metadata-dstar">
<div class="openwebrx-meta-slot">
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
</div>
<div class="openwebrx-dstar-ourcall"><span class="location"></span><span class="callsign"></span></div>
<div class="openwebrx-dstar-message"></div>
<div class="openwebrx-dstar-yourcall"></div>
<div class="openwebrx-dstar-departure"></div>
<div class="openwebrx-dstar-destination"></div>
</div>
</div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-nxdn" style="display: none;" data-panel-name="metadata-nxdn">
<div class="openwebrx-meta-slot">
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
<img class="groupcall" src="static/gfx/openwebrx-groupcall.svg">
</div>
<div class="openwebrx-nxdn-source"></div>
<div class="openwebrx-nxdn-name"></div>
<div class="openwebrx-nxdn-destination"></div>
</div>
</div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-dmr" style="display: none;" data-panel-name="metadata-dmr">
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
<div class="openwebrx-dmr-slot">Timeslot 1</div>
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
<img class="groupcall" src="static/gfx/openwebrx-groupcall.svg">
</div>
<div class="openwebrx-dmr-id"><span class="location"></span><span class="dmr-id"></span></div>
<div class="openwebrx-dmr-name"></div>
<div class="openwebrx-dmr-target"></div>
<div class="mute">
<svg viewBox="0 0 400 400"><use xlink:href="static/gfx/svg-defs.svg#meta-mute"></use></svg>
</div>
</div>
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
<div class="openwebrx-dmr-slot">Timeslot 2</div>
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
<img class="groupcall" src="static/gfx/openwebrx-groupcall.svg">
</div>
<div class="openwebrx-dmr-id"><span class="location"></span><span class="dmr-id"></span></div>
<div class="openwebrx-dmr-name"></div>
<div class="openwebrx-dmr-target"></div>
<div class="mute">
<svg viewBox="0 0 400 400"><use xlink:href="static/gfx/svg-defs.svg#meta-mute"></use></svg>
</div>
</div>
</div>
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" style="width: 619px;">
<div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
<div class="nano-content">
<div id="openwebrx-client-log-title">OpenWebRX client log</div>
<div>
Author contact: <a href="http://www.justjakob.de/" target="_blank">Jakob Ketterl, DD5JFK</a> |
<a href="https://www.openwebrx.de" target="_blank">OpenWebRX homepage</a>
</div>
<div>Support and information: <a href="https://groups.io/g/openwebrx" target="_blank">Groups.io Mailinglist</a></div>
<div id="openwebrx-debugdiv"></div>
</div>
</div>
</div>
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" style="width: 615px;" data-panel-transparent="true">
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer" data-type="audiobuffer"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output" data-type="audiooutput"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed" data-type="audiospeed"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed" data-type="networkspeed"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu" data-type="cpu"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-clients" data-type="clients"></div>
</div>
</div>
<div id="openwebrx-panels-container-right">
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" style="width: 259px;">
<div class="openwebrx-panel-line frequencies-container">
<div class="frequencies">
<div class="webrx-actual-freq"></div>
<div class="webrx-mouse-freq"></div>
</div>
<div class="openwebrx-button openwebrx-square-button openwebrx-bookmark-button" style="display:none;" title="Add bookmark...">
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#bookmark"></use></svg>
</div>
</div>
<div class="openwebrx-panel-line">
<select id="openwebrx-sdr-profiles-listbox" onchange="sdr_profile_changed();">
</select>
</div>
<div class="openwebrx-modes openwebrx-panel-line"></div>
<div class="openwebrx-panel-line">
<div title="Mute on/off" class="openwebrx-button openwebrx-slider-button openwebrx-mute-button" onclick="toggleMute();">
<svg class="unmuted" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#speaker"></use></svg>
<svg class="muted" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#speaker-muted"></use></svg>
</div>
<input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()">
<div title="Auto-adjust waterfall colors (right-click for continuous)" id="openwebrx-waterfall-colors-auto" class="openwebrx-button openwebrx-slider-button">
<svg class="auto" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-auto"></use></svg>
<svg class="continuous" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-continuous"></use></svg>
</div>
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div title="Auto-set squelch level" class="openwebrx-squelch-auto openwebrx-button openwebrx-slider-button">
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#squelch"></use></svg>
</div>
<input title="Squelch" class="openwebrx-squelch-slider openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1">
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button openwebrx-slider-button" onclick="waterfallColorsDefault()">
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-default"></use></svg>
</div>
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomInOneStep();" title="Zoom in one step"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-in"></use></svg></div>
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomOutOneStep();" title="Zoom out one step"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-out"></use></svg></div>
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomInTotal();" title="Zoom in totally"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-in-total"></use></svg></div>
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomOutTotal();" title="Zoom out totally"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-out-total"></use></svg></div>
<div id="openwebrx-smeter-db">0 dB</div>
</div>
<div class="openwebrx-panel-line">
<div id="openwebrx-smeter">
<div class="openwebrx-smeter-bar"></div>
</div>
</div>
</div>
</div>
</div>
<div id="openwebrx-error-overlay" class="openwebrx-overlay" style="display:none;">
<div class="overlay-content">
<div>This receiver is currently unavailable due to technical issues.</div>
<div>Error Message:</div>
<div class="errormessage"></div>
</div>
</div>
</div>
</div>
<div id="openwebrx-autoplay-overlay" class="openwebrx-overlay" style="display:none;">
<div id="openwebrx-error-overlay" class="openwebrx-overlay" style="display:none;">
<div class="overlay-content">
<svg viewBox="0 0 700 700"><use xlink:href="static/gfx/svg-defs.svg#play-button"></use></svg>
<div>Start OpenWebRX</div>
<div>This receiver is currently unavailable due to technical issues.</div>
<div>Error Message:</div>
<div class="errormessage"></div>
</div>
</div>
<div id="openwebrx-dialog-bookmark" class="openwebrx-dialog" style="display:none;">

View File

@ -70,7 +70,7 @@ AprsMarker.prototype.onAdd = function() {
div.appendChild(overlay);
var self = this;
div.addEventListener("click", function(event) {
google.maps.event.addDomListener(div, "click", function(event) {
event.stopPropagation();
google.maps.event.trigger(self, "click", event);
});

View File

@ -282,7 +282,7 @@ AudioEngine.prototype.processAudio = function(data, resampler) {
var buffer;
if (this.compression === "adpcm") {
//resampling & ADPCM
buffer = this.audioCodec.decodeWithSync(new Uint8Array(data));
buffer = this.audioCodec.decode(new Uint8Array(data));
} else {
buffer = new Int16Array(data);
}
@ -328,12 +328,6 @@ ImaAdpcmCodec.prototype.reset = function() {
this.stepIndex = 0;
this.predictor = 0;
this.step = 0;
this.synchronized = 0;
this.syncWord = "SYNC";
this.syncCounter = 0;
this.phase = 0;
this.syncBuffer = new Uint8Array(4);
this.syncBufferIndex = 0;
};
ImaAdpcmCodec.imaIndexTable = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 ];
@ -359,50 +353,6 @@ ImaAdpcmCodec.prototype.decode = function(data) {
return output;
};
ImaAdpcmCodec.prototype.decodeWithSync = function(data) {
var output = new Int16Array(data.length * 2);
var oi = 0;
for (var index = 0; index < data.length; index++) {
switch (this.phase) {
case 0:
// search for sync word
if (data[index] !== this.syncWord.charCodeAt(this.synchronized++)) {
// reset if data is unexpected
this.synchronized = 0;
}
// if sync word has been found pass on to next phase
if (this.synchronized === 4) {
this.syncBufferIndex = 0;
this.phase = 1;
}
break;
case 1:
// read codec runtime data from stream
this.syncBuffer[this.syncBufferIndex++] = data[index];
// if data is complete, apply and pass on to next phase
if (this.syncBufferIndex === 4) {
var syncData = new Int16Array(this.syncBuffer.buffer);
this.stepIndex = syncData[0];
this.predictor = syncData[1];
this.syncCounter = 1000;
this.phase = 2;
}
break;
case 2:
// decode actual audio data
output[oi++] = this.decodeNibble(data[index] & 0x0F);
output[oi++] = this.decodeNibble(data[index] >> 4);
// if the next sync keyword is due, reset and return to phase 0
if (this.syncCounter-- === 0) {
this.synchronized = 0;
this.phase = 0;
}
break;
}
}
return output.slice(0, oi);
};
ImaAdpcmCodec.prototype.decodeNibble = function(nibble) {
this.stepIndex += ImaAdpcmCodec.imaIndexTable[nibble];
this.stepIndex = Math.min(Math.max(this.stepIndex, 0), 88);

View File

@ -87,8 +87,8 @@ BookmarkBar.prototype.render = function(){
var $bookmark = $(
'<div class="bookmark" data-source="' + b.source + '"' + (b.editable?' editable="editable"':'') + '>' +
'<div class="bookmark-actions">' +
'<div class="openwebrx-button action" data-action="edit"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#edit"></use></svg></div>' +
'<div class="openwebrx-button action" data-action="delete"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#trashcan"></use></svg></div>' +
'<div class="openwebrx-button action" data-action="edit"><span class="sprite sprite-edit"></span></div>' +
'<div class="openwebrx-button action" data-action="delete"><span class="sprite sprite-trashcan"><span></div>' +
'</div>' +
'<div class="bookmark-content">' + b.name + '</div>' +
'</div>'

View File

@ -13,8 +13,6 @@ Filter.prototype.getLimits = function() {
max_bw = 100000;
} else if (this.demodulator.get_modulation() === 'drm') {
max_bw = 50000;
} else if (this.demodulator.get_modulation() === "freedv") {
max_bw = 4000;
} else {
max_bw = (audioEngine.getOutputRate() / 2) - 1;
}
@ -81,12 +79,6 @@ Envelope.prototype.draw = function(visible_range){
scale_ctx.fill();
scale_ctx.globalAlpha = 1;
scale_ctx.stroke();
scale_ctx.lineWidth = 1;
scale_ctx.textAlign = "left";
scale_ctx.fillText(this.demodulator.high_cut.toString(), to_px + env_att_w, env_h2);
scale_ctx.textAlign = "right";
scale_ctx.fillText(this.demodulator.low_cut.toString(), from_px - env_att_w, env_h2);
scale_ctx.lineWidth = 3;
}
if (typeof line !== "undefined") // out of screen?
{

View File

@ -89,7 +89,7 @@ DemodulatorPanel.prototype.setMode = function(requestedModulation) {
return;
}
if (!mode.isAvailable()) {
divlog('Modulation "' + mode.name + '" not supported. Please check the feature report', true);
divlog('Modulation "' + mode.name + '" not supported. Please check requirements', true);
return;
}
@ -158,8 +158,8 @@ DemodulatorPanel.prototype.updatePanels = function() {
var modulation = this.getDemodulator().get_secondary_demod();
$('#openwebrx-panel-digimodes').attr('data-mode', modulation);
toggle_panel("openwebrx-panel-digimodes", !!modulation);
toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'fst4', 'fst4w', "q65", "msk144"].indexOf(modulation) >= 0);
toggle_panel("openwebrx-panel-js8-message", modulation === "js8");
toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'fst4', 'fst4w', "q65"].indexOf(modulation) >= 0);
toggle_panel("openwebrx-panel-js8-message", modulation == "js8");
toggle_panel("openwebrx-panel-packet-message", modulation === "packet");
toggle_panel("openwebrx-panel-pocsag-message", modulation === "pocsag");

View File

@ -98,13 +98,8 @@ TuneableFrequencyDisplay.prototype.setupEvents = function() {
if (index < 0) return;
var delta = 10 ** (Math.floor(Math.max(me.exponent, Math.log10(me.frequency))) - index);
var newFrequency;
if ('deltaMode' in e.originalEvent && e.originalEvent.deltaMode === 0) {
newFrequency = me.frequency - delta * (e.originalEvent.deltaY / 50);
} else {
if (e.originalEvent.deltaY > 0) delta *= -1;
newFrequency = me.frequency + delta;
}
if (e.originalEvent.deltaY > 0) delta *= -1;
var newFrequency = me.frequency + delta;
me.element.trigger('frequencychange', newFrequency);
});

View File

@ -70,7 +70,7 @@ Js8Thread.prototype.getMessageDuration = function() {
Js8Thread.prototype.getMode = function() {
// we filter messages by mode, so the first one is as good as any
if (!this.messages.length) return;
return this.messages[0].js8mode;
return this.messages[0].mode;
};
Js8Thread.prototype.acceptsMode = function(mode) {
@ -117,10 +117,6 @@ Js8Threader = function(el){
Js8Threader.prototype = new MessagePanel();
Js8Threader.prototype.supportsMessage = function(message) {
return message['mode'] === 'JS8';
};
Js8Threader.prototype.render = function() {
$(this.el).append($(
'<table>' +
@ -162,7 +158,7 @@ Js8Threader.prototype.pushMessage = function(message) {
var thread;
// only look for exising threads if the message is not a starting message
if ((message.thread_type & 1) === 0) {
thread = this.findThread(message.freq, message.js8mode);
thread = this.findThread(message.freq, message.mode);
}
if (!thread) {
var line = $("<tr></tr>");

View File

@ -4,10 +4,6 @@ function MessagePanel(el) {
this.initClearButton();
}
MessagePanel.prototype.supportsMessage = function(message) {
return false;
};
MessagePanel.prototype.render = function() {
};
@ -50,17 +46,10 @@ MessagePanel.prototype.initClearButton = function() {
function WsjtMessagePanel(el) {
MessagePanel.call(this, el);
this.initClearTimer();
this.qsoModes = ['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'Q65', 'MSK144'];
this.beaconModes = ['WSPR', 'FST4W'];
this.modes = [].concat(this.qsoModes, this.beaconModes);
}
WsjtMessagePanel.prototype = new MessagePanel();
WsjtMessagePanel.prototype.supportsMessage = function(message) {
return this.modes.indexOf(message['mode']) >= 0;
};
WsjtMessagePanel.prototype.render = function() {
$(this.el).append($(
'<table>' +
@ -89,14 +78,14 @@ WsjtMessagePanel.prototype.pushMessage = function(msg) {
return $('<div/>').text(input).html()
};
if (this.qsoModes.indexOf(msg['mode']) >= 0) {
if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'Q65'].indexOf(msg['mode']) >= 0) {
matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
if (matches && matches[2] !== 'RR73') {
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>';
} else {
linkedmsg = html_escape(linkedmsg);
}
} else if (this.beaconModes.indexOf(msg['mode']) >= 0) {
} else if (['WSPR', 'FST4W'].indexOf(msg['mode']) >= 0) {
matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/);
if (matches) {
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>' + html_escape(matches[3]);
@ -119,7 +108,7 @@ WsjtMessagePanel.prototype.pushMessage = function(msg) {
$.fn.wsjtMessagePanel = function(){
if (!this.data('panel')) {
this.data('panel', new WsjtMessagePanel(this));
}
};
return this.data('panel');
};
@ -130,10 +119,6 @@ function PacketMessagePanel(el) {
PacketMessagePanel.prototype = new MessagePanel();
PacketMessagePanel.prototype.supportsMessage = function(message) {
return message['mode'] === 'APRS';
};
PacketMessagePanel.prototype.render = function() {
$(this.el).append($(
'<table>' +
@ -157,17 +142,13 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
if (msg.type && msg.type === 'thirdparty' && msg.data) {
msg = msg.data;
}
var source = msg.source;
var callsign;
if ('object' in source) {
callsign = source.object;
} else if ('item' in source) {
callsign = source.item;
} else {
callsign = source.callsign;
if ('ssid' in source) {
callsign += '-' + source.ssid;
if (msg.type) {
if (msg.type === 'item') {
source = msg.item;
}
if (msg.type === 'object') {
source = msg.object;
}
}
@ -199,14 +180,13 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
}
} else if (msg.lat && msg.lon) {
classes.push('openwebrx-maps-pin');
overlay = '<svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg>';
}
var attrs = [
'class="' + classes.join(' ') + '"',
'style="' + stylesToString(styles) + '"'
].join(' ');
if (msg.lat && msg.lon) {
link = '<a ' + attrs + ' href="map?' + new URLSearchParams(source).toString() + '" target="openwebrx-map">' + overlay + '</a>';
link = '<a ' + attrs + ' href="map?callsign=' + encodeURIComponent(source) + '" target="openwebrx-map">' + overlay + '</a>';
} else {
link = '<div ' + attrs + '>' + overlay + '</div>'
}
@ -214,7 +194,7 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
$b.append($(
'<tr>' +
'<td>' + timestamp + '</td>' +
'<td class="callsign">' + callsign + '</td>' +
'<td class="callsign">' + source + '</td>' +
'<td class="coord">' + link + '</td>' +
'<td class="message">' + (msg.comment || msg.message || '') + '</td>' +
'</tr>'
@ -225,7 +205,7 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
$.fn.packetMessagePanel = function() {
if (!this.data('panel')) {
this.data('panel', new PacketMessagePanel(this));
}
};
return this.data('panel');
};
@ -236,10 +216,6 @@ PocsagMessagePanel = function(el) {
PocsagMessagePanel.prototype = new MessagePanel();
PocsagMessagePanel.prototype.supportsMessage = function(message) {
return message['mode'] === 'Pocsag';
};
PocsagMessagePanel.prototype.render = function() {
$(this.el).append($(
'<table>' +
@ -266,6 +242,6 @@ PocsagMessagePanel.prototype.pushMessage = function(msg) {
$.fn.pocsagMessagePanel = function() {
if (!this.data('panel')) {
this.data('panel', new PocsagMessagePanel(this));
}
};
return this.data('panel');
};

View File

@ -22,31 +22,20 @@ function DmrMetaSlot(el) {
DmrMetaSlot.prototype.update = function(data) {
this.el[data['sync'] ? "addClass" : "removeClass"]("sync");
if (data['sync'] && data['sync'] === "voice") {
this.setId(data['additional'] && data['additional']['callsign'] || data['talkeralias'] || data['source']);
this.setId(data['additional'] && data['additional']['callsign'] || data['source']);
this.setName(data['additional'] && data['additional']['fname']);
this.setMode(['group', 'direct'].includes(data['type']) ? data['type'] : undefined);
this.setTarget(data['target']);
this.setLocation(data['lat'], data['lon'], this.getCallsign(data));
this.el.addClass("active");
} else {
this.clear();
}
};
DmrMetaSlot.prototype.getCallsign = function(data) {
if ('additional' in data) {
return data['additional']['callsign'];
}
if ('talkeralias' in data) {
var matches = /^([A-Z0-9]+)(\s.*)?$/.exec(data['talkeralias']);
if (matches) return matches[1];
}
};
DmrMetaSlot.prototype.setId = function(id) {
if (this.id === id) return;
this.id = id;
this.el.find('.openwebrx-dmr-id .dmr-id').text(id || '');
this.el.find('.openwebrx-dmr-id').text(id || '');
}
DmrMetaSlot.prototype.setName = function(name) {
@ -70,23 +59,11 @@ DmrMetaSlot.prototype.setTarget = function(target) {
this.el.find('.openwebrx-dmr-target').text(target || '');
}
DmrMetaSlot.prototype.setLocation = function(lat, lon, callsign) {
var hasLocation = lat && lon && callsign && callsign != '';
if (hasLocation === this.hasLocation && this.callsign === callsign) return;
this.hasLocation = hasLocation; this.callsign = callsign;
var html = '';
if (hasLocation) {
html = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"><svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg></a>';
}
this.el.find('.openwebrx-dmr-id .location').html(html);
}
DmrMetaSlot.prototype.clear = function() {
this.setId();
this.setName();
this.setMode();
this.setTarget();
this.setLocation();
this.el.removeClass("active");
};
@ -135,9 +112,7 @@ YsfMetaPanel.prototype.update = function(data) {
this.setLocation(data['lat'], data['lon'], data['source']);
this.setUp(data['up']);
this.setDown(data['down']);
if (data['mode'].indexOf('data') < 0) {
this.el.find(".openwebrx-meta-slot").addClass("active");
}
this.el.find(".openwebrx-meta-slot").addClass("active");
} else {
this.clear();
}
@ -170,7 +145,7 @@ YsfMetaPanel.prototype.setLocation = function(lat, lon, callsign) {
this.hasLocation = hasLocation; this.callsign = callsign;
var html = '';
if (hasLocation) {
html = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"><svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg></a>';
html = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"></a>';
}
this.el.find('.openwebrx-ysf-source .location').html(html);
};
@ -187,191 +162,16 @@ YsfMetaPanel.prototype.setDown = function(down) {
this.el.find('.openwebrx-ysf-down').text(down || '');
}
function DStarMetaPanel(el) {
MetaPanel.call(this, el);
this.modes = ['DSTAR'];
this.clear();
}
DStarMetaPanel.prototype = new MetaPanel();
DStarMetaPanel.prototype.update = function(data) {
if (!this.isSupported(data)) return;
if (data['sync'] && data['sync'] == 'voice') {
this.el.find(".openwebrx-meta-slot").addClass("active");
this.setOurCall(data['ourcall']);
this.setYourCall(data['yourcall']);
this.setDeparture(data['departure']);
this.setDestination(data['destination']);
this.setMessage(data['message']);
this.setLocation(data['lat'], data['lon'], data['ourcall']);
} else {
this.clear();
}
};
DStarMetaPanel.prototype.setOurCall = function(ourcall) {
if (this.ourcall === ourcall) return;
this.ourcall = ourcall;
this.el.find('.openwebrx-dstar-ourcall .callsign').text(ourcall || '');
};
DStarMetaPanel.prototype.setYourCall = function(yourcall) {
if (this.yourcall === yourcall) return;
this.yourcall = yourcall;
this.el.find('.openwebrx-dstar-yourcall').text(yourcall || '');
};
DStarMetaPanel.prototype.setDeparture = function(departure) {
if (this.departure === departure) return;
this.departure = departure;
this.el.find('.openwebrx-dstar-departure').text(departure || '');
};
DStarMetaPanel.prototype.setDestination = function(destination) {
if (this.destination === destination) return;
this.destination = destination;
this.el.find('.openwebrx-dstar-destination').text(destination || '');
};
DStarMetaPanel.prototype.setMessage = function(message) {
if (this.message === message) return;
this.message = message;
this.el.find('.openwebrx-dstar-message').text(message || '');
}
DStarMetaPanel.prototype.clear = function() {
MetaPanel.prototype.clear.call(this);
this.setOurCall();
this.setYourCall();
this.setDeparture();
this.setDestination();
this.setMessage();
this.setLocation();
};
DStarMetaPanel.prototype.setLocation = function(lat, lon, callsign) {
var hasLocation = lat && lon && callsign && callsign != '';
if (hasLocation === this.hasLocation && this.callsign === callsign) return;
this.hasLocation = hasLocation; this.callsign = callsign;
var html = '';
if (hasLocation) {
html = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"><svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg></a>';
}
this.el.find('.openwebrx-dstar-ourcall .location').html(html);
};
function NxdnMetaPanel(el) {
MetaPanel.call(this, el);
this.modes = ['NXDN'];
this.clear();
}
NxdnMetaPanel.prototype = new MetaPanel();
NxdnMetaPanel.prototype.update = function(data) {
if (!this.isSupported(data)) return;
if (data['sync'] && data['sync'] === 'voice') {
this.el.find(".openwebrx-meta-slot").addClass("active");
this.setSource(data['additional'] && data['additional']['callsign'] || data['source']);
this.setName(data['additional'] && data['additional']['fname']);
this.setDestination(data['destination']);
this.setMode(['conference', 'individual'].includes(data['type']) ? data['type'] : undefined);
} else {
this.clear();
}
};
NxdnMetaPanel.prototype.setSource = function(source) {
if (this.source === source) return;
this.source = source;
this.el.find('.openwebrx-nxdn-source').text(source || '');
};
NxdnMetaPanel.prototype.setName = function(name) {
if (this.name === name) return;
this.name = name;
this.el.find('.openwebrx-nxdn-name').text(name || '');
};
NxdnMetaPanel.prototype.setDestination = function(destination) {
if (this.destination === destination) return;
this.destination = destination;
this.el.find('.openwebrx-nxdn-destination').text(destination || '');
};
NxdnMetaPanel.prototype.setMode = function(mode) {
if (this.mode === mode) return;
this.mode = mode;
var modes = ['individual', 'conference'];
var classes = modes.filter(function(c){
return c !== mode;
});
this.el.find('.openwebrx-meta-slot').removeClass(classes.join(' ')).addClass(mode);
};
NxdnMetaPanel.prototype.clear = function() {
MetaPanel.prototype.clear.call(this);
this.setMode();
this.setSource();
this.setName();
this.setDestination();
};
function M17MetaPanel(el) {
MetaPanel.call(this, el);
this.modes = ['M17'];
this.clear();
}
M17MetaPanel.prototype = new MetaPanel();
M17MetaPanel.prototype.update = function(data) {
if (!this.isSupported(data)) return;
if (data['sync'] && data['sync'] === 'voice') {
this.el.find(".openwebrx-meta-slot").addClass("active");
this.setSource(data['source']);
this.setDestination(data['destination']);
} else {
this.clear();
}
};
M17MetaPanel.prototype.setSource = function(source) {
if (this.source === source) return;
this.source = source;
this.el.find('.openwebrx-m17-source').text(source || '');
};
M17MetaPanel.prototype.setDestination = function(destination) {
if (this.destination === destination) return;
this.destination = destination;
this.el.find('.openwebrx-m17-destination').text(destination || '');
};
M17MetaPanel.prototype.clear = function() {
MetaPanel.prototype.clear.call(this);
this.setSource();
this.setDestination();
};
MetaPanel.types = {
dmr: DmrMetaPanel,
ysf: YsfMetaPanel,
dstar: DStarMetaPanel,
nxdn: NxdnMetaPanel,
m17: M17MetaPanel,
ysf: YsfMetaPanel
};
$.fn.metaPanel = function() {
return this.map(function() {
var $self = $(this);
if (!$self.data('metapanel')) {
var matches = /^openwebrx-panel-metadata-([a-z0-9]+)$/.exec($self.prop('id'));
var matches = /^openwebrx-panel-metadata-([a-z]+)$/.exec($self.prop('id'));
var constructor = matches && MetaPanel.types[matches[1]] || MetaPanel;
$self.data('metapanel', new constructor($self));
}

View File

@ -1,4 +1,2 @@
/* Taken from https://github.com/cyphercodes/location-picker under GPLv3 license */
/* Contains https://github.com/cyphercodes/location-picker/pull/11 to allow latitude and longitude to be 0 */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.locationPicker=t()}(this,function(){"use strict";return function(e,t){void 0===t&&(t={});var n=t.insertAt;if(e&&"undefined"!=typeof document){var o=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===n&&o.firstChild?o.insertBefore(i,o.firstChild):o.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}('.location-picker .centerMarker{position:absolute;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAABWCAYAAACEsWWHAAAGLElEQVR4AdXcA2xsXReH8f3Ztm3btm2br23btm3btm2rd257b835vVnJStL0sj3Tds6TrOTkzN5r/5925qgorQRPwQewDHbHebgXDQxlNXLfeTlmmZzzlNJOpMyXsA86TJ2O7PGl6DnbQr/CncYx1tNj4JzTde+0la5V/6/x+x+Z85Ov6/jWZ0TFduyL12JMjI05E4iev5pxQXwFV0lGOx7Xf/xRulb5n45vflrH1z4xqYo5MTd6RC9JrvGVmRB6C04dLzR/yw10fP2TGbJyRa/oOVHw1Fh7uqS+jAY0e3uaPXvupOM7n48w01LRO9aItSDX/nKrpf6FYRi85AJzfvy1WHxGKtaKNZPhyNIKoadjZ0nfIfvFYrNSsXYiMz2tilg00Bweas7fZO1YYFYrMkQWKVfl7ScadS7z12jcFhVZxsn9ayoHimHI71RbVWRKhiPrZA7pjZn+TFX4zDWW6lSAU/PoFw3auiJjcuqSryjyPJWH9LauyBhZEXxlcdd+VyFOjDGxFhVZk6vCoUwEv5KXSXlFUYuKrJE5+dXCvlt3Ia7TYkKtKjIndxr/XcOXIMzzgrZWFZkje/Kl8WL7QP/xR7Z0wWs/9X5HvutNdnjdq2z6qpdFxXbsi9daulZkT/YZ/zbsQNwTtWSRuz7/YXu9+bU2ePlLFlcxJsa2ZM3InnSEU8EH5J1v3iRWqhs+9QGbv+plS5TKirExp/K6kT0ckg8ULIO4RW/Jd2q81GTkWvGdC4dkmYLdoXvHLSs3zrffVCrmVl4/HJLdC86r/vmKA8X7ImCVqnxACYfkvIL7EE+OKjWNI11FsehRKUM4JPcVNBCPxSo1jcN4RbHoUSlDOCSNgiHEM79KTeMcVVEselTKEA7JUMEA4pqr9mLhkAwUPAqNX36n9m/Fxi+/K3mk4GaY+/df1/7gEQ7JzQUnwPwN16h8XTjbh/twSE4oWBf6jjio9ifocEjWLfgeDN14be0vqcIh+V7BCzFobKw556ffqO1FcGQPh3AJpxLgROjebrPa3rZE9uTEEgT4IwzffENtbzQje/LH8WLPRQPycXatKjInjXAp48GGMHjx+bUTi8zJhmUieAX60ez8359rIxVZ0czsrygLA9uM+6zVoiJrsnVZFHg55sP8jddqe6nImMyP7GVxYA0Yffyxtn4iHNkiY7J6WRJ4Fu6G3n13a1uxyJbcHZnL0oDvQHNwsJmPDNqqIlNkg8haJgOOgKGrL287sciUHFEmC16FTpi/2bptIxVZks7IWKYC/grN+fOa+bBnVisyRBaIbKUKOBsGzjp11sUiQ3J2qQreij7oWn3ZWZOKtZO+yFRaAVaAsSceb3Z874szLhVrxtoQWUqrwFNxeeWfoVX/2dflkaW0ErwPQ2h2Lvf3GZOKtdDMtd9XpgOsByMP3q/j25+bdqlYI9ZK1ivTBZ6JW6D3kH2nXSzWSG6Jtct0gs+iaWSkOfcvv5g2qehtdLSJqM+WmQC7wND110ybWPROdikzBV6Ih2H+5uu1XCp6Jg/HWmUmwa+gOa+rOedHX22ZVPSKngh+VWYDnAP9Jx3TMrHolZxTZgu8B8PxBHbun39eWSp6RC8MR+8ym2AbGLzo3Mpi0SPZpsw2eCm6oPP/f5myVMxNuqJnaQewKgzdMPXDf8xNVi3tAp6Nh2DemstPWirmJNHj2aWdwDIwfNstkxaLOckypd3Ac/AEdP73T0stFWOTJ6JHaUewBgycecpSi8XYZI3SruBlGGwODy/VT0djTIzFYMwt7QwOhZ69d16iWIxJDi3tDr5CPh/5xqcWKRWvxRgEX6mDWP5GOF0r/muRYl0r/ltyV8wpdQCbQ98xhy1SLF5LNi91AV+A0ccfXaRYvJZ8oU5iT0cXNP7wExOlYl/SFWNLncARMH+L9U0Ui33JEaVuYGXoP/lYE8ViX7JyHcW+uahrx9iXfLOOYq+Bse75JorFvuQ1pY6gE8b/kV1sJ52lruCuib+9GtvJXXUWu2ri72fFdnJVncXOhvgXFykmtpOz6yx2MMzfdB0pJraTg+sstiwM33mbxm9/qPG7H4ntZNk6iz0Hd5hA7ntOqTN4OfbDo1mx/fIyzTwJJedUPgRWtocAAAAASUVORK5CYII=") no-repeat;background-size:100%;top:50%;left:50%;z-index:1;margin-left:-14px;margin-top:-43px;height:44px;width:28px;cursor:pointer}'),function(){function e(e,t,n){void 0===t&&(t={}),void 0===n&&(n={});var o={setCurrentPosition:!0};Object.assign(o,t);var i={center:new google.maps.LatLng("number"==typeof o.lat?o.lat:34.4346,"number"==typeof o.lng?o.lng:35.8362),zoom:15};Object.assign(i,n),e instanceof HTMLElement?this.element=e:this.element=document.getElementById(e),this.map=new google.maps.Map(this.element,i);var r=document.createElement("div");r.classList.add("centerMarker"),this.element&&(this.element.classList.add("location-picker"),this.element.children[0].appendChild(r)),!o.setCurrentPosition||o.lat||o.lng||this.setCurrentPosition()}return e.prototype.getMarkerPosition=function(){var e=this.map.getCenter();return{lat:e.lat(),lng:e.lng()}},e.prototype.setLocation=function(e,t){this.map.setCenter(new google.maps.LatLng(e,t))},e.prototype.setCurrentPosition=function(){var e=this;navigator.geolocation?navigator.geolocation.getCurrentPosition(function(t){var n={lat:t.coords.latitude,lng:t.coords.longitude};e.map.setCenter(n)},function(){console.log("Could not determine your location...")}):console.log("Your browser does not support Geolocation.")},e}()});
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.locationPicker=t()}(this,function(){"use strict";return function(e,t){void 0===t&&(t={});var n=t.insertAt;if(e&&"undefined"!=typeof document){var o=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===n&&o.firstChild?o.insertBefore(i,o.firstChild):o.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}('.location-picker .centerMarker{position:absolute;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAABWCAYAAACEsWWHAAAGLElEQVR4AdXcA2xsXReH8f3Ztm3btm2br23btm3btm2rd257b835vVnJStL0sj3Tds6TrOTkzN5r/5925qgorQRPwQewDHbHebgXDQxlNXLfeTlmmZzzlNJOpMyXsA86TJ2O7PGl6DnbQr/CncYx1tNj4JzTde+0la5V/6/x+x+Z85Ov6/jWZ0TFduyL12JMjI05E4iev5pxQXwFV0lGOx7Xf/xRulb5n45vflrH1z4xqYo5MTd6RC9JrvGVmRB6C04dLzR/yw10fP2TGbJyRa/oOVHw1Fh7uqS+jAY0e3uaPXvupOM7n48w01LRO9aItSDX/nKrpf6FYRi85AJzfvy1WHxGKtaKNZPhyNIKoadjZ0nfIfvFYrNSsXYiMz2tilg00Bweas7fZO1YYFYrMkQWKVfl7ScadS7z12jcFhVZxsn9ayoHimHI71RbVWRKhiPrZA7pjZn+TFX4zDWW6lSAU/PoFw3auiJjcuqSryjyPJWH9LauyBhZEXxlcdd+VyFOjDGxFhVZk6vCoUwEv5KXSXlFUYuKrJE5+dXCvlt3Ia7TYkKtKjIndxr/XcOXIMzzgrZWFZkje/Kl8WL7QP/xR7Z0wWs/9X5HvutNdnjdq2z6qpdFxXbsi9daulZkT/YZ/zbsQNwTtWSRuz7/YXu9+bU2ePlLFlcxJsa2ZM3InnSEU8EH5J1v3iRWqhs+9QGbv+plS5TKirExp/K6kT0ckg8ULIO4RW/Jd2q81GTkWvGdC4dkmYLdoXvHLSs3zrffVCrmVl4/HJLdC86r/vmKA8X7ImCVqnxACYfkvIL7EE+OKjWNI11FsehRKUM4JPcVNBCPxSo1jcN4RbHoUSlDOCSNgiHEM79KTeMcVVEselTKEA7JUMEA4pqr9mLhkAwUPAqNX36n9m/Fxi+/K3mk4GaY+/df1/7gEQ7JzQUnwPwN16h8XTjbh/twSE4oWBf6jjio9ifocEjWLfgeDN14be0vqcIh+V7BCzFobKw556ffqO1FcGQPh3AJpxLgROjebrPa3rZE9uTEEgT4IwzffENtbzQje/LH8WLPRQPycXatKjInjXAp48GGMHjx+bUTi8zJhmUieAX60ez8359rIxVZ0czsrygLA9uM+6zVoiJrsnVZFHg55sP8jddqe6nImMyP7GVxYA0Yffyxtn4iHNkiY7J6WRJ4Fu6G3n13a1uxyJbcHZnL0oDvQHNwsJmPDNqqIlNkg8haJgOOgKGrL287sciUHFEmC16FTpi/2bptIxVZks7IWKYC/grN+fOa+bBnVisyRBaIbKUKOBsGzjp11sUiQ3J2qQreij7oWn3ZWZOKtZO+yFRaAVaAsSceb3Z874szLhVrxtoQWUqrwFNxeeWfoVX/2dflkaW0ErwPQ2h2Lvf3GZOKtdDMtd9XpgOsByMP3q/j25+bdqlYI9ZK1ivTBZ6JW6D3kH2nXSzWSG6Jtct0gs+iaWSkOfcvv5g2qehtdLSJqM+WmQC7wND110ybWPROdikzBV6Ih2H+5uu1XCp6Jg/HWmUmwa+gOa+rOedHX22ZVPSKngh+VWYDnAP9Jx3TMrHolZxTZgu8B8PxBHbun39eWSp6RC8MR+8ym2AbGLzo3Mpi0SPZpsw2eCm6oPP/f5myVMxNuqJnaQewKgzdMPXDf8xNVi3tAp6Nh2DemstPWirmJNHj2aWdwDIwfNstkxaLOckypd3Ac/AEdP73T0stFWOTJ6JHaUewBgycecpSi8XYZI3SruBlGGwODy/VT0djTIzFYMwt7QwOhZ69d16iWIxJDi3tDr5CPh/5xqcWKRWvxRgEX6mDWP5GOF0r/muRYl0r/ltyV8wpdQCbQ98xhy1SLF5LNi91AV+A0ccfXaRYvJZ8oU5iT0cXNP7wExOlYl/SFWNLncARMH+L9U0Ui33JEaVuYGXoP/lYE8ViX7JyHcW+uahrx9iXfLOOYq+Bse75JorFvuQ1pY6gE8b/kV1sJ52lruCuib+9GtvJXXUWu2ri72fFdnJVncXOhvgXFykmtpOz6yx2MMzfdB0pJraTg+sstiwM33mbxm9/qPG7H4ntZNk6iz0Hd5hA7ntOqTN4OfbDo1mx/fIyzTwJJedUPgRWtocAAAAASUVORK5CYII=") no-repeat;background-size:100%;top:50%;left:50%;z-index:1;margin-left:-14px;margin-top:-43px;height:44px;width:28px;cursor:pointer}'),function(){function e(e,t,n){void 0===t&&(t={}),void 0===n&&(n={});var o={setCurrentPosition:!0};Object.assign(o,t);var i={center:new google.maps.LatLng(o.lat?o.lat:34.4346,o.lng?o.lng:35.8362),zoom:15};Object.assign(i,n),e instanceof HTMLElement?this.element=e:this.element=document.getElementById(e),this.map=new google.maps.Map(this.element,i);var r=document.createElement("div");r.classList.add("centerMarker"),this.element&&(this.element.classList.add("location-picker"),this.element.children[0].appendChild(r)),!o.setCurrentPosition||o.lat||o.lng||this.setCurrentPosition()}return e.prototype.getMarkerPosition=function(){var e=this.map.getCenter();return{lat:e.lat(),lng:e.lng()}},e.prototype.setLocation=function(e,t){this.map.setCenter(new google.maps.LatLng(e,t))},e.prototype.setCurrentPosition=function(){var e=this;navigator.geolocation?navigator.geolocation.getCurrentPosition(function(t){var n={lat:t.coords.latitude,lng:t.coords.longitude};e.map.setCenter(n)},function(){console.log("Could not determine your location...")}):console.log("Your browser does not support Geolocation.")},e}()});

View File

@ -38,7 +38,7 @@ $.fn.exponentialInput = function() {
// calculate initial exponent
var value = parseFloat($input.val());
if (!Number.isNaN(value)) {
$exponent.val(Math.floor(Math.log10(Math.abs(value)) / 3) * 3);
$exponent.val(Math.floor(Math.log10(value) / 3) * 3);
setExponent();
}
})

View File

@ -1,5 +0,0 @@
$.fn.logMessages = function() {
$.each(this, function(){
$(this).scrollTop(this.scrollHeight);
});
};

View File

@ -1,12 +1,17 @@
$(function(){
var query = new URLSearchParams(window.location.search);
var query = window.location.search.replace(/^\?/, '').split('&').map(function(v){
var s = v.split('=');
var r = {};
r[s[0]] = s.slice(1).join('=');
return r;
}).reduce(function(a, b){
return a.assign(b);
});
var expectedCallsign;
if (query.has('callsign')) {
expectedCallsign = Object.fromEntries(query.entries());
}
if (query.callsign) expectedCallsign = decodeURIComponent(query.callsign);
var expectedLocator;
if (query.has('locator')) expectedLocator = query.get('locator');
if (query.locator) expectedLocator = query.locator;
var protocol = window.location.protocol.match(/https/) ? 'wss' : 'ws';
@ -32,7 +37,6 @@ $(function(){
var retention_time = 2 * 60 * 60 * 1000;
var strokeOpacity = 0.8;
var fillOpacity = 0.35;
var callsign_service;
var colorKeys = {};
var colorScale = chroma.scale(['red', 'blue', 'green']).mode('hsl');
@ -97,11 +101,6 @@ $(function(){
return '<li class="square' + disabled + '" data-selector="' + key + '"><span class="illustration" style="background-color:' + chroma(value).alpha(fillOpacity) + ';border-color:' + chroma(value).alpha(strokeOpacity) + ';"></span>' + key + '</li>';
});
$(".openwebrx-map-legend .content").html('<ul>' + lis.join('') + '</ul>');
};
var shallowEquals = function(obj1, obj2) {
// basic shallow object comparison
return Object.entries(obj1).sort().toString() === Object.entries(obj2).sort().toString();
}
var processUpdates = function(updates) {
@ -110,7 +109,6 @@ $(function(){
return;
}
updates.forEach(function(update){
var key = sourceToKey(update.source);
switch (update.location.type) {
case 'latlon':
@ -124,33 +122,33 @@ $(function(){
aprsOptions.course = update.location.course;
aprsOptions.speed = update.location.speed;
}
if (markers[key]) {
marker = markers[key];
if (markers[update.callsign]) {
marker = markers[update.callsign];
} else {
marker = new markerClass();
marker.addListener('click', function(){
showMarkerInfoWindow(update.source, pos);
showMarkerInfoWindow(update.callsign, pos);
});
markers[key] = marker;
markers[update.callsign] = marker;
}
marker.setOptions($.extend({
position: pos,
map: map,
title: sourceToString(update.source)
title: update.callsign
}, aprsOptions, getMarkerOpacityOptions(update.lastseen) ));
marker.lastseen = update.lastseen;
marker.mode = update.mode;
marker.band = update.band;
marker.comment = update.location.comment;
if (expectedCallsign && shallowEquals(expectedCallsign, update.source)) {
if (expectedCallsign && expectedCallsign == update.callsign) {
map.panTo(pos);
showMarkerInfoWindow(update.source, pos);
showMarkerInfoWindow(update.callsign, pos);
expectedCallsign = false;
}
if (infowindow && infowindow.source && shallowEquals(infowindow.source, update.source)) {
showMarkerInfoWindow(infowindow.source, pos);
if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) {
showMarkerInfoWindow(infowindow.callsign, pos);
}
break;
case 'locator':
@ -161,16 +159,15 @@ $(function(){
var rectangle;
// the accessor is designed to work on the rectangle... but it should work on the update object, too
var color = getColor(colorAccessor(update));
if (rectangles[key]) {
rectangle = rectangles[key];
if (rectangles[update.callsign]) {
rectangle = rectangles[update.callsign];
} else {
rectangle = new google.maps.Rectangle();
rectangle.addListener('click', function(){
showLocatorInfoWindow(this.locator, this.center);
});
rectangles[key] = rectangle;
rectangles[update.callsign] = rectangle;
}
rectangle.source = update.source;
rectangle.lastseen = update.lastseen;
rectangle.locator = update.location.locator;
rectangle.mode = update.mode;
@ -190,13 +187,13 @@ $(function(){
}
}, getRectangleOpacityOptions(update.lastseen) ));
if (expectedLocator && expectedLocator === update.location.locator) {
if (expectedLocator && expectedLocator == update.location.locator) {
map.panTo(center);
showLocatorInfoWindow(expectedLocator, center);
expectedLocator = false;
}
if (infowindow && infowindow.locator && infowindow.locator === update.location.locator) {
if (infowindow && infowindow.locator && infowindow.locator == update.location.locator) {
showLocatorInfoWindow(infowindow.locator, center);
}
break;
@ -205,7 +202,7 @@ $(function(){
};
var clearMap = function(){
var reset = function(_, item) { item.setMap(); };
var reset = function(callsign, item) { item.setMap(); };
$.each(markers, reset);
$.each(rectangles, reset);
receiverMarker.setMap();
@ -289,9 +286,6 @@ $(function(){
if ('map_position_retention_time' in config) {
retention_time = config.map_position_retention_time * 1000;
}
if ('callsign_service' in config) {
callsign_service = config['callsign_service'];
}
break;
case "update":
processUpdates(json.value);
@ -338,77 +332,32 @@ $(function(){
infowindow = new google.maps.InfoWindow();
google.maps.event.addListener(infowindow, 'closeclick', function() {
delete infowindow.locator;
delete infowindow.source;
delete infowindow.callsign;
});
}
delete infowindow.locator;
delete infowindow.source;
delete infowindow.callsign;
return infowindow;
};
var sourceToKey = function(source) {
// special treatment for special entities
// not just for display but also in key treatment in order not to overlap with other locations sent by the same callsign
if ('item' in source) return source['item'];
if ('object' in source) return source['object'];
var key = source.callsign;
if ('ssid' in source) key += '-' + source.ssid;
return key;
};
// we can reuse the same logic for displaying and indexing
var sourceToString = sourceToKey;
var linkifySource = function(source) {
var callsignString = sourceToString(source);
switch (callsign_service) {
case "qrzcq":
return '<a target="callsign_info" href="https://www.qrzcq.com/call/' + source.callsign + '">' + callsignString + '</a>';
case "qrz":
return '<a target="callsign_info" href="https://www.qrz.com/db/' + source.callsign + '">' + callsignString + '</a>';
case 'aprsfi':
var callWithSsid = sourceToKey(source);
return '<a target="callsign_info" href="https://aprs.fi/info/a/' + callWithSsid + '">' + callsignString + '</a>';
default:
return callsignString;
}
};
var distanceKm = function(p1, p2) {
// Earth radius in km
var R = 6371.0;
// Convert degrees to radians
var rlat1 = p1.lat() * (Math.PI/180);
var rlat2 = p2.lat() * (Math.PI/180);
// Compute difference in radians
var difflat = rlat2-rlat1;
var difflon = (p2.lng()-p1.lng()) * (Math.PI/180);
// Compute distance
d = 2 * R * Math.asin(Math.sqrt(
Math.sin(difflat/2) * Math.sin(difflat/2) +
Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(difflon/2) * Math.sin(difflon/2)
));
return Math.round(d);
};
}
var infowindow;
var showLocatorInfoWindow = function(locator, pos) {
var infowindow = getInfoWindow();
infowindow.locator = locator;
var inLocator = Object.values(rectangles).filter(rectangleFilter).filter(function(d) {
return d.locator === locator;
var inLocator = $.map(rectangles, function(r, callsign) {
return {callsign: callsign, locator: r.locator, lastseen: r.lastseen, mode: r.mode, band: r.band}
}).filter(rectangleFilter).filter(function(d) {
return d.locator == locator;
}).sort(function(a, b){
return b.lastseen - a.lastseen;
});
var distance = receiverMarker?
" at " + distanceKm(receiverMarker.position, pos) + " km" : "";
infowindow.setContent(
'<h3>Locator: ' + locator + distance + '</h3>' +
'<h3>Locator: ' + locator + '</h3>' +
'<div>Active Callsigns:</div>' +
'<ul>' +
inLocator.map(function(i){
var timestring = moment(i.lastseen).fromNow();
var message = linkifySource(i.source) + ' (' + timestring + ' using ' + i.mode;
var message = i.callsign + ' (' + timestring + ' using ' + i.mode;
if (i.band) message += ' on ' + i.band;
message += ')';
return '<li>' + message + '</li>'
@ -419,26 +368,22 @@ $(function(){
infowindow.open(map);
};
var showMarkerInfoWindow = function(source, pos) {
var showMarkerInfoWindow = function(callsign, pos) {
var infowindow = getInfoWindow();
infowindow.source = source;
var marker = markers[sourceToKey(source)];
infowindow.callsign = callsign;
var marker = markers[callsign];
var timestring = moment(marker.lastseen).fromNow();
var commentString = "";
var distance = "";
if (marker.comment) {
commentString = '<div>' + marker.comment + '</div>';
}
if (receiverMarker) {
distance = " at " + distanceKm(receiverMarker.position, marker.position) + " km";
}
infowindow.setContent(
'<h3>' + linkifySource(source) + distance + '</h3>' +
'<h3>' + callsign + '</h3>' +
'<div>' + timestring + ' using ' + marker.mode + ( marker.band ? ' on ' + marker.band : '' ) + '</div>' +
commentString
);
infowindow.open(map, marker);
};
}
var showReceiverInfoWindow = function(marker) {
var infowindow = getInfoWindow()
@ -447,7 +392,7 @@ $(function(){
'<div>Receiver location</div>'
);
infowindow.open(map, marker);
};
}
var getScale = function(lastseen) {
var age = new Date().getTime() - lastseen;
@ -476,19 +421,19 @@ $(function(){
// fade out / remove positions after time
setInterval(function(){
var now = new Date().getTime();
Object.values(rectangles).forEach(function(m){
$.each(rectangles, function(callsign, m) {
var age = now - m.lastseen;
if (age > retention_time) {
delete rectangles[sourceToKey(m.source)];
delete rectangles[callsign];
m.setMap();
return;
}
m.setOptions(getRectangleOpacityOptions(m.lastseen));
});
Object.values(markers).forEach(function(m) {
$.each(markers, function(callsign, m) {
var age = now - m.lastseen;
if (age > retention_time) {
delete markers[sourceToKey(m.source)];
delete markers[callsign];
m.setMap();
return;
}

Some files were not shown because too many files have changed in this diff Show More