merge recent openwebrx changes into our work
This commit is contained in:
parent
ac6e001fd6
commit
bf4c70dfef
|
@ -1 +1,3 @@
|
|||
*.pyc
|
||||
*.swp
|
||||
tags
|
||||
|
|
35
README.md
35
README.md
|
@ -1,21 +1,26 @@
|
|||
OpenWebRX
|
||||
=========
|
||||
|
||||
[:floppy_disk: Setup guide for Ubuntu](http://blog.sdr.hu/2015/06/30/quick-setup-openwebrx.html) | [:blue_book: Knowledge base on the Wiki](https://github.com/simonyiszk/openwebrx/wiki/) | [:earth_americas: Receivers on SDR.hu](http://sdr.hu/)
|
||||
|
||||
OpenWebRX is a multi-user SDR receiver software with a web interface.
|
||||
|
||||
![OpenWebRX](/screenshot.png?raw=true)
|
||||
![OpenWebRX](http://blog.sdr.hu/images/openwebrx/screenshot.png)
|
||||
|
||||
It has the following features:
|
||||
|
||||
- <a href="https://github.com/simonyiszk/csdr">libcsdr</a> based demodulators (AM/FM/SSB),
|
||||
- <a href="https://github.com/simonyiszk/csdr">csdr</a> based demodulators (AM/FM/SSB/CW/BPSK31),
|
||||
- filter passband can be set from GUI,
|
||||
- waterfall display can be shifted back in time,
|
||||
- it extensively uses HTML5 features like WebSocket, Web Audio API, and <canvas>.
|
||||
- it extensively uses HTML5 features like WebSocket, Web Audio API, and <canvas>,
|
||||
- it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28),
|
||||
- currently supports RTL-SDR and HackRF; other SDR hardware may be easily added.
|
||||
- currently supports RTL-SDR, HackRF, SDRplay, AirSpy and many other devices, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/">OpenWebRX Wiki</a>,
|
||||
- it has a 3D waterfall display:
|
||||
|
||||
![OpenWebRX 3D waterfall](http://blog.sdr.hu/images/openwebrx/screenshot-3d.gif)
|
||||
|
||||
**News (2015-08-18)**
|
||||
- My BSc. thesis written on OpenWebRX is <a href="http://openwebrx.org/bsc-thesis.pdf">available here.</a>
|
||||
- My BSc. thesis written on OpenWebRX is <a href="https://sdr.hu/static/bsc-thesis.pdf">available here.</a>
|
||||
- Several bugs were fixed to improve reliability and stability.
|
||||
- OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.)
|
||||
- OpenWebRX now uses <a href="https://github.com/simonyiszk/csdr#sdrjs">sdr.js</a> (*libcsdr* compiled to JavaScript) for some client-side DSP tasks.
|
||||
|
@ -28,13 +33,20 @@ It has the following features:
|
|||
- OpenWebRX now supports URLs like: `http://localhost:8073/#freq=145555000,mod=usb`
|
||||
- UI improvements were made, thanks to John Seamons and Gnoxter.
|
||||
|
||||
> When upgrading OpenWebRX, please make sure that you also upgrade *csdr*, and install the new dependency, *ncat*!
|
||||
**News (2017-04-04)**
|
||||
- *ncat* has been replaced with a custom implementation called *nmux* due to a bug that caused regular crashes on some machines. The *nmux* tool is part of the *csdr* package.
|
||||
- Most consumer SDR devices are supported via <a href="https://github.com/rxseger/rx_tools">rx_tools</a>, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX">OpenWebRX Wiki</a> on that.
|
||||
|
||||
**News (2017-07-12)**
|
||||
- OpenWebRX now has a BPSK31 demodulator and a 3D waterfall display.
|
||||
|
||||
> When upgrading OpenWebRX, please make sure that you also upgrade *csdr*!
|
||||
|
||||
## OpenWebRX servers on SDR.hu
|
||||
|
||||
[SDR.hu](http://sdr.hu) is a site which lists the active, public OpenWebRX servers. Your receiver [can also be part of it](http://sdr.hu/openwebrx), if you want.
|
||||
|
||||
![sdr.hu](/screenshot-sdrhu.png?raw=true)
|
||||
![sdr.hu](http://blog.sdr.hu/images/openwebrx/screenshot-sdrhu.png)
|
||||
|
||||
## Setup
|
||||
|
||||
|
@ -44,11 +56,6 @@ First you will need to install the dependencies:
|
|||
|
||||
- <a href="https://github.com/simonyiszk/csdr">libcsdr</a>
|
||||
- <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</a>
|
||||
- ncat (On Debian/Ubuntu, it is in the *nmap* package).
|
||||
|
||||
> By the way, *nmap* is a tool commonly used for auditing network security, and it is not used by OpenWebRX in any way. We need to install it, because the *ncat* command is packaged with it.
|
||||
>
|
||||
> *ncat* is a better *netcat* alternative, which is used by OpenWebRX for internally distributing the I/Q data stream. It also solves the problem of having different versions of *netcat* on different Linux distributions, which are not compatible by their command-line arguments.
|
||||
|
||||
After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server:
|
||||
|
||||
|
@ -62,7 +69,7 @@ Please note that the server is also listening on the following ports (on localho
|
|||
|
||||
Now the next step is to customize the parameters of your server in `config_webrx.py`.
|
||||
|
||||
Actually, if you do something cool with OpenWebRX (or just have a problem), please drop me a mail:
|
||||
Actually, if you do something cool with OpenWebRX, please drop me a mail:
|
||||
*Andras Retzler, HA7ILM <randras@sdr.hu>*
|
||||
|
||||
## Usage tips
|
||||
|
@ -85,4 +92,4 @@ If you want to run OpenWebRX on a remote server instead of *localhost*, do not f
|
|||
|
||||
OpenWebRX is available under Affero GPL v3 license (<a href="https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)">summary</a>).
|
||||
|
||||
OpenWebRX is also available under a commercial license on request. Please contact me at the address *<randras@sdr.hu>* for other licensing options.
|
||||
OpenWebRX is also available under a commercial license on request. Please contact me at the address *<randras@sdr.hu>* for licensing options.
|
||||
|
|
101
config_webrx.py
101
config_webrx.py
|
@ -3,9 +3,9 @@
|
|||
"""
|
||||
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>
|
||||
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>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
|
@ -20,15 +20,15 @@ config_webrx: configuration options for OpenWebRX
|
|||
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.
|
||||
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.)
|
||||
(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.)
|
||||
"""
|
||||
|
||||
# NOTE: you can find additional information about configuring OpenWebRX in the Wiki:
|
||||
|
@ -67,34 +67,49 @@ sdrhu_key = ""
|
|||
sdrhu_public_listing = False
|
||||
|
||||
# ==== DSP/RX settings ====
|
||||
dsp_plugin="csdr"
|
||||
fft_fps=9
|
||||
fft_size=4096
|
||||
samp_rate = 250000
|
||||
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.
|
||||
|
||||
center_freq = 145525000
|
||||
# samp_rate = 250000
|
||||
samp_rate = 2400000
|
||||
center_freq = 144250000
|
||||
rf_gain = 5 #in dB. For an RTL-SDR, rf_gain=0 will set the tuner to auto gain mode, else it will be in manual gain mode.
|
||||
ppm = 0
|
||||
|
||||
audio_compression="adpcm" #valid values: "adpcm", "none"
|
||||
fft_compression="adpcm" #valid values: "adpcm", "none"
|
||||
|
||||
digimodes_enable=True #Decoding digimodes come with higher CPU usage.
|
||||
digimodes_fft_size=1024
|
||||
|
||||
start_rtl_thread=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.)
|
||||
|
||||
# There are guides for setting may different SDR hardware including AirSpy, AFEDRI-SDR, RTL-SDR in direct sampling mode, etc. in the Wiki:
|
||||
# https://github.com/simonyiszk/openwebrx/wiki
|
||||
#################################################################################################
|
||||
# Is my SDR hardware supported? #
|
||||
# Check here: https://github.com/simonyiszk/openwebrx/wiki#guides-for-receiver-hardware-support #
|
||||
#################################################################################################
|
||||
|
||||
# You can use other SDR hardware as well, by giving your own command that outputs the I/Q samples... Some examples of configuration are available here (default is RTL-SDR):
|
||||
|
||||
# >> RTL-SDR via rtl_sdr
|
||||
|
||||
start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
|
||||
format_conversion="csdr convert_u8_f"
|
||||
|
||||
#start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l16 -a0 -q -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
|
||||
#lna_gain=8
|
||||
#rf_amp=1
|
||||
#start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm, rf_amp=rf_amp, lna_gain=lna_gain)
|
||||
#format_conversion="csdr convert_s8_f"
|
||||
"""
|
||||
To use a HackRF, compile the HackRF host tools from its "stdout" branch:
|
||||
|
@ -117,20 +132,29 @@ To use a HackRF, compile the HackRF host tools from its "stdout" branch:
|
|||
#format_conversion="csdr convert_s16_f | csdr gain_ff 30"
|
||||
|
||||
# >> /dev/urandom test signal source
|
||||
#samp_rate = 2400000
|
||||
#start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
|
||||
#format_conversion="csdr convert_u8_f"
|
||||
# samp_rate = 2400000
|
||||
# start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
|
||||
# format_conversion="csdr convert_u8_f"
|
||||
|
||||
# >> Pre-recorded raw I/Q file as signal source
|
||||
# You will have to correctly specify: samp_rate, center_freq, format_conversion in order to correctly play an I/Q file.
|
||||
#start_rtl_command="(while true; do cat my_iq_file.raw; done) | csdr flowcontrol {sr} 20 ".format(sr=samp_rate*2*1.05)
|
||||
#format_conversion="csdr convert_u8_f"
|
||||
|
||||
#>> The rx_sdr command works with a variety of SDR harware: RTL-SDR, HackRF, SDRplay, UHD, Airspy, Red Pitaya, audio devices, etc.
|
||||
# It will auto-detect your SDR hardware if the following tools are installed:
|
||||
# * the vendor provided driver and library,
|
||||
# * the vendor-specific SoapySDR wrapper library,
|
||||
# * and SoapySDR itself.
|
||||
# Check out this article on the OpenWebRX Wiki: https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX/
|
||||
#start_rtl_command="rx_sdr -F CF32 -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
|
||||
#format_conversion=""
|
||||
|
||||
# >> gr-osmosdr signal source using GNU Radio (follow this guide: https://github.com/simonyiszk/openwebrx/wiki/Using-GrOsmoSDR-as-signal-source)
|
||||
#start_rtl_command="cat /tmp/osmocom_fifo"
|
||||
#format_conversion=""
|
||||
|
||||
# ==== Misc options ====
|
||||
# ==== Misc settings ====
|
||||
|
||||
shown_center_freq = center_freq #you can change this if you use an upconverter
|
||||
|
||||
|
@ -146,16 +170,43 @@ iq_server_port = 4951 #TCP port for ncat to listen on. It will send I/Q data ove
|
|||
|
||||
#access_log = "~/openwebrx_access.log"
|
||||
|
||||
waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]"
|
||||
waterfall_min_level = -115 #in dB
|
||||
waterfall_max_level = 0
|
||||
# ==== Color themes ====
|
||||
|
||||
#A guide is available to help you set these values: https://github.com/simonyiszk/openwebrx/wiki/Calibrating-waterfall-display-levels
|
||||
|
||||
### default theme by teejez:
|
||||
waterfall_colors = "[0x000000ff,0x0000ffff,0x00ffffff,0x00ff00ff,0xffff00ff,0xff0000ff,0xff00ffff,0xffffffff]"
|
||||
waterfall_min_level = -88 #in dB
|
||||
waterfall_max_level = -20
|
||||
waterfall_auto_level_margin = (5, 40)
|
||||
### old theme by HA7ILM:
|
||||
#waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]"
|
||||
#waterfall_min_level = -115 #in dB
|
||||
#waterfall_max_level = 0
|
||||
#waterfall_auto_level_margin = (20, 30)
|
||||
##For the old colors, you might also want to set [fft_voverlap_factor] to 0.
|
||||
|
||||
#Note: When the auto waterfall level button is clicked, the following happens:
|
||||
# [waterfall_min_level] = [current_min_power_level] - [waterfall_auto_level_margin[0]]
|
||||
# [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin[1]]
|
||||
#
|
||||
# ___|____________________________________|____________________________________|____________________________________|___> signal power
|
||||
# \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level | \_waterfall_auto_level_margin[1]_/
|
||||
# current_max_power_level __|
|
||||
|
||||
# 3D view settings
|
||||
mathbox_waterfall_frequency_resolution = 128 #bins
|
||||
mathbox_waterfall_history_length = 10 #seconds
|
||||
mathbox_waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]"
|
||||
|
||||
# === Experimental settings ===
|
||||
#Warning! The settings below are very experimental.
|
||||
csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr.
|
||||
csdr_print_bufsizes = False # This prints the buffer sizes used for csdr processes.
|
||||
csdr_through = False # Setting this True will print out how much data is going into the DSP chains.
|
||||
|
||||
nmux_memory = 50 #in megabytes. This sets the approximate size of the circular buffer used by nmux.
|
||||
|
||||
#Look up external IP address automatically from icanhazip.com, and use it as [server_hostname]
|
||||
"""
|
||||
print "[openwebrx-config] Detecting external IP address..."
|
||||
|
|
|
@ -0,0 +1,453 @@
|
|||
"""
|
||||
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>
|
||||
|
||||
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 time
|
||||
import os
|
||||
import code
|
||||
import signal
|
||||
import fcntl
|
||||
|
||||
class dsp:
|
||||
|
||||
def __init__(self):
|
||||
self.samp_rate = 250000
|
||||
self.output_rate = 11025 #this is default, and cannot be set at the moment
|
||||
self.fft_size = 1024
|
||||
self.fft_fps = 5
|
||||
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.format_conversion = "csdr convert_u8_f"
|
||||
self.base_bufsize = 512
|
||||
self.nc_port = 4951
|
||||
self.csdr_dynamic_bufsize = False
|
||||
self.csdr_print_bufsizes = False
|
||||
self.csdr_through = False
|
||||
self.squelch_level = 0
|
||||
self.fft_averages = 50
|
||||
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", "shift_pipe", "squelch_pipe", "smeter_pipe", "iqtee_pipe", "iqtee2_pipe"]
|
||||
self.secondary_pipe_names=["secondary_shift_pipe"]
|
||||
self.secondary_offset_freq = 1000
|
||||
|
||||
def chain(self,which):
|
||||
if which in [ "dmr", "dstar", "nxdn", "ysf" ]:
|
||||
self.set_output_rate(48000)
|
||||
else:
|
||||
self.set_output_rate(11025)
|
||||
any_chain_base="nc -v 127.0.0.1 {nc_port} | "
|
||||
if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
|
||||
if self.csdr_through: any_chain_base+="csdr through | "
|
||||
any_chain_base+=self.format_conversion+(" | " if self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
|
||||
if which == "fft":
|
||||
fft_chain_base = any_chain_base+"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":
|
||||
return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}"
|
||||
else:
|
||||
return fft_chain_base
|
||||
chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | "
|
||||
if self.secondary_demodulator:
|
||||
chain_begin+="csdr tee {iqtee_pipe} | "
|
||||
chain_begin+="csdr tee {iqtee2_pipe} | "
|
||||
chain_end = ""
|
||||
if self.audio_compression=="adpcm":
|
||||
chain_end = " | csdr encode_ima_adpcm_i16_u8"
|
||||
if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr convert_f_s16"+chain_end
|
||||
if which in [ "dstar", "nxdn" ]:
|
||||
c = chain_begin
|
||||
c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
|
||||
if which == "dstar":
|
||||
c += " | dsd -fd"
|
||||
elif which == "nxdn":
|
||||
c += " | dsd -fi"
|
||||
c += " -i - -o - -u 2 -g 10"
|
||||
c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --input-buffer 160 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 220"
|
||||
c += chain_end
|
||||
return c
|
||||
elif which == "dmr":
|
||||
c = chain_begin
|
||||
c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
|
||||
c += " | rrc_filter | gfsk_demodulator | dmr_decoder --fifo {meta_pipe} | mbe_synthesizer"
|
||||
c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256"
|
||||
c += chain_end
|
||||
return c
|
||||
elif which == "ysf":
|
||||
c = chain_begin
|
||||
c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
|
||||
c += " | rrc_filter | gfsk_demodulator | ysf_decoder --fifo {meta_pipe} | mbe_synthesizer -y"
|
||||
c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256"
|
||||
c += chain_end
|
||||
return c
|
||||
elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
||||
elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
||||
|
||||
def secondary_chain(self, which):
|
||||
secondary_chain_base="cat {input_pipe} | "
|
||||
if which == "fft":
|
||||
return secondary_chain_base+"csdr realpart_cf | csdr fft_fc {secondary_fft_input_size} {secondary_fft_block_size} | csdr logpower_cf -70 " + (" | csdr compress_fft_adpcm_f_u8 {secondary_fft_size}" if self.fft_compression=="adpcm" else "")
|
||||
elif which == "bpsk31":
|
||||
return secondary_chain_base + "csdr shift_addition_cc --fifo {secondary_shift_pipe} | " + \
|
||||
"csdr bandpass_fir_fft_cc $(csdr '=-(31.25)/{if_samp_rate}') $(csdr '=(31.25)/{if_samp_rate}') $(csdr '=31.25/{if_samp_rate}') | " + \
|
||||
"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"
|
||||
|
||||
def set_secondary_demodulator(self, what):
|
||||
self.secondary_demodulator = what
|
||||
|
||||
def secondary_fft_block_size(self):
|
||||
return (self.samp_rate/self.decimation)/(self.fft_fps*2) #*2 is there because we do FFT on real signal here
|
||||
|
||||
def secondary_decimation(self):
|
||||
return 1 #currently unused
|
||||
|
||||
def secondary_bpf_cutoff(self):
|
||||
if self.secondary_demodulator == "bpsk31":
|
||||
return (31.25/2) / self.if_samp_rate()
|
||||
return 0
|
||||
|
||||
def secondary_bpf_transition_bw(self):
|
||||
if self.secondary_demodulator == "bpsk31":
|
||||
return (31.25/2) / 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
|
||||
return 0
|
||||
|
||||
def secondary_bw(self):
|
||||
if self.secondary_demodulator == "bpsk31":
|
||||
return 31.25
|
||||
|
||||
def start_secondary_demodulator(self):
|
||||
if(not self.secondary_demodulator): return
|
||||
print "[openwebrx] starting secondary demodulator from IF input sampled at %d"%self.if_samp_rate()
|
||||
secondary_command_fft=self.secondary_chain("fft")
|
||||
secondary_command_demod=self.secondary_chain(self.secondary_demodulator)
|
||||
self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod + secondary_command_fft)
|
||||
|
||||
secondary_command_fft=secondary_command_fft.format( \
|
||||
input_pipe=self.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(), \
|
||||
)
|
||||
secondary_command_demod=secondary_command_demod.format( \
|
||||
input_pipe=self.iqtee2_pipe, \
|
||||
secondary_shift_pipe=self.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()
|
||||
)
|
||||
|
||||
print "[openwebrx-dsp-plugin:csdr] secondary command (fft) =", secondary_command_fft
|
||||
print "[openwebrx-dsp-plugin:csdr] secondary command (demod) =", secondary_command_demod
|
||||
#code.interact(local=locals())
|
||||
my_env=os.environ.copy()
|
||||
#if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
|
||||
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
|
||||
self.secondary_process_fft = subprocess.Popen(secondary_command_fft, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
|
||||
print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (fft)"
|
||||
self.secondary_process_demod = subprocess.Popen(secondary_command_demod, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) #TODO digimodes
|
||||
print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (demod)" #TODO digimodes
|
||||
self.secondary_processes_running = True
|
||||
|
||||
#open control pipes for csdr and send initialization data
|
||||
# print "==========> 1"
|
||||
if self.secondary_shift_pipe != None: #TODO digimodes
|
||||
# print "==========> 2", self.secondary_shift_pipe
|
||||
self.secondary_shift_pipe_file=open(self.secondary_shift_pipe,"w") #TODO digimodes
|
||||
# print "==========> 3"
|
||||
self.set_secondary_offset_freq(self.secondary_offset_freq) #TODO digimodes
|
||||
# print "==========> 4"
|
||||
|
||||
self.set_pipe_nonblocking(self.secondary_process_demod.stdout)
|
||||
self.set_pipe_nonblocking(self.secondary_process_fft.stdout)
|
||||
|
||||
def set_secondary_offset_freq(self, value):
|
||||
self.secondary_offset_freq=value
|
||||
if self.secondary_processes_running:
|
||||
self.secondary_shift_pipe_file.write("%g\n"%(-float(self.secondary_offset_freq)/self.if_samp_rate()))
|
||||
self.secondary_shift_pipe_file.flush()
|
||||
|
||||
def stop_secondary_demodulator(self):
|
||||
if self.secondary_processes_running == False: return
|
||||
self.try_delete_pipes(self.secondary_pipe_names)
|
||||
if self.secondary_process_fft: os.killpg(os.getpgid(self.secondary_process_fft.pid), signal.SIGTERM)
|
||||
if self.secondary_process_demod: os.killpg(os.getpgid(self.secondary_process_demod.pid), signal.SIGTERM)
|
||||
self.secondary_processes_running = False
|
||||
|
||||
def read_secondary_demod(self, size):
|
||||
return self.secondary_process_demod.stdout.read(size)
|
||||
|
||||
def read_secondary_fft(self, size):
|
||||
return self.secondary_process_fft.stdout.read(size)
|
||||
|
||||
def get_secondary_demodulator(self):
|
||||
return self.secondary_demodulator
|
||||
|
||||
def set_secondary_fft_size(self,secondary_fft_size):
|
||||
#to change this, restart is required
|
||||
self.secondary_fft_size=secondary_fft_size
|
||||
|
||||
def set_audio_compression(self,what):
|
||||
self.audio_compression = what
|
||||
|
||||
def set_fft_compression(self,what):
|
||||
self.fft_compression = what
|
||||
|
||||
def get_fft_bytes_to_read(self):
|
||||
if self.fft_compression=="none": return self.fft_size*4
|
||||
if self.fft_compression=="adpcm": return (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):
|
||||
#to change this, restart is required
|
||||
self.samp_rate=samp_rate
|
||||
self.decimation=1
|
||||
while self.samp_rate/(self.decimation+1)>self.output_rate:
|
||||
self.decimation+=1
|
||||
self.last_decimation=float(self.if_samp_rate())/self.output_rate
|
||||
|
||||
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 set_output_rate(self,output_rate):
|
||||
self.output_rate=output_rate
|
||||
self.set_samp_rate(self.samp_rate) #as it depends on output_rate
|
||||
|
||||
def set_demodulator(self,demodulator):
|
||||
#to change this, restart is required
|
||||
self.demodulator=demodulator
|
||||
|
||||
def get_demodulator(self):
|
||||
return self.demodulator
|
||||
|
||||
def set_fft_size(self,fft_size):
|
||||
#to change this, restart is required
|
||||
self.fft_size=fft_size
|
||||
|
||||
def set_fft_fps(self,fft_fps):
|
||||
#to change this, restart is required
|
||||
self.fft_fps=fft_fps
|
||||
|
||||
def set_fft_averages(self,fft_averages):
|
||||
#to change this, restart is required
|
||||
self.fft_averages=fft_averages
|
||||
|
||||
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_format_conversion(self,format_conversion):
|
||||
self.format_conversion=format_conversion
|
||||
|
||||
def set_offset_freq(self,offset_freq):
|
||||
self.offset_freq=offset_freq
|
||||
if self.running:
|
||||
self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate))
|
||||
self.shift_pipe_file.flush()
|
||||
|
||||
def set_bpf(self,low_cut,high_cut):
|
||||
self.low_cut=low_cut
|
||||
self.high_cut=high_cut
|
||||
if self.running:
|
||||
self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) )
|
||||
self.bpf_pipe_file.flush()
|
||||
|
||||
def get_bpf(self):
|
||||
return [self.low_cut, self.high_cut]
|
||||
|
||||
def set_squelch_level(self, squelch_level):
|
||||
self.squelch_level=squelch_level
|
||||
if self.running:
|
||||
self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) )
|
||||
self.squelch_pipe_file.flush()
|
||||
|
||||
def get_smeter_level(self):
|
||||
if self.running:
|
||||
line=self.smeter_pipe_file.readline()
|
||||
return float(line[:-1])
|
||||
|
||||
def mkfifo(self,path):
|
||||
try:
|
||||
os.unlink(path)
|
||||
except:
|
||||
pass
|
||||
os.mkfifo(path)
|
||||
|
||||
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):
|
||||
# print "try_create_pipes"
|
||||
for pipe_name in pipe_names:
|
||||
# print "\t"+pipe_name
|
||||
if "{"+pipe_name+"}" in command_base:
|
||||
setattr(self, pipe_name, self.pipe_base_path+pipe_name)
|
||||
self.mkfifo(getattr(self, pipe_name))
|
||||
else:
|
||||
setattr(self, pipe_name, None)
|
||||
|
||||
def try_delete_pipes(self, pipe_names):
|
||||
for pipe_name in pipe_names:
|
||||
pipe_path = getattr(self,pipe_name,None)
|
||||
if pipe_path:
|
||||
try: os.unlink(pipe_path)
|
||||
except Exception as e: print "[openwebrx-dsp-plugin:csdr] try_delete_pipes() ::", e
|
||||
|
||||
def set_pipe_nonblocking(self, pipe):
|
||||
flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
|
||||
fcntl.fcntl(pipe, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
|
||||
def start(self):
|
||||
command_base=self.chain(self.demodulator)
|
||||
|
||||
#create control pipes for csdr
|
||||
self.pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
|
||||
# self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = None
|
||||
|
||||
self.try_create_pipes(self.pipe_names, command_base)
|
||||
|
||||
# if "{bpf_pipe}" in command_base:
|
||||
# self.bpf_pipe=pipe_base_path+"bpf"
|
||||
# self.mkfifo(self.bpf_pipe)
|
||||
# if "{shift_pipe}" in command_base:
|
||||
# self.shift_pipe=pipe_base_path+"shift"
|
||||
# self.mkfifo(self.shift_pipe)
|
||||
# if "{squelch_pipe}" in command_base:
|
||||
# self.squelch_pipe=pipe_base_path+"squelch"
|
||||
# self.mkfifo(self.squelch_pipe)
|
||||
# if "{smeter_pipe}" in command_base:
|
||||
# self.smeter_pipe=pipe_base_path+"smeter"
|
||||
# self.mkfifo(self.smeter_pipe)
|
||||
# if "{iqtee_pipe}" in command_base:
|
||||
# self.iqtee_pipe=pipe_base_path+"iqtee"
|
||||
# self.mkfifo(self.iqtee_pipe)
|
||||
# if "{iqtee2_pipe}" in command_base:
|
||||
# self.iqtee2_pipe=pipe_base_path+"iqtee2"
|
||||
# self.mkfifo(self.iqtee2_pipe)
|
||||
|
||||
#run the command
|
||||
command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_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, \
|
||||
squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe, iqtee_pipe=self.iqtee_pipe, iqtee2_pipe=self.iqtee2_pipe )
|
||||
|
||||
print "[openwebrx-dsp-plugin:csdr] Command =",command
|
||||
#code.interact(local=locals())
|
||||
my_env=os.environ.copy()
|
||||
if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
|
||||
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
|
||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
|
||||
self.running = True
|
||||
|
||||
#open control pipes for csdr and send initialization data
|
||||
if self.bpf_pipe != None:
|
||||
self.bpf_pipe_file=open(self.bpf_pipe,"w")
|
||||
self.set_bpf(self.low_cut,self.high_cut)
|
||||
if self.shift_pipe != None:
|
||||
self.shift_pipe_file=open(self.shift_pipe,"w")
|
||||
self.set_offset_freq(self.offset_freq)
|
||||
if self.squelch_pipe != None:
|
||||
self.squelch_pipe_file=open(self.squelch_pipe,"w")
|
||||
self.set_squelch_level(self.squelch_level)
|
||||
if self.smeter_pipe != None:
|
||||
self.smeter_pipe_file=open(self.smeter_pipe,"r")
|
||||
self.set_pipe_nonblocking(self.smeter_pipe_file)
|
||||
|
||||
self.start_secondary_demodulator()
|
||||
|
||||
def read(self,size):
|
||||
return self.process.stdout.read(size)
|
||||
|
||||
def stop(self):
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
self.stop_secondary_demodulator()
|
||||
#if(self.process.poll()!=None):return # returns None while subprocess is running
|
||||
#while(self.process.poll()==None):
|
||||
# #self.process.kill()
|
||||
# print "killproc",os.getpgid(self.process.pid),self.process.pid
|
||||
# os.killpg(self.process.pid, signal.SIGTERM)
|
||||
#
|
||||
# time.sleep(0.1)
|
||||
|
||||
self.try_delete_pipes(self.pipe_names)
|
||||
|
||||
# if self.bpf_pipe:
|
||||
# try: os.unlink(self.bpf_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
|
||||
# if self.shift_pipe:
|
||||
# try: os.unlink(self.shift_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe
|
||||
# if self.squelch_pipe:
|
||||
# try: os.unlink(self.squelch_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe
|
||||
# if self.smeter_pipe:
|
||||
# try: os.unlink(self.smeter_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe
|
||||
# if self.iqtee_pipe:
|
||||
# try: os.unlink(self.iqtee_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee_pipe
|
||||
# if self.iqtee2_pipe:
|
||||
# try: os.unlink(self.iqtee2_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee2_pipe
|
||||
|
||||
self.running = False
|
||||
|
||||
def restart(self):
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
del(self.process)
|
Binary file not shown.
After Width: | Height: | Size: 9.9 KiB |
294
htdocs/index.wrx
294
htdocs/index.wrx
|
@ -1,9 +1,9 @@
|
|||
<!DOCTYPE HTML>
|
||||
<!--
|
||||
|
||||
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>
|
||||
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>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
|
@ -20,133 +20,171 @@
|
|||
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
|
||||
<script type="text/javascript">
|
||||
//Global variables
|
||||
var client_id="%[CLIENT_ID]";
|
||||
var ws_url="%[WS_URL]";
|
||||
var rx_photo_height=%[RX_PHOTO_HEIGHT];
|
||||
var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
|
||||
var starting_mod = "%[START_MOD]";
|
||||
var starting_offset_frequency = %[START_OFFSET_FREQ];
|
||||
var waterfall_colors=%[WATERFALL_COLORS];
|
||||
var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL];
|
||||
var waterfall_max_level_default=%[WATERFALL_MAX_LEVEL];
|
||||
</script>
|
||||
<script src="sdr.js"></script>
|
||||
<script src="openwebrx.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body onload="openwebrx_init();">
|
||||
<head>
|
||||
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
|
||||
<script type="text/javascript">
|
||||
//Global variables
|
||||
var client_id="%[CLIENT_ID]";
|
||||
var ws_url="%[WS_URL]";
|
||||
var rx_photo_height=%[RX_PHOTO_HEIGHT];
|
||||
var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
|
||||
var starting_mod="%[START_MOD]";
|
||||
var starting_offset_frequency = %[START_OFFSET_FREQ];
|
||||
var waterfall_colors=%[WATERFALL_COLORS];
|
||||
var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL];
|
||||
var waterfall_max_level_default=%[WATERFALL_MAX_LEVEL];
|
||||
var waterfall_auto_level_margin=%[WATERFALL_AUTO_LEVEL_MARGIN];
|
||||
var server_enable_digimodes=%[DIGIMODES_ENABLE];
|
||||
var mathbox_waterfall_frequency_resolution=%[MATHBOX_WATERFALL_FRES];
|
||||
var mathbox_waterfall_history_length=%[MATHBOX_WATERFALL_THIST];
|
||||
var mathbox_waterfall_colors=%[MATHBOX_WATERFALL_COLORS];
|
||||
</script>
|
||||
<script src="sdr.js"></script>
|
||||
<script src="mathbox-bundle.min.js"></script>
|
||||
<script src="openwebrx.js"></script>
|
||||
<script src="jquery-3.2.1.min.js"></script>
|
||||
<script src="jquery.nanoscroller.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="nanoscroller.css" />
|
||||
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body onload="openwebrx_init();">
|
||||
<div id="webrx-page-container">
|
||||
<div id="webrx-top-container">
|
||||
<div id="webrx-top-photo-clip">
|
||||
<img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>
|
||||
<div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div>
|
||||
<div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div>
|
||||
</div>
|
||||
<div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div>
|
||||
<div id="webrx-top-bar" class="webrx-top-bar-parts">
|
||||
<a href="http://openwebrx.org/" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
|
||||
<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a>
|
||||
<img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/>
|
||||
<img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/>
|
||||
<div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div>
|
||||
<div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div>
|
||||
<div id="openwebrx-rx-details-arrow">
|
||||
<a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
|
||||
<a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
|
||||
</div>
|
||||
<section id="openwebrx-main-buttons">
|
||||
<ul>
|
||||
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
|
||||
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
|
||||
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div id="webrx-main-container">
|
||||
<div id="openwebrx-scale-container">
|
||||
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
|
||||
</div>
|
||||
<div id="webrx-canvas-container">
|
||||
|
||||
<div id="openwebrx-phantom-canvas"></div>
|
||||
<!-- add canvas here by javascript -->
|
||||
</div>
|
||||
<div id="openwebrx-panels-container">
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="259,260">
|
||||
<div id="webrx-actual-freq">---.--- MHz</div>
|
||||
<div id="webrx-mouse-freq">---.--- MHz</div>
|
||||
<!--<div class="openwebrx-button" onclick="ws.send('SET mod=wfm');" >WFM</div>-->
|
||||
<div class="openwebrx-panel-line">
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nfm');">FM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('am');">AM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('lsb');">LSB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('usb');">USB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('cw');">CW</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('dmr');">DMR</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('dstar');">DStar</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nxdn');">NXDN</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('ysf');">YSF</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div title="Mute on/off" id="openwebrx-mute-off" class="openwebrx-button" onclick="toggleMute();"><img src="gfx/openwebrx-speaker.png" class="openwebrx-sliderbtn-img" id="openwebrx-mute-img"></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" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></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" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
|
||||
<input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()">
|
||||
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></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"> <img src="gfx/openwebrx-zoom-in.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
|
||||
<div id="openwebrx-smeter-db">0 dB</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div id="openwebrx-smeter-outer">
|
||||
<div id="openwebrx-smeter-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="2" data-panel-size="619,142">
|
||||
<div class="openwebrx-panel-inner" id="openwebrx-log-scroll">
|
||||
<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
|
||||
<span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/>
|
||||
<div id="openwebrx-debugdiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
</div>
|
||||
<div id="webrx-top-container">
|
||||
<div id="webrx-top-photo-clip">
|
||||
<img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>
|
||||
<div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div>
|
||||
<div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div>
|
||||
</div>
|
||||
<div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div>
|
||||
<div id="webrx-top-bar" class="webrx-top-bar-parts">
|
||||
<a href="https://sdr.hu/openwebrx" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
|
||||
<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a>
|
||||
<img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/>
|
||||
<img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/>
|
||||
<div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div>
|
||||
<div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div>
|
||||
<div id="openwebrx-rx-details-arrow">
|
||||
<a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
|
||||
<a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
|
||||
</div>
|
||||
<section id="openwebrx-main-buttons">
|
||||
<ul>
|
||||
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
|
||||
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
|
||||
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div id="webrx-main-container">
|
||||
<div id="openwebrx-scale-container">
|
||||
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
|
||||
</div>
|
||||
<div id="openwebrx-mathbox-container"> </div>
|
||||
<div id="webrx-canvas-container">
|
||||
<div id="openwebrx-phantom-canvas"></div>
|
||||
<!-- add canvas here by javascript -->
|
||||
</div>
|
||||
<div id="openwebrx-panels-container">
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="259,115">
|
||||
<div id="webrx-actual-freq">---.--- MHz</div>
|
||||
<div id="webrx-mouse-freq">---.--- MHz</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nfm"
|
||||
onclick="demodulator_analog_replace('nfm');">FM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-am"
|
||||
onclick="demodulator_analog_replace('am');">AM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-lsb"
|
||||
onclick="demodulator_analog_replace('lsb');">LSB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-usb"
|
||||
onclick="demodulator_analog_replace('usb');">USB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-cw"
|
||||
onclick="demodulator_analog_replace('cw');">CW</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dmr"
|
||||
onclick="demodulator_analog_replace('dmr');">DMR</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dstar"
|
||||
onclick="demodulator_analog_replace('dstar');">DStar</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nxdn"
|
||||
onclick="demodulator_analog_replace('nxdn');">NXDN</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-ysf"
|
||||
onclick="demodulator_analog_replace('ysf');">YSF</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dig" onclick="demodulator_digital_replace_last();">DIG</div>
|
||||
<select id="openwebrx-secondary-demod-listbox" onchange="secondary_demod_listbox_changed();">
|
||||
<option value="none"></option>
|
||||
<option value="bpsk31">BPSK31</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div title="Mute on/off" id="openwebrx-mute-off" class="openwebrx-button" onclick="toggleMute();"><img src="gfx/openwebrx-speaker.png" class="openwebrx-sliderbtn-img" id="openwebrx-mute-img"></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" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></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" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
|
||||
<input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()">
|
||||
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></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"> <img src="gfx/openwebrx-zoom-in.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="mathbox_toggle();" title="Toggle 3D view"><img src="gfx/openwebrx-3d-spectrum.png" /></div>
|
||||
<div id="openwebrx-smeter-db">0 dB</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div id="openwebrx-smeter-outer">
|
||||
<div id="openwebrx-smeter-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,137">
|
||||
<div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
|
||||
<div class="nano-content">
|
||||
<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
|
||||
<span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/>
|
||||
<div id="openwebrx-debugdiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="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" data-panel-name="digimodes" data-panel-pos="left" data-panel-order="2" data-panel-size="619,210">
|
||||
<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" data-panel-name="metadata" data-panel-pos="left" data-panel-order="1" data-panel-size="615,36">
|
||||
</div>
|
||||
<div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
|
||||
<div id="openwebrx-play-button-text">
|
||||
<img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" />
|
||||
<br /><br />Start OpenWebRX
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
|
||||
<div id="openwebrx-play-button-text">
|
||||
<img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" />
|
||||
<br /><br />Start OpenWebRX
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,461 @@
|
|||
.shadergraph-graph {
|
||||
font: 12px sans-serif;
|
||||
line-height: 25px;
|
||||
position: relative;
|
||||
}
|
||||
.shadergraph-graph:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
clear: both;
|
||||
}
|
||||
.shadergraph-graph svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
.shadergraph-clear {
|
||||
clear: both;
|
||||
}
|
||||
.shadergraph-graph svg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
.shadergraph-column {
|
||||
float: left;
|
||||
}
|
||||
.shadergraph-node .shadergraph-graph {
|
||||
float: left;
|
||||
clear: both;
|
||||
overflow: visible;
|
||||
}
|
||||
.shadergraph-node .shadergraph-graph .shadergraph-node {
|
||||
margin: 5px 15px 15px;
|
||||
}
|
||||
.shadergraph-node {
|
||||
margin: 5px 15px 25px;
|
||||
background: rgba(0, 0, 0, .1);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .2),
|
||||
0 1px 10px rgba(0, 0, 0, .2);
|
||||
min-height: 35px;
|
||||
float: left;
|
||||
clear: left;
|
||||
position: relative;
|
||||
}
|
||||
.shadergraph-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
.shadergraph-header {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
background: rgba(0, 0, 0, .3);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, .25);
|
||||
color: #fff;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
margin-bottom: 5px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.shadergraph-outlet div {
|
||||
}
|
||||
.shadergraph-outlet-in .shadergraph-name {
|
||||
margin-right: 7px;
|
||||
}
|
||||
.shadergraph-outlet-out .shadergraph-name {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.shadergraph-name {
|
||||
margin: 0 4px;
|
||||
}
|
||||
.shadergraph-point {
|
||||
margin: 6px;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 7.5px;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
.shadergraph-outlet-in {
|
||||
float: left;
|
||||
clear: left;
|
||||
}
|
||||
.shadergraph-outlet-in div {
|
||||
float: left;
|
||||
}
|
||||
.shadergraph-outlet-out {
|
||||
float: right;
|
||||
clear: right;
|
||||
}
|
||||
.shadergraph-outlet-out div {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.shadergraph-node-callback {
|
||||
background: rgba(205, 209, 221, .5);
|
||||
box-shadow: 0 1px 2px rgba(0, 10, 40, .2),
|
||||
0 1px 10px rgba(0, 10, 40, .2);
|
||||
}
|
||||
.shadergraph-node-callback > .shadergraph-header {
|
||||
background: rgba(0, 20, 80, .3);
|
||||
}
|
||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-callback {
|
||||
background: rgba(0, 20, 80, .1);
|
||||
}
|
||||
|
||||
.shadergraph-node-call {
|
||||
background: rgba(209, 221, 205, .5);
|
||||
box-shadow: 0 1px 2px rgba(10, 40, 0, .2),
|
||||
0 1px 10px rgba(10, 40, 0, .2);
|
||||
}
|
||||
.shadergraph-node-call > .shadergraph-header {
|
||||
background: rgba(20, 80, 0, .3);
|
||||
}
|
||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-call {
|
||||
background: rgba(20, 80, 0, .1);
|
||||
}
|
||||
|
||||
.shadergraph-node-isolate {
|
||||
background: rgba(221, 205, 209, .5);
|
||||
box-shadow: 0 1px 2px rgba(40, 0, 10, .2),
|
||||
0 1px 10px rgba(40, 0, 10, .2);
|
||||
}
|
||||
.shadergraph-node-isolate > .shadergraph-header {
|
||||
background: rgba(80, 0, 20, .3);
|
||||
}
|
||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-isolate {
|
||||
background: rgba(80, 0, 20, .1);
|
||||
}
|
||||
|
||||
.shadergraph-node.shadergraph-has-code {
|
||||
cursor: pointer;
|
||||
}
|
||||
.shadergraph-node.shadergraph-has-code::before {
|
||||
position: absolute;
|
||||
content: ' ';
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: none;
|
||||
border: 2px solid rgba(0, 0, 0, .25);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.shadergraph-node.shadergraph-has-code:hover::before {
|
||||
display: block;
|
||||
}
|
||||
.shadergraph-code {
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
white-space: pre;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .2),
|
||||
0 1px 10px rgba(0, 0, 0, .2);
|
||||
font-family: monospace;
|
||||
font-size: 10px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.shadergraph-overlay {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
border-top: 1px solid #CCC;
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-view {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-inside {
|
||||
width: 4000px;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-close {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
padding: 4px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255,255,255,.3);
|
||||
color: rgba(0, 0, 0, .3);
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-close:hover {
|
||||
background: rgba(255,255,255,1);
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-graph {
|
||||
padding-top: 10px;
|
||||
overflow: visible;
|
||||
min-height: 100%;
|
||||
}
|
||||
.shadergraph-overlay span {
|
||||
display: block;
|
||||
padding: 5px 15px;
|
||||
margin: 0;
|
||||
background: rgba(0, 0, 0, .1);
|
||||
font-weight: bold;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.mathbox-loader {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 10px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.mathbox-loader.mathbox-exit {
|
||||
opacity: 0;
|
||||
-webkit-transition:
|
||||
opacity .15s ease-in-out;
|
||||
transition:
|
||||
opacity .15s ease-in-out;
|
||||
}
|
||||
|
||||
.mathbox-progress {
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
width: 80px;
|
||||
margin: 0 auto 20px;
|
||||
box-shadow:
|
||||
1px 1px 1px rgba(255, 255, 255, .2),
|
||||
1px -1px 1px rgba(255, 255, 255, .2),
|
||||
-1px 1px 1px rgba(255, 255, 255, .2),
|
||||
-1px -1px 1px rgba(255, 255, 255, .2);
|
||||
background: #ccc;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mathbox-progress > div {
|
||||
display: block;
|
||||
width: 0px;
|
||||
height: 10px;
|
||||
background: #888;
|
||||
}
|
||||
|
||||
.mathbox-logo {
|
||||
position: relative;
|
||||
width: 140px;
|
||||
height: 100px;
|
||||
margin: 0 auto 10px;
|
||||
-webkit-perspective: 200px;
|
||||
perspective: 200px;
|
||||
}
|
||||
|
||||
.mathbox-logo > div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.mathbox-logo > :nth-child(1) {
|
||||
-webkit-transform: rotateZ(22deg) rotateX(24deg) rotateY(30deg);
|
||||
transform: rotateZ(22deg) rotateX(24deg) rotateY(30deg);
|
||||
}
|
||||
|
||||
.mathbox-logo > :nth-child(2) {
|
||||
-webkit-transform: rotateZ(11deg) rotateX(12deg) rotateY(15deg) scale3d(.6, .6, .6);
|
||||
transform: rotateZ(11deg) rotateX(12deg) rotateY(15deg) scale3d(.6, .6, .6);
|
||||
}
|
||||
|
||||
.mathbox-logo > div > div {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
margin-top: -100px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.mathbox-logo > div > :nth-child(1) {
|
||||
-webkit-transform: scale(0.5, 0.5);
|
||||
transform: rotateX(30deg) scale(0.5, 0.5);
|
||||
}
|
||||
|
||||
.mathbox-logo > div > :nth-child(2) {
|
||||
-webkit-transform: rotateX(90deg) scale(0.42, 0.42);
|
||||
transform: rotateX(90deg) scale(0.42, 0.42);
|
||||
}
|
||||
|
||||
.mathbox-logo > div > :nth-child(3) {
|
||||
-webkit-transform: rotateY(90deg) scale(0.35, 0.35);
|
||||
transform: rotateY(90deg) scale(0.35, 0.35);
|
||||
}
|
||||
|
||||
.mathbox-logo > :nth-child(1) > :nth-child(1) {
|
||||
border: 16px solid #808080;
|
||||
}
|
||||
.mathbox-logo > :nth-child(1) > :nth-child(2) {
|
||||
border: 19px solid #A0A0A0;
|
||||
}
|
||||
.mathbox-logo > :nth-child(1) > :nth-child(3) {
|
||||
border: 23px solid #C0C0C0;
|
||||
}
|
||||
.mathbox-logo > :nth-child(2) > :nth-child(1) {
|
||||
border: 27px solid #808080;
|
||||
}
|
||||
.mathbox-logo > :nth-child(2) > :nth-child(2) {
|
||||
border: 32px solid #A0A0A0;
|
||||
}
|
||||
.mathbox-logo > :nth-child(2) > :nth-child(3) {
|
||||
border: 38px solid #C0C0C0;
|
||||
}
|
||||
|
||||
.mathbox-splash-blue .mathbox-progress {
|
||||
background: #def;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-progress > div {
|
||||
background: #1979e7;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(1) {
|
||||
border-color: #1979e7;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(2) {
|
||||
border-color: #33b0ff;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(3) {
|
||||
border-color: #75eaff;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(1) {
|
||||
border-color: #18487F;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(2) {
|
||||
border-color: #33b0ff;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(3) {
|
||||
border-color: #75eaff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.mathbox-overlays {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
transform-style: preserve-3d;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mathbox-overlays > div {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.mathbox-overlay > div {
|
||||
position: absolute;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.mathbox-label {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.mathbox-outline-1 {
|
||||
text-shadow:
|
||||
-1px -1px 0px rgb(255, 255, 255),
|
||||
1px 1px 0px rgb(255, 255, 255),
|
||||
-1px 1px 0px rgb(255, 255, 255),
|
||||
1px -1px 0px rgb(255, 255, 255),
|
||||
1px 0px 1px rgb(255, 255, 255),
|
||||
-1px 0px 1px rgb(255, 255, 255),
|
||||
0px -1px 1px rgb(255, 255, 255),
|
||||
0px 1px 1px rgb(255, 255, 255);
|
||||
}
|
||||
.mathbox-outline-2 {
|
||||
text-shadow:
|
||||
0px -2px 0px rgb(255, 255, 255),
|
||||
0px 2px 0px rgb(255, 255, 255),
|
||||
-2px 0px 0px rgb(255, 255, 255),
|
||||
2px 0px 0px rgb(255, 255, 255),
|
||||
-1px -2px 0px rgb(255, 255, 255),
|
||||
-2px -1px 0px rgb(255, 255, 255),
|
||||
-1px 2px 0px rgb(255, 255, 255),
|
||||
-2px 1px 0px rgb(255, 255, 255),
|
||||
1px 2px 0px rgb(255, 255, 255),
|
||||
2px 1px 0px rgb(255, 255, 255),
|
||||
1px -2px 0px rgb(255, 255, 255),
|
||||
2px -1px 0px rgb(255, 255, 255);
|
||||
}
|
||||
.mathbox-outline-3 {
|
||||
text-shadow:
|
||||
3px 0px 0px rgb(255, 255, 255),
|
||||
-3px 0px 0px rgb(255, 255, 255),
|
||||
0px 3px 0px rgb(255, 255, 255),
|
||||
0px -3px 0px rgb(255, 255, 255),
|
||||
|
||||
-2px -2px 0px rgb(255, 255, 255),
|
||||
-2px 2px 0px rgb(255, 255, 255),
|
||||
2px 2px 0px rgb(255, 255, 255),
|
||||
2px -2px 0px rgb(255, 255, 255),
|
||||
|
||||
-1px -2px 1px rgb(255, 255, 255),
|
||||
-2px -1px 1px rgb(255, 255, 255),
|
||||
-1px 2px 1px rgb(255, 255, 255),
|
||||
-2px 1px 1px rgb(255, 255, 255),
|
||||
1px 2px 1px rgb(255, 255, 255),
|
||||
2px 1px 1px rgb(255, 255, 255),
|
||||
1px -2px 1px rgb(255, 255, 255),
|
||||
2px -1px 1px rgb(255, 255, 255);
|
||||
}
|
||||
.mathbox-outline-4 {
|
||||
text-shadow:
|
||||
4px 0px 0px rgb(255, 255, 255),
|
||||
-4px 0px 0px rgb(255, 255, 255),
|
||||
0px 4px 0px rgb(255, 255, 255),
|
||||
0px -4px 0px rgb(255, 255, 255),
|
||||
|
||||
-3px -2px 0px rgb(255, 255, 255),
|
||||
-3px 2px 0px rgb(255, 255, 255),
|
||||
3px 2px 0px rgb(255, 255, 255),
|
||||
3px -2px 0px rgb(255, 255, 255),
|
||||
|
||||
-2px -3px 0px rgb(255, 255, 255),
|
||||
-2px 3px 0px rgb(255, 255, 255),
|
||||
2px 3px 0px rgb(255, 255, 255),
|
||||
2px -3px 0px rgb(255, 255, 255),
|
||||
|
||||
-1px -2px 1px rgb(255, 255, 255),
|
||||
-2px -1px 1px rgb(255, 255, 255),
|
||||
-1px 2px 1px rgb(255, 255, 255),
|
||||
-2px 1px 1px rgb(255, 255, 255),
|
||||
1px 2px 1px rgb(255, 255, 255),
|
||||
2px 1px 1px rgb(255, 255, 255),
|
||||
1px -2px 1px rgb(255, 255, 255),
|
||||
2px -1px 1px rgb(255, 255, 255);
|
||||
|
||||
}
|
||||
.mathbox-outline-fill, .mathbox-outline-fill * {
|
||||
color: #fff !important;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/** initial setup **/
|
||||
.nano {
|
||||
position : relative;
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
overflow : hidden;
|
||||
}
|
||||
.nano > .nano-content {
|
||||
position : absolute;
|
||||
overflow : scroll;
|
||||
overflow-x : hidden;
|
||||
top : 0;
|
||||
right : 0;
|
||||
bottom : 0;
|
||||
left : 0;
|
||||
}
|
||||
.nano > .nano-content:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
.nano > .nano-content::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.has-scrollbar > .nano-content::-webkit-scrollbar {
|
||||
display: block;
|
||||
}
|
||||
.nano > .nano-pane {
|
||||
background : rgba(0,0,0,.25);
|
||||
position : absolute;
|
||||
width : 8px;
|
||||
right : 0;
|
||||
top : 0;
|
||||
bottom : 0;
|
||||
visibility : hidden\9; /* Target only IE7 and IE8 with this hack */
|
||||
opacity : .01;
|
||||
-webkit-transition : .2s;
|
||||
-moz-transition : .2s;
|
||||
-o-transition : .2s;
|
||||
transition : .2s;
|
||||
-moz-border-radius : 3px;
|
||||
-webkit-border-radius : 3px;
|
||||
border-radius : 3px;
|
||||
}
|
||||
.nano > .nano-pane > .nano-slider {
|
||||
background: #444;
|
||||
background: rgba(0,0,0,.5);
|
||||
position : relative;
|
||||
margin : 0 0px;
|
||||
-moz-border-radius : 4px;
|
||||
-webkit-border-radius : 4px;
|
||||
border-radius : 4px;
|
||||
}
|
||||
.nano:hover > .nano-pane, .nano-pane.active, .nano-pane.flashed {
|
||||
visibility : visible\9; /* Target only IE7 and IE8 with this hack */
|
||||
opacity : 0.99;
|
||||
}
|
|
@ -28,6 +28,11 @@ html, body
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
select
|
||||
{
|
||||
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
|
||||
}
|
||||
|
||||
input
|
||||
{
|
||||
vertical-align:middle;
|
||||
|
@ -394,6 +399,13 @@ input[type=range]:focus::-ms-fill-upper
|
|||
border-style: none;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
/*transition: left 200ms, width 200ms;*/
|
||||
}
|
||||
|
||||
#openwebrx-mathbox-container
|
||||
{
|
||||
overflow: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#openwebrx-phantom-canvas
|
||||
|
@ -410,11 +422,15 @@ input[type=range]:focus::-ms-fill-upper
|
|||
height: 396px;
|
||||
}*/
|
||||
|
||||
/*#webrx-debugdiv
|
||||
#openwebrx-log-scroll
|
||||
{
|
||||
font-size: 10pt;
|
||||
/*overflow-y:scroll;*/
|
||||
/*}*/
|
||||
/*overflow-y:auto;*/
|
||||
height: 125px;
|
||||
width: 619px
|
||||
}
|
||||
|
||||
.nano .nano-pane { background: #444; }
|
||||
.nano .nano-slider { background: #eee !important; }
|
||||
|
||||
#webrx-main-container
|
||||
{
|
||||
|
@ -499,7 +515,7 @@ input[type=range]:focus::-ms-fill-upper
|
|||
|
||||
.openwebrx-panel
|
||||
{
|
||||
transform: perspective( 600px );
|
||||
transform: perspective( 600px ) rotateX( 90deg );
|
||||
visibility: hidden;
|
||||
background-color: #575757;
|
||||
padding: 10px;
|
||||
|
@ -526,7 +542,7 @@ input[type=range]:focus::-ms-fill-upper
|
|||
.openwebrx-button
|
||||
{
|
||||
background-color: #373737;
|
||||
padding: 5px;
|
||||
padding: 4.2px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
color: White;
|
||||
|
@ -544,7 +560,7 @@ input[type=range]:focus::-ms-fill-upper
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.openwebrx-button:hover
|
||||
.openwebrx-button:hover, .openwebrx-demodulator-button.highlighted
|
||||
{
|
||||
/*background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0 , #3F3F3F), color-stop(1, #777777) );
|
||||
background:-moz-linear-gradient( center top, #373737 5%, #4F4F4F 100% );*/
|
||||
|
@ -560,7 +576,7 @@ input[type=range]:focus::-ms-fill-upper
|
|||
|
||||
.openwebrx-demodulator-button
|
||||
{
|
||||
width: 50px;
|
||||
width: 38px;
|
||||
height: 19px;
|
||||
font-size: 12pt;
|
||||
text-align: center;
|
||||
|
@ -568,7 +584,7 @@ input[type=range]:focus::-ms-fill-upper
|
|||
|
||||
.openwebrx-square-button img
|
||||
{
|
||||
height: 30px;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
.openwebrx-round-button
|
||||
|
@ -732,7 +748,7 @@ img.openwebrx-mirror-img
|
|||
{
|
||||
position: relative;
|
||||
top: -2px;
|
||||
width:91px;
|
||||
width: 95px;
|
||||
}
|
||||
|
||||
.openwebrx-sliderbtn-img
|
||||
|
@ -776,7 +792,7 @@ img.openwebrx-mirror-img
|
|||
font-size: 10pt;
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
margin-top: 29px;
|
||||
margin-top: 24px;
|
||||
font-family: 'expletus-sans-medium';
|
||||
}
|
||||
|
||||
|
@ -806,3 +822,152 @@ img.openwebrx-mirror-img
|
|||
{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-canvas-container
|
||||
{
|
||||
/*margin: -10px -10px 10px -10px;*/
|
||||
margin: -10px -10px 0px -10px;
|
||||
border-radius: 15px;
|
||||
height: 150px;
|
||||
background-color: #333;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-canvas-container canvas
|
||||
{
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
transition: width 500ms, left 500ms;
|
||||
}
|
||||
|
||||
#openwebrx-secondary-demod-listbox
|
||||
{
|
||||
width: 201px;
|
||||
height: 27px;
|
||||
border-radius: 5px;
|
||||
background-color: #373737;
|
||||
color: White;
|
||||
font-weight: normal;
|
||||
font-size: 13pt;
|
||||
margin-right: 1px;
|
||||
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0 , #373737), color-stop(1, #4F4F4F) );
|
||||
background:-moz-linear-gradient( center top, #373737 0%, #4F4F4F 100% );
|
||||
border-color: transparent;
|
||||
border-width: 0px;
|
||||
-moz-appearance: none;
|
||||
padding-left:3px;
|
||||
}
|
||||
|
||||
#openwebrx-secondary-demod-listbox option
|
||||
{
|
||||
border-width: 0px;
|
||||
background-color: #373737;
|
||||
color: White;
|
||||
}
|
||||
|
||||
#openwebrx-cursor-blink
|
||||
{
|
||||
animation: cursor-blink 1s infinite;
|
||||
/*animation: cursor-3d 2s infinite;*/
|
||||
animation-timing-function: linear;
|
||||
animation-direction: alternate;
|
||||
height: 1em;
|
||||
width: 8px;
|
||||
background-color: White;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
/*perspective: 60px;*/
|
||||
|
||||
}
|
||||
|
||||
@keyframes cursor-blink
|
||||
{
|
||||
0%{ opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
100%{ opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes cursor-3d
|
||||
{
|
||||
0%{ transform: rotateX(0deg) rotateX(Ydeg); }
|
||||
50% { transform: rotateX(180deg) rotateY(360deg); opacity: 0.1; }
|
||||
100%{ transform: rotateX(360deg) rotateY(720deg); }
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content
|
||||
{
|
||||
word-wrap: break-word;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content-container
|
||||
{
|
||||
overflow-y: hidden;
|
||||
display: block;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content-container .gradient
|
||||
{
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: linear-gradient(to top, rgba(87,87,87,0) 0%,rgba(87,87,87,1) 100%);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
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%;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content .part .subpart
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@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;
|
||||
background-color: Yellow;
|
||||
display: block;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
height: 100%;
|
||||
width: 0px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
opacity: 0.7;
|
||||
border-style: solid;
|
||||
border-width: 0px;
|
||||
border-color: Red;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ var audio_compression="none";
|
|||
var waterfall_setup_done=0;
|
||||
var waterfall_queue = [];
|
||||
var waterfall_timer;
|
||||
var secondary_fft_size;
|
||||
|
||||
/*function fade(something,from,to,time_ms,fps)
|
||||
{
|
||||
|
@ -72,7 +73,8 @@ var rx_photo_state=1;
|
|||
|
||||
function e(what) { return document.getElementById(what); }
|
||||
|
||||
ios = /iPad|iPod|iPhone/.test(navigator.userAgent);
|
||||
ios = /iPad|iPod|iPhone|Chrome/.test(navigator.userAgent);
|
||||
is_chrome = /Chrome/.test(navigator.userAgent);
|
||||
//alert("ios="+ios.toString()+" "+navigator.userAgent);
|
||||
|
||||
function init_rx_photo()
|
||||
|
@ -183,8 +185,8 @@ function waterfallColorsDefault()
|
|||
|
||||
function waterfallColorsAuto()
|
||||
{
|
||||
e("openwebrx-waterfall-color-min").value=(waterfall_measure_minmax_min-20).toString();
|
||||
e("openwebrx-waterfall-color-max").value=(waterfall_measure_minmax_max+30).toString();
|
||||
e("openwebrx-waterfall-color-min").value=(waterfall_measure_minmax_min-waterfall_auto_level_margin[0]).toString();
|
||||
e("openwebrx-waterfall-color-max").value=(waterfall_measure_minmax_max+waterfall_auto_level_margin[1]).toString();
|
||||
updateWaterfallColors(0);
|
||||
}
|
||||
|
||||
|
@ -559,6 +561,7 @@ function demodulator_default_analog(offset_frequency,subtype)
|
|||
|
||||
this.envelope.drag_end=function(x)
|
||||
{ //in this demodulator we've already changed values in the drag_move() function so we shouldn't do too much here.
|
||||
demodulator_buttons_update();
|
||||
to_return=this.dragged_range!=demodulator.draggable_ranges.none; //this part is required for cliking anywhere on the scale to set offset
|
||||
this.dragged_range=demodulator.draggable_ranges.none;
|
||||
return to_return;
|
||||
|
@ -575,6 +578,7 @@ function mkenvelopes(visible_range) //called from mkscale
|
|||
{
|
||||
demodulators[i].envelope.draw(visible_range);
|
||||
}
|
||||
if(demodulators.length) secondary_demod_waterfall_set_zoom(demodulators[0].low_cut, demodulators[0].high_cut);
|
||||
}
|
||||
|
||||
function demodulator_remove(which)
|
||||
|
@ -589,8 +593,17 @@ function demodulator_add(what)
|
|||
mkenvelopes(get_visible_freq_range());
|
||||
}
|
||||
|
||||
function demodulator_analog_replace(subtype)
|
||||
last_analog_demodulator_subtype = 'nfm';
|
||||
last_digital_demodulator_subtype = 'bpsk31';
|
||||
|
||||
function demodulator_analog_replace(subtype, for_digital)
|
||||
{ //this function should only exist until the multi-demodulator capability is added
|
||||
if(!(typeof for_digital !== "undefined" && for_digital && secondary_demod))
|
||||
{
|
||||
secondary_demod_close_window();
|
||||
secondary_demod_listbox_update();
|
||||
}
|
||||
last_analog_demodulator_subtype = subtype;
|
||||
var temp_offset=0;
|
||||
if(demodulators.length)
|
||||
{
|
||||
|
@ -598,6 +611,7 @@ function demodulator_analog_replace(subtype)
|
|||
demodulator_remove(0);
|
||||
}
|
||||
demodulator_add(new demodulator_default_analog(temp_offset,subtype));
|
||||
demodulator_buttons_update();
|
||||
}
|
||||
|
||||
function demodulator_set_offset_frequency(which,to_what)
|
||||
|
@ -1028,7 +1042,7 @@ function canvas_mousewheel(evt)
|
|||
|
||||
|
||||
zoom_max_level_hps=33; //Hz/pixel
|
||||
zoom_levels_count=5;
|
||||
zoom_levels_count=14;
|
||||
|
||||
function get_zoom_coeff_from_hps(hps)
|
||||
{
|
||||
|
@ -1050,8 +1064,10 @@ function mkzoomlevels()
|
|||
zoom_levels=[1];
|
||||
maxc=get_zoom_coeff_from_hps(zoom_max_level_hps);
|
||||
if(maxc<1) return;
|
||||
// logarithmic interpolation
|
||||
zoom_ratio = Math.pow(maxc, 1/zoom_levels_count);
|
||||
for(i=1;i<zoom_levels_count;i++)
|
||||
zoom_levels.push(1+(maxc-1)*(i/(zoom_levels_count-1)));
|
||||
zoom_levels.push(Math.pow(zoom_ratio, i));
|
||||
}
|
||||
|
||||
function zoom_step(out, where, onscreen)
|
||||
|
@ -1063,7 +1079,7 @@ function zoom_step(out, where, onscreen)
|
|||
zoom_center_rel=canvas_get_freq_offset(where);
|
||||
//console.log("zoom_step || zlevel: "+zoom_level.toString()+" zlevel_val: "+zoom_levels[zoom_level].toString()+" zoom_center_rel: "+zoom_center_rel.toString());
|
||||
zoom_center_where=onscreen;
|
||||
console.log(zoom_center_where, zoom_center_rel, where);
|
||||
//console.log(zoom_center_where, zoom_center_rel, where);
|
||||
resize_canvases(true);
|
||||
mkscale();
|
||||
}
|
||||
|
@ -1095,9 +1111,17 @@ function zoom_calc()
|
|||
function resize_waterfall_container(check_init)
|
||||
{
|
||||
if(check_init&&!waterfall_setup_done) return;
|
||||
canvas_container.style.height=(window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px";
|
||||
}
|
||||
var numHeight;
|
||||
mathbox_container.style.height=canvas_container.style.height=(numHeight=window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px";
|
||||
if(mathbox)
|
||||
{
|
||||
//mathbox.three.camera.aspect = document.body.offsetWidth / numHeight;
|
||||
//mathbox.three.camera.updateProjectionMatrix();
|
||||
mathbox.three.renderer.setSize(document.body.offsetWidth, numHeight);
|
||||
console.log(document.body.offsetWidth, numHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
audio_server_output_rate=11025;
|
||||
audio_client_resampling_factor=4;
|
||||
|
@ -1134,14 +1158,15 @@ function on_ws_recv(evt)
|
|||
if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; }
|
||||
//
|
||||
debug_ws_data_received+=evt.data.byteLength/1000;
|
||||
firstChars=getFirstChars(evt.data,3);
|
||||
if(firstChars=="CLI")
|
||||
first4Chars=getFirstChars(evt.data,4);
|
||||
first3Chars=first4Chars.slice(0,3);
|
||||
if(first3Chars=="CLI")
|
||||
{
|
||||
var stringData=arrayBufferToString(evt.data);
|
||||
if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Acknowledged WebSocket connection: "+stringData);
|
||||
if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Server acknowledged WebSocket connection.");
|
||||
|
||||
}
|
||||
if(firstChars=="AUD")
|
||||
if(first3Chars=="AUD")
|
||||
{
|
||||
var audio_data;
|
||||
if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4)
|
||||
|
@ -1149,11 +1174,12 @@ function on_ws_recv(evt)
|
|||
audio_prepare(audio_data);
|
||||
audio_buffer_current_size_debug+=audio_data.length;
|
||||
audio_buffer_all_size_debug+=audio_data.length;
|
||||
if(!ios && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init()
|
||||
if(!(ios||is_chrome) && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init()
|
||||
}
|
||||
else if(firstChars=="FFT")
|
||||
else if(first3Chars=="FFT")
|
||||
{
|
||||
//alert("Yupee! Doing FFT");
|
||||
//if(first4Chars=="FFTS") console.log("FFTS");
|
||||
if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4));
|
||||
else if(fft_compression="adpcm")
|
||||
{
|
||||
|
@ -1162,9 +1188,17 @@ function on_ws_recv(evt)
|
|||
var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4));
|
||||
var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N);
|
||||
for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100;
|
||||
waterfall_add_queue(waterfall_f32);
|
||||
if(first4Chars=="FFTS") secondary_demod_waterfall_add_queue(waterfall_f32); //TODO digimodes
|
||||
else waterfall_add_queue(waterfall_f32);
|
||||
}
|
||||
} else if(firstChars=="MSG")
|
||||
}
|
||||
else if(first3Chars=="DAT")
|
||||
{
|
||||
//secondary_demod_push_binary_data(new Uint8Array(evt.data,4));
|
||||
secondary_demod_push_data(arrayBufferToString(evt.data).substring(4));
|
||||
//console.log("DAT");
|
||||
}
|
||||
else if(first3Chars=="MSG")
|
||||
{
|
||||
/*try
|
||||
{*/
|
||||
|
@ -1188,6 +1222,18 @@ function on_ws_recv(evt)
|
|||
case "fft_size":
|
||||
fft_size=parseInt(param[1]);
|
||||
break;
|
||||
case "secondary_fft_size":
|
||||
secondary_fft_size=parseInt(param[1]);
|
||||
break;
|
||||
case "secondary_setup":
|
||||
secondary_demod_init_canvases();
|
||||
break;
|
||||
case "if_samp_rate":
|
||||
if_samp_rate=parseInt(param[1]);
|
||||
break;
|
||||
case "secondary_bw":
|
||||
secondary_bw=parseFloat(param[1]);
|
||||
break;
|
||||
case "fft_fps":
|
||||
fft_fps=parseInt(param[1]);
|
||||
break;
|
||||
|
@ -1336,8 +1382,10 @@ function divlog(what, is_error)
|
|||
if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present
|
||||
}
|
||||
e("openwebrx-debugdiv").innerHTML+=what+"<br />";
|
||||
var wls=e("openwebrx-log-scroll");
|
||||
wls.scrollTop=wls.scrollHeight; //scroll to bottom
|
||||
//var wls=e("openwebrx-log-scroll");
|
||||
//wls.scrollTop=wls.scrollHeight; //scroll to bottom
|
||||
$(".nano").nanoScroller();
|
||||
$(".nano").nanoScroller({ scroll: 'bottom' });
|
||||
}
|
||||
|
||||
var audio_context;
|
||||
|
@ -1585,19 +1633,27 @@ function webrx_set_param(what, value)
|
|||
ws.send("SET "+what+"="+value.toString());
|
||||
}
|
||||
|
||||
var starting_mute = false;
|
||||
|
||||
function parsehash()
|
||||
{
|
||||
if(h=window.location.hash)
|
||||
{
|
||||
h.substring(1).split(",").forEach(function(x){
|
||||
harr=x.split("=");
|
||||
console.log(harr);
|
||||
if(harr[0]=="mod") starting_mod = harr[1];
|
||||
if(harr[0]=="sql") { e("openwebrx-panel-squelch").value=harr[1]; updateSquelch(); }
|
||||
if(harr[0]=="freq") {
|
||||
console.log(parseInt(harr[1]));
|
||||
console.log(center_freq);
|
||||
starting_offset_frequency = parseInt(harr[1])-center_freq;
|
||||
//console.log(harr);
|
||||
if(harr[0]=="mute") toggleMute();
|
||||
else if(harr[0]=="mod") starting_mod = harr[1];
|
||||
else if(harr[0]=="sql")
|
||||
{
|
||||
e("openwebrx-panel-squelch").value=harr[1];
|
||||
updateSquelch();
|
||||
}
|
||||
else if(harr[0]=="freq")
|
||||
{
|
||||
console.log(parseInt(harr[1]));
|
||||
console.log(center_freq);
|
||||
starting_offset_frequency = parseInt(harr[1])-center_freq;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1638,6 +1694,9 @@ function audio_preinit()
|
|||
|
||||
function audio_init()
|
||||
{
|
||||
if(is_chrome) audio_context.resume()
|
||||
if(starting_mute) toggleMute();
|
||||
|
||||
if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor...
|
||||
|
||||
audio_debug_time_start=(new Date()).getTime();
|
||||
|
@ -1682,6 +1741,7 @@ function audio_init()
|
|||
//window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200)
|
||||
}
|
||||
},2000);
|
||||
|
||||
}
|
||||
|
||||
function on_ws_closed()
|
||||
|
@ -1722,17 +1782,18 @@ function open_websocket()
|
|||
ws.onerror = on_ws_error;
|
||||
}
|
||||
|
||||
function waterfall_mkcolor(db_value)
|
||||
function waterfall_mkcolor(db_value, waterfall_colors_arg)
|
||||
{
|
||||
if(typeof waterfall_colors_arg === 'undefined') waterfall_colors_arg = waterfall_colors;
|
||||
if(db_value<waterfall_min_level) db_value=waterfall_min_level;
|
||||
if(db_value>waterfall_max_level) db_value=waterfall_max_level;
|
||||
full_scale=waterfall_max_level-waterfall_min_level;
|
||||
relative_value=db_value-waterfall_min_level;
|
||||
value_percent=relative_value/full_scale;
|
||||
percent_for_one_color=1/(waterfall_colors.length-1);
|
||||
percent_for_one_color=1/(waterfall_colors_arg.length-1);
|
||||
index=Math.floor(value_percent/percent_for_one_color);
|
||||
remain=(value_percent-percent_for_one_color*index)/percent_for_one_color;
|
||||
return color_between(waterfall_colors[index+1],waterfall_colors[index],remain);
|
||||
return color_between(waterfall_colors_arg[index+1],waterfall_colors_arg[index],remain);
|
||||
}
|
||||
|
||||
function color_between(first, second, percent)
|
||||
|
@ -1755,7 +1816,7 @@ var canvas_phantom;
|
|||
|
||||
function add_canvas()
|
||||
{
|
||||
new_canvas = document.createElement("canvas");
|
||||
var new_canvas = document.createElement("canvas");
|
||||
new_canvas.width=fft_size;
|
||||
new_canvas.height=canvas_default_height;
|
||||
canvas_actual_line=canvas_default_height-1;
|
||||
|
@ -1780,9 +1841,11 @@ function add_canvas()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function init_canvas_container()
|
||||
{
|
||||
canvas_container=e("webrx-canvas-container");
|
||||
mathbox_container=e("openwebrx-mathbox-container");
|
||||
canvas_container.addEventListener("mouseout",canvas_container_mouseout, false);
|
||||
//window.addEventListener("mouseout",window_mouseout,false);
|
||||
//document.body.addEventListener("mouseup",body_mouseup,false);
|
||||
|
@ -1839,7 +1902,7 @@ function resize_canvases(zoom)
|
|||
function waterfall_init()
|
||||
{
|
||||
init_canvas_container();
|
||||
waterfall_timer = window.setInterval(waterfall_dequeue,900/fft_fps);
|
||||
waterfall_timer = window.setInterval(()=>{waterfall_dequeue(); secondary_demod_waterfall_dequeue();},900/fft_fps);
|
||||
resize_waterfall_container(false); /* then */ resize_canvases();
|
||||
scale_setup();
|
||||
mkzoomlevels();
|
||||
|
@ -1848,6 +1911,42 @@ function waterfall_init()
|
|||
|
||||
var waterfall_dont_scale=0;
|
||||
|
||||
var mathbox_shift = function()
|
||||
{
|
||||
if(mathbox_data_current_depth < mathbox_data_max_depth) mathbox_data_current_depth++;
|
||||
if(mathbox_data_index+1>=mathbox_data_max_depth) mathbox_data_index = 0;
|
||||
else mathbox_data_index++;
|
||||
mathbox_data_global_index++;
|
||||
}
|
||||
|
||||
var mathbox_clear_data = function()
|
||||
{
|
||||
mathbox_data_index = 50;
|
||||
mathbox_data_current_depth = 0;
|
||||
}
|
||||
|
||||
//var mathbox_get_data_line = function(x) //x counts from 0 to mathbox_data_current_depth
|
||||
//{
|
||||
// return (mathbox_data_max_depth + mathbox_data_index - mathbox_data_current_depth + x - 1) % mathbox_data_max_depth;
|
||||
//}
|
||||
//
|
||||
//var mathbox_data_index_valid = function(x) //x counts from 0 to mathbox_data_current_depth
|
||||
//{
|
||||
// return x<mathbox_data_current_depth;
|
||||
//}
|
||||
|
||||
var mathbox_get_data_line = function(x)
|
||||
{
|
||||
return (mathbox_data_max_depth + mathbox_data_index + x - 1) % mathbox_data_max_depth;
|
||||
}
|
||||
|
||||
var mathbox_data_index_valid = function(x)
|
||||
{
|
||||
return x>mathbox_data_max_depth-mathbox_data_current_depth;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function waterfall_add(data)
|
||||
{
|
||||
if(!waterfall_setup_done) return;
|
||||
|
@ -1924,6 +2023,14 @@ function waterfall_add(data)
|
|||
waterfall_image.data[base+x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
|
||||
}*/
|
||||
|
||||
if(mathbox_mode==MATHBOX_MODES.WATERFALL)
|
||||
{
|
||||
//Handle mathbox
|
||||
for(var i=0;i<fft_size;i++) mathbox_data[i+mathbox_data_index*fft_size]=data[i];
|
||||
mathbox_shift();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Add line to waterfall image
|
||||
oneline_image = canvas_context.createImageData(w,1);
|
||||
for(x=0;x<w;x++)
|
||||
|
@ -1933,13 +2040,13 @@ function waterfall_add(data)
|
|||
oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
|
||||
}
|
||||
|
||||
|
||||
//Draw image
|
||||
canvas_context.putImageData(oneline_image, 0, canvas_actual_line--);
|
||||
shift_canvases();
|
||||
if(canvas_actual_line<0) add_canvas();
|
||||
}
|
||||
|
||||
|
||||
//divlog("Drawn FFT");
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1969,6 +2076,180 @@ function check_top_bar_congestion()
|
|||
|
||||
}
|
||||
|
||||
var MATHBOX_MODES =
|
||||
{
|
||||
UNINITIALIZED: 0,
|
||||
NONE: 1,
|
||||
WATERFALL: 2,
|
||||
CONSTELLATION: 3
|
||||
};
|
||||
var mathbox_mode = MATHBOX_MODES.UNINITIALIZED;
|
||||
var mathbox;
|
||||
var mathbox_element;
|
||||
|
||||
function mathbox_init()
|
||||
{
|
||||
//mathbox_waterfall_history_length is defined in the config
|
||||
mathbox_data_max_depth = fft_fps * mathbox_waterfall_history_length; //how many lines can the buffer store
|
||||
mathbox_data_current_depth = 0; //how many lines are in the buffer currently
|
||||
mathbox_data_index = 0; //the index of the last empty line / the line to be overwritten
|
||||
mathbox_data = new Float32Array(fft_size * mathbox_data_max_depth);
|
||||
mathbox_data_global_index = 0;
|
||||
mathbox_correction_for_z = 0;
|
||||
|
||||
mathbox = mathBox({
|
||||
plugins: ['core', 'controls', 'cursor', 'stats'],
|
||||
controls: { klass: THREE.OrbitControls },
|
||||
});
|
||||
three = mathbox.three;
|
||||
if(typeof three == "undefined") divlog("3D waterfall cannot be initialized because WebGL is not supported in your browser.", true);
|
||||
|
||||
three.renderer.setClearColor(new THREE.Color(0x808080), 1.0);
|
||||
mathbox_container.appendChild((mathbox_element=three.renderer.domElement));
|
||||
view = mathbox
|
||||
.set({
|
||||
scale: 1080,
|
||||
focus: 3,
|
||||
})
|
||||
.camera({
|
||||
proxy: true,
|
||||
position: [-2, 1, 3],
|
||||
})
|
||||
.cartesian({
|
||||
range: [[-1, 1], [0, 1], [0, 1]],
|
||||
scale: [2, 2/3, 1],
|
||||
});
|
||||
|
||||
view.axis({
|
||||
axis: 1,
|
||||
width: 3,
|
||||
color: "#fff",
|
||||
});
|
||||
view.axis({
|
||||
axis: 2,
|
||||
width: 3,
|
||||
color: "#fff",
|
||||
//offset: [0, 0, 0],
|
||||
});
|
||||
view.axis({
|
||||
axis: 3,
|
||||
width: 3,
|
||||
color: "#fff",
|
||||
});
|
||||
|
||||
view.grid({
|
||||
width: 2,
|
||||
opacity: 0.5,
|
||||
axes: [1, 3],
|
||||
zOrder: 1,
|
||||
color: "#fff",
|
||||
});
|
||||
|
||||
//var remap = function (v) { return Math.sqrt(.5 + .5 * v); };
|
||||
|
||||
|
||||
var remap = function(x,z,t)
|
||||
{
|
||||
var currentTimePos = mathbox_data_global_index/(fft_fps*1.0);
|
||||
var realZAdd = (-(t-currentTimePos)/mathbox_waterfall_history_length);
|
||||
var zAdd = realZAdd - mathbox_correction_for_z;
|
||||
if(zAdd<-0.2 || zAdd>0.2) { mathbox_correction_for_z = realZAdd; }
|
||||
|
||||
var xIndex = Math.trunc(((x+1)/2.0)*fft_size); //x: frequency
|
||||
var zIndex = Math.trunc(z*(mathbox_data_max_depth-1)); //z: time
|
||||
var realZIndex = mathbox_get_data_line(zIndex);
|
||||
if(!mathbox_data_index_valid(zIndex)) return {y: undefined, dBValue: undefined, zAdd: 0 };
|
||||
//if(realZIndex>=(mathbox_data_max_depth-1)) console.log("realZIndexundef", realZIndex, zIndex);
|
||||
var index = Math.trunc(xIndex + realZIndex * fft_size);
|
||||
/*if(mathbox_data[index]==undefined) console.log("Undef", index, mathbox_data.length, zIndex,
|
||||
realZIndex, mathbox_data_max_depth,
|
||||
mathbox_data_current_depth, mathbox_data_index);*/
|
||||
var dBValue = mathbox_data[index];
|
||||
//y=1;
|
||||
if(dBValue>waterfall_max_level) y = 1;
|
||||
else if(dBValue<waterfall_min_level) y = 0;
|
||||
else y = (dBValue-waterfall_min_level)/(waterfall_max_level-waterfall_min_level);
|
||||
mathbox_dbg = { dbv: dBValue, indexval: index, mbd: mathbox_data.length, yval: y };
|
||||
if(!y) y=0;
|
||||
return {y: y, dBValue: dBValue, zAdd: zAdd};
|
||||
}
|
||||
|
||||
var points = view.area({
|
||||
expr: function (emit, x, z, i, j, t) {
|
||||
var y;
|
||||
remapResult=remap(x,z,t);
|
||||
if((y=remapResult.y)==undefined) return;
|
||||
emit(x, y, z+remapResult.zAdd);
|
||||
},
|
||||
width: mathbox_waterfall_frequency_resolution,
|
||||
height: mathbox_data_max_depth - 1,
|
||||
channels: 3,
|
||||
axes: [1, 3],
|
||||
});
|
||||
|
||||
var colors = view.area({
|
||||
expr: function (emit, x, z, i, j, t) {
|
||||
var dBValue;
|
||||
if((dBValue=remap(x,z,t).dBValue)==undefined) return;
|
||||
var color=waterfall_mkcolor(dBValue, mathbox_waterfall_colors);
|
||||
var b = (color&0xff)/255.0;
|
||||
var g = ((color&0xff00)>>8)/255.0;
|
||||
var r = ((color&0xff0000)>>16)/255.0;
|
||||
emit(r, g, b, 1.0);
|
||||
},
|
||||
width: mathbox_waterfall_frequency_resolution,
|
||||
height: mathbox_data_max_depth - 1,
|
||||
channels: 4,
|
||||
axes: [1, 3],
|
||||
});
|
||||
|
||||
view.surface({
|
||||
shaded: true,
|
||||
points: '<<',
|
||||
colors: '<',
|
||||
color: 0xFFFFFF,
|
||||
});
|
||||
|
||||
view.surface({
|
||||
fill: false,
|
||||
lineX: false,
|
||||
lineY: false,
|
||||
points: '<<',
|
||||
colors: '<',
|
||||
color: 0xFFFFFF,
|
||||
width: 2,
|
||||
blending: 'add',
|
||||
opacity: .25,
|
||||
zBias: 5,
|
||||
});
|
||||
mathbox_mode = MATHBOX_MODES.NONE;
|
||||
|
||||
//mathbox_element.style.width="100%";
|
||||
//mathbox_element.style.height="100%";
|
||||
|
||||
}
|
||||
|
||||
function mathbox_toggle()
|
||||
{
|
||||
|
||||
if(mathbox_mode == MATHBOX_MODES.UNINITIALIZED) mathbox_init();
|
||||
mathbox_mode = (mathbox_mode == MATHBOX_MODES.NONE) ? MATHBOX_MODES.WATERFALL : MATHBOX_MODES.NONE;
|
||||
mathbox_container.style.display = (mathbox_mode == MATHBOX_MODES.WATERFALL) ? "block" : "none";
|
||||
mathbox_clear_data();
|
||||
waterfall_clear();
|
||||
}
|
||||
|
||||
function waterfall_clear()
|
||||
{
|
||||
while(canvases.length) //delete all canvases
|
||||
{
|
||||
var x=canvases.shift();
|
||||
x.parentNode.removeChild(x);
|
||||
delete x;
|
||||
}
|
||||
add_canvas();
|
||||
}
|
||||
|
||||
function openwebrx_resize()
|
||||
{
|
||||
resize_canvases();
|
||||
|
@ -1979,10 +2260,11 @@ function openwebrx_resize()
|
|||
|
||||
function openwebrx_init()
|
||||
{
|
||||
if(ios) e("openwebrx-big-grey").style.display="table-cell";
|
||||
if(ios||is_chrome) e("openwebrx-big-grey").style.display="table-cell";
|
||||
(opb=e("openwebrx-play-button-text")).style.marginTop=(window.innerHeight/2-opb.clientHeight/2).toString()+"px";
|
||||
init_rx_photo();
|
||||
open_websocket();
|
||||
secondary_demod_init();
|
||||
place_panels(first_show_panel);
|
||||
window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000);
|
||||
window.addEventListener("resize",openwebrx_resize);
|
||||
|
@ -2076,9 +2358,14 @@ function pop_bottommost_panel(from)
|
|||
return to_return;
|
||||
}
|
||||
|
||||
function toggle_panel(what)
|
||||
function toggle_panel(what, on)
|
||||
{
|
||||
var item=e(what);
|
||||
var item=e(what);
|
||||
if(typeof on !== "undefined")
|
||||
{
|
||||
if(item.openwebrxHidden && !on) return;
|
||||
if(!item.openwebrxHidden && on) return;
|
||||
}
|
||||
if(item.openwebrxDisableClick) return;
|
||||
item.style.transitionDuration="599ms";
|
||||
item.style.transitionDelay="0ms";
|
||||
|
@ -2167,6 +2454,7 @@ function place_panels(function_apply)
|
|||
p.style.visibility="visible";
|
||||
y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
|
||||
if(function_apply) function_apply(p);
|
||||
//console.log(p.id, y, p.openwebrxPanelTransparent);
|
||||
}
|
||||
y=hoffset;
|
||||
while(right_col.length>0)
|
||||
|
@ -2175,7 +2463,7 @@ function place_panels(function_apply)
|
|||
p.style.right=(e("webrx-canvas-container").offsetWidth-e("webrx-canvas-container").clientWidth).toString()+"px"; //get scrollbar width
|
||||
p.style.bottom=y.toString()+"px";
|
||||
p.style.visibility="visible";
|
||||
y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
|
||||
y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
|
||||
if(function_apply) function_apply(p);
|
||||
}
|
||||
}
|
||||
|
@ -2199,3 +2487,331 @@ function progressbar_set(obj,val,text,over)
|
|||
if(innerText==null) return;
|
||||
innerText.innerHTML=text;
|
||||
}
|
||||
|
||||
function demodulator_buttons_update()
|
||||
{
|
||||
$(".openwebrx-demodulator-button").removeClass("highlighted");
|
||||
if(secondary_demod) $("#openwebrx-button-dig").addClass("highlighted");
|
||||
else switch(demodulators[0].subtype)
|
||||
{
|
||||
case "nfm":
|
||||
$("#openwebrx-button-nfm").addClass("highlighted");
|
||||
break;
|
||||
case "am":
|
||||
$("#openwebrx-button-am").addClass("highlighted");
|
||||
break;
|
||||
case "lsb":
|
||||
case "usb":
|
||||
case "cw":
|
||||
if(demodulators[0].high_cut-demodulators[0].low_cut<300)
|
||||
$("#openwebrx-button-cw").addClass("highlighted");
|
||||
else
|
||||
{
|
||||
if(demodulators[0].high_cut<0)
|
||||
$("#openwebrx-button-lsb").addClass("highlighted");
|
||||
else if(demodulators[0].low_cut>0)
|
||||
$("#openwebrx-button-usb").addClass("highlighted");
|
||||
else $("#openwebrx-button-lsb, #openwebrx-button-usb").addClass("highlighted");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
function demodulator_analog_replace_last() { demodulator_analog_replace(last_analog_demodulator_subtype); }
|
||||
|
||||
/*
|
||||
_____ _ _ _
|
||||
| __ \(_) (_) | |
|
||||
| | | |_ __ _ _ _ __ ___ ___ __| | ___ ___
|
||||
| | | | |/ _` | | '_ ` _ \ / _ \ / _` |/ _ \/ __|
|
||||
| |__| | | (_| | | | | | | | (_) | (_| | __/\__ \
|
||||
|_____/|_|\__, |_|_| |_| |_|\___/ \__,_|\___||___/
|
||||
__/ |
|
||||
|___/
|
||||
*/
|
||||
|
||||
secondary_demod = false;
|
||||
secondary_demod_offset_freq = 0;
|
||||
secondary_demod_waterfall_queue = [];
|
||||
|
||||
function demodulator_digital_replace_last()
|
||||
{
|
||||
demodulator_digital_replace(last_digital_demodulator_subtype);
|
||||
secondary_demod_listbox_update();
|
||||
}
|
||||
function demodulator_digital_replace(subtype)
|
||||
{
|
||||
switch(subtype)
|
||||
{
|
||||
case "bpsk31":
|
||||
case "rtty":
|
||||
secondary_demod_start(subtype);
|
||||
demodulator_analog_replace('usb', true);
|
||||
demodulator_buttons_update();
|
||||
break;
|
||||
}
|
||||
toggle_panel("openwebrx-panel-digimodes", true);
|
||||
}
|
||||
|
||||
function secondary_demod_create_canvas()
|
||||
{
|
||||
var new_canvas = document.createElement("canvas");
|
||||
new_canvas.width=secondary_fft_size;
|
||||
new_canvas.height=$(secondary_demod_canvas_container).height();
|
||||
new_canvas.style.width=$(secondary_demod_canvas_container).width()+"px";
|
||||
new_canvas.style.height=$(secondary_demod_canvas_container).height()+"px";
|
||||
console.log(new_canvas.width, new_canvas.height, new_canvas.style.width, new_canvas.style.height);
|
||||
secondary_demod_current_canvas_actual_line=new_canvas.height-1;
|
||||
$(secondary_demod_canvas_container).children().last().before(new_canvas);
|
||||
return new_canvas;
|
||||
}
|
||||
|
||||
function secondary_demod_remove_canvases()
|
||||
{
|
||||
$(secondary_demod_canvas_container).children("canvas").remove();
|
||||
}
|
||||
|
||||
function secondary_demod_init_canvases()
|
||||
{
|
||||
secondary_demod_remove_canvases();
|
||||
secondary_demod_canvases=[];
|
||||
secondary_demod_canvases.push(secondary_demod_create_canvas());
|
||||
secondary_demod_canvases.push(secondary_demod_create_canvas());
|
||||
secondary_demod_canvases[0].openwebrx_top=-$(secondary_demod_canvas_container).height();
|
||||
secondary_demod_canvases[1].openwebrx_top=0;
|
||||
secondary_demod_canvases_update_top();
|
||||
secondary_demod_current_canvas_context = secondary_demod_canvases[0].getContext("2d");
|
||||
secondary_demod_current_canvas_actual_line=$(secondary_demod_canvas_container).height()-1;
|
||||
secondary_demod_current_canvas_index=0;
|
||||
secondary_demod_canvases_initialized=true;
|
||||
//secondary_demod_update_channel_freq_from_event();
|
||||
mkscale(); //so that the secondary waterfall zoom level will be initialized
|
||||
}
|
||||
|
||||
function secondary_demod_canvases_update_top()
|
||||
{
|
||||
for(var i=0;i<2;i++) secondary_demod_canvases[i].style.top=secondary_demod_canvases[i].openwebrx_top+"px";
|
||||
}
|
||||
|
||||
function secondary_demod_swap_canvases()
|
||||
{
|
||||
console.log("swap");
|
||||
secondary_demod_canvases[0+!secondary_demod_current_canvas_index].openwebrx_top-=$(secondary_demod_canvas_container).height()*2;
|
||||
secondary_demod_current_canvas_index=0+!secondary_demod_current_canvas_index;
|
||||
secondary_demod_current_canvas_context = secondary_demod_canvases[secondary_demod_current_canvas_index].getContext("2d");
|
||||
secondary_demod_current_canvas_actual_line=$(secondary_demod_canvas_container).height()-1;
|
||||
}
|
||||
|
||||
function secondary_demod_init()
|
||||
{
|
||||
$("#openwebrx-panel-digimodes")[0].openwebrxHidden = true;
|
||||
secondary_demod_canvas_container = $("#openwebrx-digimode-canvas-container")[0];
|
||||
$(secondary_demod_canvas_container)
|
||||
.mousemove(secondary_demod_canvas_container_mousemove)
|
||||
.mouseup(secondary_demod_canvas_container_mouseup)
|
||||
.mousedown(secondary_demod_canvas_container_mousedown)
|
||||
.mouseenter(secondary_demod_canvas_container_mousein)
|
||||
.mouseleave(secondary_demod_canvas_container_mouseout);
|
||||
}
|
||||
|
||||
function secondary_demod_start(subtype)
|
||||
{
|
||||
secondary_demod_canvases_initialized = false;
|
||||
ws.send("SET secondary_mod="+subtype);
|
||||
secondary_demod = subtype;
|
||||
}
|
||||
|
||||
function secondary_demod_set()
|
||||
{
|
||||
ws.send("SET secondary_offset_freq="+secondary_demod_offset_freq.toString());
|
||||
}
|
||||
|
||||
function secondary_demod_stop()
|
||||
{
|
||||
ws.send("SET secondary_mod=off");
|
||||
secondary_demod = false;
|
||||
secondary_demod_waterfall_queue = [];
|
||||
}
|
||||
|
||||
function secondary_demod_waterfall_add_queue(x)
|
||||
{
|
||||
secondary_demod_waterfall_queue.push(x);
|
||||
}
|
||||
|
||||
function secondary_demod_push_binary_data(x)
|
||||
{
|
||||
secondary_demod_push_data(Array.from(x).map( y => (y)?"1":"0" ).join(""));
|
||||
}
|
||||
|
||||
function secondary_demod_push_data(x)
|
||||
{
|
||||
x=Array.from(x).map((y)=>{
|
||||
var c=y.charCodeAt(0);
|
||||
if(y=="\r") return " ";
|
||||
if(y=="\n") return " ";
|
||||
//if(y=="\n") return "<br />";
|
||||
if(c<32||c>126) return "";
|
||||
if(y=="&") return "&";
|
||||
if(y=="<") return "<";
|
||||
if(y==">") return ">";
|
||||
if(y==" ") return " ";
|
||||
return y;
|
||||
}).join("");
|
||||
$("#openwebrx-cursor-blink").before("<span class=\"part\"><span class=\"subpart\">"+x+"</span></span>");
|
||||
}
|
||||
|
||||
function secondary_demod_data_clear()
|
||||
{
|
||||
$("#openwebrx-cursor-blink").prevAll().remove();
|
||||
}
|
||||
|
||||
function secondary_demod_close_window()
|
||||
{
|
||||
secondary_demod_stop();
|
||||
toggle_panel("openwebrx-panel-digimodes", false);
|
||||
}
|
||||
|
||||
secondary_demod_fft_offset_db=30; //need to calculate that later
|
||||
|
||||
function secondary_demod_waterfall_add(data)
|
||||
{
|
||||
if(!secondary_demod) return;
|
||||
var w=secondary_fft_size;
|
||||
|
||||
//Add line to waterfall image
|
||||
var oneline_image = secondary_demod_current_canvas_context.createImageData(w,1);
|
||||
for(x=0;x<w;x++)
|
||||
{
|
||||
var color=waterfall_mkcolor(data[x]+secondary_demod_fft_offset_db);
|
||||
for(i=0;i<4;i++) oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
|
||||
}
|
||||
|
||||
//Draw image
|
||||
secondary_demod_current_canvas_context.putImageData(oneline_image, 0, secondary_demod_current_canvas_actual_line--);
|
||||
secondary_demod_canvases.map((x)=>{x.openwebrx_top += 1;});
|
||||
secondary_demod_canvases_update_top();
|
||||
if(secondary_demod_current_canvas_actual_line<0) secondary_demod_swap_canvases();
|
||||
}
|
||||
|
||||
var secondary_demod_canvases_initialized = false;
|
||||
|
||||
function secondary_demod_waterfall_dequeue()
|
||||
{
|
||||
if(!secondary_demod || !secondary_demod_canvases_initialized) return;
|
||||
if(secondary_demod_waterfall_queue.length) secondary_demod_waterfall_add(secondary_demod_waterfall_queue.shift());
|
||||
if(secondary_demod_waterfall_queue.length>Math.max(fft_fps/2,20)) //in case of fft overflow
|
||||
{
|
||||
console.log("secondary waterfall overflow, queue length:", secondary_demod_waterfall_queue.length);
|
||||
while(secondary_demod_waterfall_queue.length) secondary_demod_waterfall_add(secondary_demod_waterfall_queue.shift());
|
||||
}
|
||||
}
|
||||
|
||||
secondary_demod_listbox_updating = false;
|
||||
function secondary_demod_listbox_changed()
|
||||
{
|
||||
if(secondary_demod_listbox_updating) return;
|
||||
switch ($("#openwebrx-secondary-demod-listbox")[0].value)
|
||||
{
|
||||
case "none":
|
||||
demodulator_analog_replace_last();
|
||||
break;
|
||||
case "bpsk31":
|
||||
demodulator_digital_replace('bpsk31');
|
||||
break;
|
||||
case "rtty":
|
||||
demodulator_digital_replace('rtty');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function secondary_demod_listbox_update()
|
||||
{
|
||||
secondary_demod_listbox_updating = true;
|
||||
$("#openwebrx-secondary-demod-listbox").val((secondary_demod)?secondary_demod:"none");
|
||||
console.log("update");
|
||||
secondary_demod_listbox_updating = false;
|
||||
}
|
||||
|
||||
secondary_demod_channel_freq=1000;
|
||||
function secondary_demod_update_marker()
|
||||
{
|
||||
var width = Math.max( (secondary_bw / (if_samp_rate/2)) * secondary_demod_canvas_width, 5);
|
||||
var center_at = (secondary_demod_channel_freq / (if_samp_rate/2)) * secondary_demod_canvas_width + secondary_demod_canvas_left;
|
||||
var left = center_at-width/2;
|
||||
//console.log("sdum", width, left);
|
||||
$("#openwebrx-digimode-select-channel").width(width).css("left",left+"px")
|
||||
}
|
||||
|
||||
secondary_demod_waiting_for_set = false;
|
||||
function secondary_demod_update_channel_freq_from_event(evt)
|
||||
{
|
||||
if(typeof evt !== "undefined")
|
||||
{
|
||||
var relativeX=(evt.offsetX)?evt.offsetX:evt.layerX;
|
||||
secondary_demod_channel_freq=secondary_demod_low_cut +
|
||||
(relativeX/$(secondary_demod_canvas_container).width()) * (secondary_demod_high_cut-secondary_demod_low_cut);
|
||||
}
|
||||
//console.log("toset:", secondary_demod_channel_freq);
|
||||
if(!secondary_demod_waiting_for_set)
|
||||
{
|
||||
secondary_demod_waiting_for_set = true;
|
||||
window.setTimeout(()=>{
|
||||
ws.send("SET secondary_offset_freq="+Math.floor(secondary_demod_channel_freq));
|
||||
//console.log("doneset:", secondary_demod_channel_freq);
|
||||
secondary_demod_waiting_for_set = false;
|
||||
}, 50);
|
||||
}
|
||||
secondary_demod_update_marker();
|
||||
}
|
||||
|
||||
secondary_demod_mousedown=false;
|
||||
function secondary_demod_canvas_container_mousein()
|
||||
{
|
||||
$("#openwebrx-digimode-select-channel").css("opacity","0.7"); //.css("border-width", "1px");
|
||||
}
|
||||
|
||||
function secondary_demod_canvas_container_mouseout()
|
||||
{
|
||||
$("#openwebrx-digimode-select-channel").css("opacity","0");
|
||||
}
|
||||
|
||||
function secondary_demod_canvas_container_mousemove(evt)
|
||||
{
|
||||
if(secondary_demod_mousedown) secondary_demod_update_channel_freq_from_event(evt);
|
||||
}
|
||||
|
||||
function secondary_demod_canvas_container_mousedown(evt)
|
||||
{
|
||||
if(evt.which==1) secondary_demod_mousedown=true;
|
||||
}
|
||||
|
||||
function secondary_demod_canvas_container_mouseup(evt)
|
||||
{
|
||||
if(evt.which==1) secondary_demod_mousedown=false;
|
||||
secondary_demod_update_channel_freq_from_event(evt);
|
||||
}
|
||||
|
||||
|
||||
function secondary_demod_waterfall_set_zoom(low_cut, high_cut)
|
||||
{
|
||||
if(!secondary_demod || !secondary_demod_canvases_initialized) return;
|
||||
if(low_cut<0 && high_cut<0)
|
||||
{
|
||||
var hctmp = high_cut;
|
||||
var lctmp = low_cut;
|
||||
low_cut = -hctmp;
|
||||
low_cut = -lctmp;
|
||||
}
|
||||
else if(low_cut<0 && high_cut>0)
|
||||
{
|
||||
high_cut=Math.max(Math.abs(high_cut), Math.abs(low_cut));
|
||||
low_cut=0;
|
||||
}
|
||||
secondary_demod_low_cut = low_cut;
|
||||
secondary_demod_high_cut = high_cut;
|
||||
var shown_bw = high_cut-low_cut;
|
||||
secondary_demod_canvas_width = $(secondary_demod_canvas_container).width() * (if_samp_rate/2)/shown_bw;
|
||||
secondary_demod_canvas_left = -secondary_demod_canvas_width*(low_cut/(if_samp_rate/2));
|
||||
//console.log("setzoom", secondary_demod_canvas_width, secondary_demod_canvas_left, low_cut, high_cut);
|
||||
secondary_demod_canvases.map((x)=>{$(x).css("left",secondary_demod_canvas_left+"px").css("width",secondary_demod_canvas_width+"px");});
|
||||
secondary_demod_update_channel_freq_from_event();
|
||||
}
|
||||
|
|
1089
openwebrx.py
1089
openwebrx.py
File diff suppressed because it is too large
Load Diff
|
@ -1,294 +0,0 @@
|
|||
"""
|
||||
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>
|
||||
|
||||
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 time
|
||||
import os
|
||||
import code
|
||||
import signal
|
||||
import fcntl
|
||||
import select
|
||||
|
||||
class dsp_plugin:
|
||||
|
||||
def __init__(self):
|
||||
self.samp_rate = 250000
|
||||
self.output_rate = 11025 #this is default, and cannot be set at the moment
|
||||
self.fft_size = 1024
|
||||
self.fft_fps = 5
|
||||
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.audio_compression = "none"
|
||||
self.fft_compression = "none"
|
||||
self.demodulator = "nfm"
|
||||
self.name = "csdr"
|
||||
self.format_conversion = "csdr convert_u8_f"
|
||||
self.base_bufsize = 512
|
||||
self.nc_port = 4951
|
||||
self.csdr_dynamic_bufsize = False
|
||||
self.csdr_print_bufsizes = False
|
||||
self.csdr_through = False
|
||||
self.squelch_level = 0
|
||||
|
||||
def chain(self,which):
|
||||
if which in [ "dmr", "dstar", "nxdn", "ysf" ]:
|
||||
self.set_output_rate(48000)
|
||||
else:
|
||||
self.set_output_rate(11025)
|
||||
any_chain_base="ncat -v 127.0.0.1 {nc_port} | "
|
||||
if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
|
||||
if self.csdr_through: any_chain_base+="csdr through | "
|
||||
any_chain_base+=self.format_conversion+(" | " if self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
|
||||
if which == "fft":
|
||||
fft_chain_base = any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | csdr logpower_cf -70 | csdr fft_exchange_sides_ff {fft_size}"
|
||||
if self.fft_compression=="adpcm":
|
||||
return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}"
|
||||
else:
|
||||
return fft_chain_base
|
||||
chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | "
|
||||
chain_end = ""
|
||||
if self.audio_compression=="adpcm":
|
||||
chain_end = " | csdr encode_ima_adpcm_i16_u8"
|
||||
if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr convert_f_s16"+chain_end
|
||||
if which in [ "dstar", "nxdn" ]:
|
||||
c = chain_begin
|
||||
c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
|
||||
if which == "dstar":
|
||||
c += " | dsd -fd"
|
||||
elif which == "nxdn":
|
||||
c += " | dsd -fi"
|
||||
c += " -i - -o - -u 2 -g 10"
|
||||
c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --input-buffer 160 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 220"
|
||||
c += chain_end
|
||||
return c
|
||||
elif which == "dmr":
|
||||
c = chain_begin
|
||||
c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
|
||||
c += " | rrc_filter | gfsk_demodulator | dmr_decoder --fifo {meta_pipe} | mbe_synthesizer"
|
||||
c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256"
|
||||
c += chain_end
|
||||
return c
|
||||
elif which == "ysf":
|
||||
c = chain_begin
|
||||
c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
|
||||
c += " | rrc_filter | gfsk_demodulator | ysf_decoder --fifo {meta_pipe} | mbe_synthesizer -y"
|
||||
c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256"
|
||||
c += chain_end
|
||||
return c
|
||||
elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
||||
elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
||||
|
||||
def set_audio_compression(self,what):
|
||||
self.audio_compression = what
|
||||
|
||||
def set_fft_compression(self,what):
|
||||
self.fft_compression = what
|
||||
|
||||
def get_fft_bytes_to_read(self):
|
||||
if self.fft_compression=="none": return self.fft_size*4
|
||||
if self.fft_compression=="adpcm": return (self.fft_size/2)+(10/2)
|
||||
|
||||
def set_samp_rate(self,samp_rate):
|
||||
#to change this, restart is required
|
||||
self.samp_rate=samp_rate
|
||||
self.decimation=1
|
||||
while self.samp_rate/(self.decimation+1)>=self.output_rate:
|
||||
self.decimation+=1
|
||||
self.last_decimation=float(self.if_samp_rate())/self.output_rate
|
||||
|
||||
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 set_output_rate(self,output_rate):
|
||||
self.output_rate=output_rate
|
||||
self.set_samp_rate(self.samp_rate) #as it depends on output_rate
|
||||
|
||||
def set_demodulator(self,demodulator):
|
||||
#to change this, restart is required
|
||||
self.demodulator=demodulator
|
||||
|
||||
def get_demodulator(self):
|
||||
return self.demodulator
|
||||
|
||||
def set_fft_size(self,fft_size):
|
||||
#to change this, restart is required
|
||||
self.fft_size=fft_size
|
||||
|
||||
def set_fft_fps(self,fft_fps):
|
||||
#to change this, restart is required
|
||||
self.fft_fps=fft_fps
|
||||
|
||||
def fft_block_size(self):
|
||||
return self.samp_rate/self.fft_fps
|
||||
|
||||
def set_format_conversion(self,format_conversion):
|
||||
self.format_conversion=format_conversion
|
||||
|
||||
def set_offset_freq(self,offset_freq):
|
||||
self.offset_freq=offset_freq
|
||||
if self.running:
|
||||
self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate))
|
||||
self.shift_pipe_file.flush()
|
||||
|
||||
def set_bpf(self,low_cut,high_cut):
|
||||
self.low_cut=low_cut
|
||||
self.high_cut=high_cut
|
||||
if self.running:
|
||||
self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) )
|
||||
self.bpf_pipe_file.flush()
|
||||
|
||||
def get_bpf(self):
|
||||
return [self.low_cut, self.high_cut]
|
||||
|
||||
def set_squelch_level(self, squelch_level):
|
||||
self.squelch_level=squelch_level
|
||||
if self.running:
|
||||
self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) )
|
||||
self.squelch_pipe_file.flush()
|
||||
|
||||
def get_smeter_level(self):
|
||||
if self.running:
|
||||
line=self.smeter_pipe_file.readline()
|
||||
return float(line[:-1])
|
||||
|
||||
def get_metadata(self):
|
||||
if self.running and self.meta_pipe:
|
||||
return self.meta_pipe_file.readline()
|
||||
|
||||
def mkfifo(self,path):
|
||||
try:
|
||||
os.unlink(path)
|
||||
except:
|
||||
pass
|
||||
os.mkfifo(path)
|
||||
|
||||
def ddc_transition_bw(self):
|
||||
return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate))
|
||||
|
||||
def start(self):
|
||||
command_base=self.chain(self.demodulator)
|
||||
|
||||
#create control pipes for csdr
|
||||
pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
|
||||
self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = self.meta_pipe = None
|
||||
if "{bpf_pipe}" in command_base:
|
||||
self.bpf_pipe=pipe_base_path+"bpf"
|
||||
self.mkfifo(self.bpf_pipe)
|
||||
if "{shift_pipe}" in command_base:
|
||||
self.shift_pipe=pipe_base_path+"shift"
|
||||
self.mkfifo(self.shift_pipe)
|
||||
if "{squelch_pipe}" in command_base:
|
||||
self.squelch_pipe=pipe_base_path+"squelch"
|
||||
self.mkfifo(self.squelch_pipe)
|
||||
if "{smeter_pipe}" in command_base:
|
||||
self.smeter_pipe=pipe_base_path+"smeter"
|
||||
self.mkfifo(self.smeter_pipe)
|
||||
if "{meta_pipe}" in command_base:
|
||||
self.meta_pipe=pipe_base_path+"meta"
|
||||
self.mkfifo(self.meta_pipe)
|
||||
else:
|
||||
self.meta_pipe=None
|
||||
|
||||
#run the command
|
||||
command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \
|
||||
last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), \
|
||||
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, \
|
||||
squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe, meta_pipe=self.meta_pipe )
|
||||
|
||||
print "[openwebrx-dsp-plugin:csdr] Command =",command
|
||||
#code.interact(local=locals())
|
||||
my_env=os.environ.copy()
|
||||
if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
|
||||
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
|
||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
|
||||
self.running = True
|
||||
|
||||
#open control pipes for csdr and send initialization data
|
||||
if self.bpf_pipe != None:
|
||||
self.bpf_pipe_file=open(self.bpf_pipe,"w")
|
||||
self.set_bpf(self.low_cut,self.high_cut)
|
||||
if self.shift_pipe != None:
|
||||
self.shift_pipe_file=open(self.shift_pipe,"w")
|
||||
self.set_offset_freq(self.offset_freq)
|
||||
if self.squelch_pipe != None:
|
||||
self.squelch_pipe_file=open(self.squelch_pipe,"w")
|
||||
self.set_squelch_level(self.squelch_level)
|
||||
if self.smeter_pipe != None:
|
||||
self.smeter_pipe_file=open(self.smeter_pipe,"r")
|
||||
fcntl.fcntl(self.smeter_pipe_file, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
if self.meta_pipe != None:
|
||||
self.meta_pipe_file=open(self.meta_pipe,"r")
|
||||
fcntl.fcntl(self.meta_pipe_file, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
|
||||
def read(self,size):
|
||||
return self.process.stdout.read(size)
|
||||
|
||||
def read_async(self, size):
|
||||
if (select.select([self.process.stdout], [], [], 0)[0] != []):
|
||||
return self.process.stdout.read(size)
|
||||
else:
|
||||
return None
|
||||
|
||||
def stop(self):
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
#if(self.process.poll()!=None):return # returns None while subprocess is running
|
||||
#while(self.process.poll()==None):
|
||||
# #self.process.kill()
|
||||
# print "killproc",os.getpgid(self.process.pid),self.process.pid
|
||||
# os.killpg(self.process.pid, signal.SIGTERM)
|
||||
#
|
||||
# time.sleep(0.1)
|
||||
if self.bpf_pipe:
|
||||
try: os.unlink(self.bpf_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
|
||||
if self.shift_pipe:
|
||||
try: os.unlink(self.shift_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe
|
||||
if self.squelch_pipe:
|
||||
try: os.unlink(self.squelch_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe
|
||||
if self.smeter_pipe:
|
||||
try: os.unlink(self.smeter_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe
|
||||
if self.meta_pipe:
|
||||
try: os.unlink(self.meta_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.meta_pipe
|
||||
self.running = False
|
||||
|
||||
def restart(self):
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
del(self.process)
|
236
rxws.py
236
rxws.py
|
@ -1,9 +1,9 @@
|
|||
"""
|
||||
rxws: WebSocket methods implemented 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>
|
||||
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>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
|
@ -26,146 +26,146 @@ import select
|
|||
import code
|
||||
|
||||
class WebSocketException(Exception):
|
||||
pass
|
||||
pass
|
||||
|
||||
def handshake(myself):
|
||||
my_client_id=myself.path[4:]
|
||||
my_headers=myself.headers.items()
|
||||
my_header_keys=map(lambda x:x[0],my_headers)
|
||||
h_key_exists=lambda x:my_header_keys.count(x)
|
||||
h_value=lambda x:my_headers[my_header_keys.index(x)][1]
|
||||
#print "The Lambdas(tm)"
|
||||
#print h_key_exists("upgrade")
|
||||
#print h_value("upgrade")
|
||||
#print h_key_exists("sec-websocket-key")
|
||||
if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")):
|
||||
raise WebSocketException
|
||||
ws_key=h_value("sec-websocket-key")
|
||||
ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest())
|
||||
#A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')]
|
||||
myself.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n")
|
||||
my_client_id=myself.path[4:]
|
||||
my_headers=myself.headers.items()
|
||||
my_header_keys=map(lambda x:x[0],my_headers)
|
||||
h_key_exists=lambda x:my_header_keys.count(x)
|
||||
h_value=lambda x:my_headers[my_header_keys.index(x)][1]
|
||||
#print "The Lambdas(tm)"
|
||||
#print h_key_exists("upgrade")
|
||||
#print h_value("upgrade")
|
||||
#print h_key_exists("sec-websocket-key")
|
||||
if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")):
|
||||
raise WebSocketException
|
||||
ws_key=h_value("sec-websocket-key")
|
||||
ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest())
|
||||
#A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')]
|
||||
myself.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n")
|
||||
|
||||
def get_header(size):
|
||||
#this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php
|
||||
ws_first_byte=0b10000010 # FIN=1, OP=2
|
||||
if(size>125):
|
||||
ws_second_byte=126 # The following two bytes will indicate frame size
|
||||
extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP
|
||||
else:
|
||||
ws_second_byte=size
|
||||
#256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data]
|
||||
extended_size=""
|
||||
return chr(ws_first_byte)+chr(ws_second_byte)+extended_size
|
||||
#this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php
|
||||
ws_first_byte=0b10000010 # FIN=1, OP=2
|
||||
if(size>125):
|
||||
ws_second_byte=126 # The following two bytes will indicate frame size
|
||||
extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP
|
||||
else:
|
||||
ws_second_byte=size
|
||||
#256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data]
|
||||
extended_size=""
|
||||
return chr(ws_first_byte)+chr(ws_second_byte)+extended_size
|
||||
|
||||
def code_payload(data, masking_key=""):
|
||||
# both encode or decode
|
||||
if masking_key=="":
|
||||
key = (61, 84, 35, 6)
|
||||
else:
|
||||
key = [ord(i) for i in masking_key]
|
||||
encoded=""
|
||||
for i in range(0,len(data)):
|
||||
encoded+=chr(ord(data[i])^key[i%4])
|
||||
return encoded
|
||||
# both encode or decode
|
||||
if masking_key=="":
|
||||
key = (61, 84, 35, 6)
|
||||
else:
|
||||
key = [ord(i) for i in masking_key]
|
||||
encoded=""
|
||||
for i in range(0,len(data)):
|
||||
encoded+=chr(ord(data[i])^key[i%4])
|
||||
return encoded
|
||||
|
||||
def xxdg(data):
|
||||
output=""
|
||||
for i in range(0,len(data)/8):
|
||||
output+=xxd(data[i:i+8])
|
||||
if i%2: output+="\n"
|
||||
else: output+=" "
|
||||
return output
|
||||
|
||||
output=""
|
||||
for i in range(0,len(data)/8):
|
||||
output+=xxd(data[i:i+8])
|
||||
if i%2: output+="\n"
|
||||
else: output+=" "
|
||||
return output
|
||||
|
||||
|
||||
def xxd(data):
|
||||
#diagnostic purposes only
|
||||
output=""
|
||||
for d in data:
|
||||
output+=hex(ord(d))[2:].zfill(2)+" "
|
||||
return output
|
||||
#diagnostic purposes only
|
||||
output=""
|
||||
for d in data:
|
||||
output+=hex(ord(d))[2:].zfill(2)+" "
|
||||
return output
|
||||
|
||||
#for R/W the WebSocket, use recv/send
|
||||
#for reading the TCP socket, use readsock
|
||||
#for writing the TCP socket, use myself.wfile.write and flush
|
||||
|
||||
def readsock(myself,size,blocking):
|
||||
#http://thenestofheliopolis.blogspot.hu/2011/01/how-to-implement-non-blocking-two-way.html
|
||||
if blocking:
|
||||
return myself.rfile.read(size)
|
||||
else:
|
||||
poll = select.poll()
|
||||
poll.register(myself.rfile.fileno(), select.POLLIN or select.POLLPRI)
|
||||
fd = poll.poll(0) #timeout is 0
|
||||
if len(fd):
|
||||
f = fd[0]
|
||||
if f[1] > 0:
|
||||
return myself.rfile.read(size)
|
||||
return ""
|
||||
#http://thenestofheliopolis.blogspot.hu/2011/01/how-to-implement-non-blocking-two-way.html
|
||||
if blocking:
|
||||
return myself.rfile.read(size)
|
||||
else:
|
||||
poll = select.poll()
|
||||
poll.register(myself.rfile.fileno(), select.POLLIN or select.POLLPRI)
|
||||
fd = poll.poll(0) #timeout is 0
|
||||
if len(fd):
|
||||
f = fd[0]
|
||||
if f[1] > 0:
|
||||
return myself.rfile.read(size)
|
||||
return ""
|
||||
|
||||
|
||||
def recv(myself, blocking=False, debug=False):
|
||||
bufsize=70000
|
||||
#myself.connection.setblocking(blocking) #umm... we cannot do that with rfile
|
||||
if debug: print "ws_recv begin"
|
||||
try:
|
||||
data=readsock(myself,6,blocking)
|
||||
#print "rxws.recv bytes:",xxd(data)
|
||||
except:
|
||||
if debug: print "ws_recv error"
|
||||
return ""
|
||||
if debug: print "ws_recv recved"
|
||||
if(len(data)==0): return ""
|
||||
fin=ord(data[0])&128!=0
|
||||
is_text_frame=ord(data[0])&15==1
|
||||
length=ord(data[1])&0x7f
|
||||
data+=readsock(myself,length,blocking)
|
||||
#print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data)
|
||||
has_one_byte_length=length<125
|
||||
masked=ord(data[1])&0x80!=0
|
||||
#print "len=", length, len(data)-2
|
||||
#print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked)
|
||||
#print xxd(data)
|
||||
if fin and is_text_frame and has_one_byte_length:
|
||||
if masked:
|
||||
return code_payload(data[6:], data[2:6])
|
||||
else:
|
||||
return data[2:]
|
||||
bufsize=70000
|
||||
#myself.connection.setblocking(blocking) #umm... we cannot do that with rfile
|
||||
if debug: print "ws_recv begin"
|
||||
try:
|
||||
data=readsock(myself,6,blocking)
|
||||
#print "rxws.recv bytes:",xxd(data)
|
||||
except:
|
||||
if debug: print "ws_recv error"
|
||||
return ""
|
||||
if debug: print "ws_recv recved"
|
||||
if(len(data)==0): return ""
|
||||
fin=ord(data[0])&128!=0
|
||||
is_text_frame=ord(data[0])&15==1
|
||||
length=ord(data[1])&0x7f
|
||||
data+=readsock(myself,length,blocking)
|
||||
#print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data)
|
||||
has_one_byte_length=length<125
|
||||
masked=ord(data[1])&0x80!=0
|
||||
#print "len=", length, len(data)-2
|
||||
#print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked)
|
||||
#print xxd(data)
|
||||
if fin and is_text_frame and has_one_byte_length:
|
||||
if masked:
|
||||
return code_payload(data[6:], data[2:6])
|
||||
else:
|
||||
return data[2:]
|
||||
|
||||
#Useful links for ideas on WebSockets:
|
||||
# http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
|
||||
# https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_server
|
||||
# http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
# http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
|
||||
|
||||
def flush(myself):
|
||||
myself.wfile.flush()
|
||||
#or the socket, not the rfile:
|
||||
#lR,lW,lX = select.select([],[myself.connection,],[],60)
|
||||
|
||||
myself.wfile.flush()
|
||||
#or the socket, not the rfile:
|
||||
#lR,lW,lX = select.select([],[myself.connection,],[],60)
|
||||
|
||||
|
||||
def send(myself, data, begin_id="", debug=0):
|
||||
base_frame_size=35000 #could guess by MTU?
|
||||
debug=0
|
||||
#try:
|
||||
while True:
|
||||
counter=0
|
||||
from_end=len(data)-counter
|
||||
if from_end+len(begin_id)>base_frame_size:
|
||||
data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)]
|
||||
header=get_header(len(data_to_send))
|
||||
flush(myself)
|
||||
myself.wfile.write(header+data_to_send)
|
||||
flush(myself)
|
||||
if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header))
|
||||
else:
|
||||
data_to_send=begin_id+data[counter:]
|
||||
header=get_header(len(data_to_send))
|
||||
flush(myself)
|
||||
myself.wfile.write(header+data_to_send)
|
||||
flush(myself)
|
||||
if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header))
|
||||
#if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send)
|
||||
break
|
||||
counter+=base_frame_size-len(begin_id)
|
||||
#except:
|
||||
# pass
|
||||
base_frame_size=35000 #could guess by MTU?
|
||||
debug=0
|
||||
#try:
|
||||
while True:
|
||||
counter=0
|
||||
from_end=len(data)-counter
|
||||
if from_end+len(begin_id)>base_frame_size:
|
||||
data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)]
|
||||
header=get_header(len(data_to_send))
|
||||
flush(myself)
|
||||
myself.wfile.write(header+data_to_send)
|
||||
flush(myself)
|
||||
if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header))
|
||||
else:
|
||||
data_to_send=begin_id+data[counter:]
|
||||
header=get_header(len(data_to_send))
|
||||
flush(myself)
|
||||
myself.wfile.write(header+data_to_send)
|
||||
flush(myself)
|
||||
if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header))
|
||||
#if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send)
|
||||
break
|
||||
counter+=base_frame_size-len(begin_id)
|
||||
#except:
|
||||
# pass
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 511 KiB |
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 1.4 MiB |
51
sdrhu.py
51
sdrhu.py
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/python2
|
||||
"""
|
||||
|
||||
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>
|
||||
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>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
|
@ -23,29 +23,28 @@
|
|||
import config_webrx as cfg, time, subprocess
|
||||
|
||||
def run(continuously=True):
|
||||
if not cfg.sdrhu_key: return
|
||||
firsttime="(Your receiver is soon getting listed on sdr.hu!)"
|
||||
while True:
|
||||
cmd = "wget --timeout=15 -qO- http://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1"
|
||||
#print "[openwebrx-sdrhu]", cmd
|
||||
returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
|
||||
returned=returned[0]
|
||||
#print returned
|
||||
if "UPDATE:" in returned:
|
||||
retrytime_mins = 20
|
||||
value=returned.split("UPDATE:")[1].split("\n",1)[0]
|
||||
if value.startswith("SUCCESS"):
|
||||
print "[openwebrx-sdrhu] Update succeeded! "+firsttime
|
||||
firsttime=""
|
||||
else:
|
||||
print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value
|
||||
else:
|
||||
retrytime_mins = 2
|
||||
print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!"
|
||||
if not continuously: break
|
||||
time.sleep(60*retrytime_mins)
|
||||
if not cfg.sdrhu_key: return
|
||||
firsttime="(Your receiver is soon getting listed on sdr.hu!)"
|
||||
while True:
|
||||
cmd = "wget --timeout=15 -4qO- https://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1"
|
||||
print "[openwebrx-sdrhu]", cmd
|
||||
returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
|
||||
returned=returned[0]
|
||||
#print returned
|
||||
if "UPDATE:" in returned:
|
||||
retrytime_mins = 20
|
||||
value=returned.split("UPDATE:")[1].split("\n",1)[0]
|
||||
if value.startswith("SUCCESS"):
|
||||
print "[openwebrx-sdrhu] Update succeeded! "+firsttime
|
||||
firsttime=""
|
||||
else:
|
||||
print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value
|
||||
else:
|
||||
retrytime_mins = 2
|
||||
print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!"
|
||||
if not continuously: break
|
||||
time.sleep(60*retrytime_mins)
|
||||
|
||||
if __name__=="__main__":
|
||||
run(False)
|
||||
run(False)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue