merge recent openwebrx changes into our work

This commit is contained in:
Jakob Ketterl 2018-09-25 14:56:47 +02:00
parent ac6e001fd6
commit bf4c70dfef
22 changed files with 3817 additions and 1168 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
*.pyc *.pyc
*.swp
tags

View File

@ -1,21 +1,26 @@
OpenWebRX 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 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: 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, - filter passband can be set from GUI,
- waterfall display can be shifted back in time, - waterfall display can be shifted back in time,
- it extensively uses HTML5 features like WebSocket, Web Audio API, and &lt;canvas&gt;. - it extensively uses HTML5 features like WebSocket, Web Audio API, and &lt;canvas&gt;,
- it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28), - 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)** **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. - 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 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. - 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` - OpenWebRX now supports URLs like: `http://localhost:8073/#freq=145555000,mod=usb`
- UI improvements were made, thanks to John Seamons and Gnoxter. - 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 ## 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](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 ## Setup
@ -44,11 +56,6 @@ First you will need to install the dependencies:
- <a href="https://github.com/simonyiszk/csdr">libcsdr</a> - <a href="https://github.com/simonyiszk/csdr">libcsdr</a>
- <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</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: 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`. 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 &lt;randras@sdr.hu&gt;* *Andras Retzler, HA7ILM &lt;randras@sdr.hu&gt;*
## Usage tips ## 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 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 *&lt;randras@sdr.hu&gt;* for other licensing options. OpenWebRX is also available under a commercial license on request. Please contact me at the address *&lt;randras@sdr.hu&gt;* for licensing options.

View File

@ -67,34 +67,49 @@ sdrhu_key = ""
sdrhu_public_listing = False sdrhu_public_listing = False
# ==== DSP/RX settings ==== # ==== DSP/RX settings ====
dsp_plugin="csdr"
fft_fps=9 fft_fps=9
fft_size=4096 fft_size=4096 #Should be power of 2
samp_rate = 250000 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. 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 ppm = 0
audio_compression="adpcm" #valid values: "adpcm", "none" audio_compression="adpcm" #valid values: "adpcm", "none"
fft_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 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 ==== # ==== I/Q sources ====
# (Uncomment the appropriate by removing # characters at the beginning of the corresponding lines.) # (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): # 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 # >> 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) 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" 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" #format_conversion="csdr convert_s8_f"
""" """
To use a HackRF, compile the HackRF host tools from its "stdout" branch: To use a HackRF, compile the HackRF host tools from its "stdout" branch:
@ -126,11 +141,20 @@ To use a HackRF, compile the HackRF host tools from its "stdout" branch:
#start_rtl_command="(while true; do cat my_iq_file.raw; done) | csdr flowcontrol {sr} 20 ".format(sr=samp_rate*2*1.05) #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" #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) # >> 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" #start_rtl_command="cat /tmp/osmocom_fifo"
#format_conversion="" #format_conversion=""
# ==== Misc options ==== # ==== Misc settings ====
shown_center_freq = center_freq #you can change this if you use an upconverter 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" #access_log = "~/openwebrx_access.log"
waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]" # ==== Color themes ====
waterfall_min_level = -115 #in dB
waterfall_max_level = 0
#A guide is available to help you set these values: https://github.com/simonyiszk/openwebrx/wiki/Calibrating-waterfall-display-levels #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. #Warning! The settings below are very experimental.
csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr. 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_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. 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] #Look up external IP address automatically from icanhazip.com, and use it as [server_hostname]
""" """
print "[openwebrx-config] Detecting external IP address..." print "[openwebrx-config] Detecting external IP address..."

453
csdr.py Executable file
View File

@ -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

View File

@ -33,9 +33,18 @@
var waterfall_colors=%[WATERFALL_COLORS]; var waterfall_colors=%[WATERFALL_COLORS];
var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL]; var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL];
var waterfall_max_level_default=%[WATERFALL_MAX_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>
<script src="sdr.js"></script> <script src="sdr.js"></script>
<script src="mathbox-bundle.min.js"></script>
<script src="openwebrx.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" /> <link rel="stylesheet" type="text/css" href="openwebrx.css" />
<meta charset="utf-8"> <meta charset="utf-8">
</head> </head>
@ -49,7 +58,7 @@
</div> </div>
<div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div> <div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div>
<div id="webrx-top-bar" class="webrx-top-bar-parts"> <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="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> <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-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();"/> <img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/>
@ -72,26 +81,41 @@
<div id="openwebrx-scale-container"> <div id="openwebrx-scale-container">
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas> <canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
</div> </div>
<div id="openwebrx-mathbox-container"> </div>
<div id="webrx-canvas-container"> <div id="webrx-canvas-container">
<div id="openwebrx-phantom-canvas"></div> <div id="openwebrx-phantom-canvas"></div>
<!-- add canvas here by javascript --> <!-- add canvas here by javascript -->
</div> </div>
<div id="openwebrx-panels-container"> <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 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-actual-freq">---.--- MHz</div>
<div id="webrx-mouse-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-panel-line">
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nfm');">FM</div> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nfm"
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('am');">AM</div> onclick="demodulator_analog_replace('nfm');">FM</div>
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('lsb');">LSB</div> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-am"
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('usb');">USB</div> onclick="demodulator_analog_replace('am');">AM</div>
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('cw');">CW</div> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-lsb"
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('dmr');">DMR</div> onclick="demodulator_analog_replace('lsb');">LSB</div>
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('dstar');">DStar</div> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-usb"
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nxdn');">NXDN</div> onclick="demodulator_analog_replace('usb');">USB</div>
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('ysf');">YSF</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>
<div class="openwebrx-panel-line"> <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> <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>
@ -110,6 +134,7 @@
<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="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="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="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 id="openwebrx-smeter-db">0 dB</div>
</div> </div>
<div class="openwebrx-panel-line"> <div class="openwebrx-panel-line">
@ -118,13 +143,15 @@
</div> </div>
</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" 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" id="openwebrx-log-scroll"> <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> <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/> <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 id="openwebrx-debugdiv"></div>
</div> </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-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-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-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
@ -133,12 +160,23 @@
<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-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 class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></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;"> <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> <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. <br />We're working on the code right now, so the application might fail.
</div> </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> </div>
</div> </div>
</div> </div>

4
htdocs/jquery-3.2.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

33
htdocs/mathbox-bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

461
htdocs/mathbox.css Normal file
View File

@ -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;
}

55
htdocs/nanoscroller.css Normal file
View File

@ -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;
}

View File

@ -28,6 +28,11 @@ html, body
overflow: hidden; overflow: hidden;
} }
select
{
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
}
input input
{ {
vertical-align:middle; vertical-align:middle;
@ -394,6 +399,13 @@ input[type=range]:focus::-ms-fill-upper
border-style: none; border-style: none;
image-rendering: crisp-edges; image-rendering: crisp-edges;
image-rendering: -webkit-optimize-contrast; image-rendering: -webkit-optimize-contrast;
/*transition: left 200ms, width 200ms;*/
}
#openwebrx-mathbox-container
{
overflow: none;
display: none;
} }
#openwebrx-phantom-canvas #openwebrx-phantom-canvas
@ -410,11 +422,15 @@ input[type=range]:focus::-ms-fill-upper
height: 396px; height: 396px;
}*/ }*/
/*#webrx-debugdiv #openwebrx-log-scroll
{ {
font-size: 10pt; /*overflow-y:auto;*/
/*overflow-y:scroll;*/ height: 125px;
/*}*/ width: 619px
}
.nano .nano-pane { background: #444; }
.nano .nano-slider { background: #eee !important; }
#webrx-main-container #webrx-main-container
{ {
@ -499,7 +515,7 @@ input[type=range]:focus::-ms-fill-upper
.openwebrx-panel .openwebrx-panel
{ {
transform: perspective( 600px ); transform: perspective( 600px ) rotateX( 90deg );
visibility: hidden; visibility: hidden;
background-color: #575757; background-color: #575757;
padding: 10px; padding: 10px;
@ -526,7 +542,7 @@ input[type=range]:focus::-ms-fill-upper
.openwebrx-button .openwebrx-button
{ {
background-color: #373737; background-color: #373737;
padding: 5px; padding: 4.2px;
border-radius: 5px; border-radius: 5px;
-moz-border-radius: 5px; -moz-border-radius: 5px;
color: White; color: White;
@ -544,7 +560,7 @@ input[type=range]:focus::-ms-fill-upper
display: inline-block; 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:-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% );*/ background:-moz-linear-gradient( center top, #373737 5%, #4F4F4F 100% );*/
@ -560,7 +576,7 @@ input[type=range]:focus::-ms-fill-upper
.openwebrx-demodulator-button .openwebrx-demodulator-button
{ {
width: 50px; width: 38px;
height: 19px; height: 19px;
font-size: 12pt; font-size: 12pt;
text-align: center; text-align: center;
@ -568,7 +584,7 @@ input[type=range]:focus::-ms-fill-upper
.openwebrx-square-button img .openwebrx-square-button img
{ {
height: 30px; height: 27px;
} }
.openwebrx-round-button .openwebrx-round-button
@ -732,7 +748,7 @@ img.openwebrx-mirror-img
{ {
position: relative; position: relative;
top: -2px; top: -2px;
width:91px; width: 95px;
} }
.openwebrx-sliderbtn-img .openwebrx-sliderbtn-img
@ -776,7 +792,7 @@ img.openwebrx-mirror-img
font-size: 10pt; font-size: 10pt;
float: right; float: right;
margin-right: 5px; margin-right: 5px;
margin-top: 29px; margin-top: 24px;
font-family: 'expletus-sans-medium'; font-family: 'expletus-sans-medium';
} }
@ -806,3 +822,152 @@ img.openwebrx-mirror-img
{ {
width: 150px; 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;
}

View File

@ -51,6 +51,7 @@ var audio_compression="none";
var waterfall_setup_done=0; var waterfall_setup_done=0;
var waterfall_queue = []; var waterfall_queue = [];
var waterfall_timer; var waterfall_timer;
var secondary_fft_size;
/*function fade(something,from,to,time_ms,fps) /*function fade(something,from,to,time_ms,fps)
{ {
@ -72,7 +73,8 @@ var rx_photo_state=1;
function e(what) { return document.getElementById(what); } 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); //alert("ios="+ios.toString()+" "+navigator.userAgent);
function init_rx_photo() function init_rx_photo()
@ -183,8 +185,8 @@ function waterfallColorsDefault()
function waterfallColorsAuto() function waterfallColorsAuto()
{ {
e("openwebrx-waterfall-color-min").value=(waterfall_measure_minmax_min-20).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+30).toString(); e("openwebrx-waterfall-color-max").value=(waterfall_measure_minmax_max+waterfall_auto_level_margin[1]).toString();
updateWaterfallColors(0); updateWaterfallColors(0);
} }
@ -559,6 +561,7 @@ function demodulator_default_analog(offset_frequency,subtype)
this.envelope.drag_end=function(x) 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. { //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 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; this.dragged_range=demodulator.draggable_ranges.none;
return to_return; return to_return;
@ -575,6 +578,7 @@ function mkenvelopes(visible_range) //called from mkscale
{ {
demodulators[i].envelope.draw(visible_range); 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) function demodulator_remove(which)
@ -589,8 +593,17 @@ function demodulator_add(what)
mkenvelopes(get_visible_freq_range()); 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 { //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; var temp_offset=0;
if(demodulators.length) if(demodulators.length)
{ {
@ -598,6 +611,7 @@ function demodulator_analog_replace(subtype)
demodulator_remove(0); demodulator_remove(0);
} }
demodulator_add(new demodulator_default_analog(temp_offset,subtype)); demodulator_add(new demodulator_default_analog(temp_offset,subtype));
demodulator_buttons_update();
} }
function demodulator_set_offset_frequency(which,to_what) function demodulator_set_offset_frequency(which,to_what)
@ -1028,7 +1042,7 @@ function canvas_mousewheel(evt)
zoom_max_level_hps=33; //Hz/pixel zoom_max_level_hps=33; //Hz/pixel
zoom_levels_count=5; zoom_levels_count=14;
function get_zoom_coeff_from_hps(hps) function get_zoom_coeff_from_hps(hps)
{ {
@ -1050,8 +1064,10 @@ function mkzoomlevels()
zoom_levels=[1]; zoom_levels=[1];
maxc=get_zoom_coeff_from_hps(zoom_max_level_hps); maxc=get_zoom_coeff_from_hps(zoom_max_level_hps);
if(maxc<1) return; if(maxc<1) return;
// logarithmic interpolation
zoom_ratio = Math.pow(maxc, 1/zoom_levels_count);
for(i=1;i<zoom_levels_count;i++) 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) function zoom_step(out, where, onscreen)
@ -1063,7 +1079,7 @@ function zoom_step(out, where, onscreen)
zoom_center_rel=canvas_get_freq_offset(where); 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()); //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; 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); resize_canvases(true);
mkscale(); mkscale();
} }
@ -1095,9 +1111,17 @@ function zoom_calc()
function resize_waterfall_container(check_init) function resize_waterfall_container(check_init)
{ {
if(check_init&&!waterfall_setup_done) return; 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_server_output_rate=11025;
audio_client_resampling_factor=4; 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; } if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; }
// //
debug_ws_data_received+=evt.data.byteLength/1000; debug_ws_data_received+=evt.data.byteLength/1000;
firstChars=getFirstChars(evt.data,3); first4Chars=getFirstChars(evt.data,4);
if(firstChars=="CLI") first3Chars=first4Chars.slice(0,3);
if(first3Chars=="CLI")
{ {
var stringData=arrayBufferToString(evt.data); 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; var audio_data;
if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4) 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_prepare(audio_data);
audio_buffer_current_size_debug+=audio_data.length; audio_buffer_current_size_debug+=audio_data.length;
audio_buffer_all_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"); //alert("Yupee! Doing FFT");
//if(first4Chars=="FFTS") console.log("FFTS");
if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4)); if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4));
else if(fft_compression="adpcm") 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_i16=fft_codec.decode(new Uint8Array(evt.data,4));
var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N); 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; 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 /*try
{*/ {*/
@ -1187,6 +1221,18 @@ function on_ws_recv(evt)
break; break;
case "fft_size": case "fft_size":
fft_size=parseInt(param[1]); 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; break;
case "fft_fps": case "fft_fps":
fft_fps=parseInt(param[1]); fft_fps=parseInt(param[1]);
@ -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 if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present
} }
e("openwebrx-debugdiv").innerHTML+=what+"<br />"; e("openwebrx-debugdiv").innerHTML+=what+"<br />";
var wls=e("openwebrx-log-scroll"); //var wls=e("openwebrx-log-scroll");
wls.scrollTop=wls.scrollHeight; //scroll to bottom //wls.scrollTop=wls.scrollHeight; //scroll to bottom
$(".nano").nanoScroller();
$(".nano").nanoScroller({ scroll: 'bottom' });
} }
var audio_context; var audio_context;
@ -1585,16 +1633,24 @@ function webrx_set_param(what, value)
ws.send("SET "+what+"="+value.toString()); ws.send("SET "+what+"="+value.toString());
} }
var starting_mute = false;
function parsehash() function parsehash()
{ {
if(h=window.location.hash) if(h=window.location.hash)
{ {
h.substring(1).split(",").forEach(function(x){ h.substring(1).split(",").forEach(function(x){
harr=x.split("="); harr=x.split("=");
console.log(harr); //console.log(harr);
if(harr[0]=="mod") starting_mod = harr[1]; if(harr[0]=="mute") toggleMute();
if(harr[0]=="sql") { e("openwebrx-panel-squelch").value=harr[1]; updateSquelch(); } else if(harr[0]=="mod") starting_mod = harr[1];
if(harr[0]=="freq") { 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(parseInt(harr[1]));
console.log(center_freq); console.log(center_freq);
starting_offset_frequency = parseInt(harr[1])-center_freq; starting_offset_frequency = parseInt(harr[1])-center_freq;
@ -1638,6 +1694,9 @@ function audio_preinit()
function audio_init() 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... if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor...
audio_debug_time_start=(new Date()).getTime(); 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) //window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200)
} }
},2000); },2000);
} }
function on_ws_closed() function on_ws_closed()
@ -1722,17 +1782,18 @@ function open_websocket()
ws.onerror = on_ws_error; 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_min_level) db_value=waterfall_min_level;
if(db_value>waterfall_max_level) db_value=waterfall_max_level; if(db_value>waterfall_max_level) db_value=waterfall_max_level;
full_scale=waterfall_max_level-waterfall_min_level; full_scale=waterfall_max_level-waterfall_min_level;
relative_value=db_value-waterfall_min_level; relative_value=db_value-waterfall_min_level;
value_percent=relative_value/full_scale; 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); index=Math.floor(value_percent/percent_for_one_color);
remain=(value_percent-percent_for_one_color*index)/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) function color_between(first, second, percent)
@ -1755,7 +1816,7 @@ var canvas_phantom;
function add_canvas() function add_canvas()
{ {
new_canvas = document.createElement("canvas"); var new_canvas = document.createElement("canvas");
new_canvas.width=fft_size; new_canvas.width=fft_size;
new_canvas.height=canvas_default_height; new_canvas.height=canvas_default_height;
canvas_actual_line=canvas_default_height-1; canvas_actual_line=canvas_default_height-1;
@ -1780,9 +1841,11 @@ function add_canvas()
} }
} }
function init_canvas_container() function init_canvas_container()
{ {
canvas_container=e("webrx-canvas-container"); canvas_container=e("webrx-canvas-container");
mathbox_container=e("openwebrx-mathbox-container");
canvas_container.addEventListener("mouseout",canvas_container_mouseout, false); canvas_container.addEventListener("mouseout",canvas_container_mouseout, false);
//window.addEventListener("mouseout",window_mouseout,false); //window.addEventListener("mouseout",window_mouseout,false);
//document.body.addEventListener("mouseup",body_mouseup,false); //document.body.addEventListener("mouseup",body_mouseup,false);
@ -1839,7 +1902,7 @@ function resize_canvases(zoom)
function waterfall_init() function waterfall_init()
{ {
init_canvas_container(); 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(); resize_waterfall_container(false); /* then */ resize_canvases();
scale_setup(); scale_setup();
mkzoomlevels(); mkzoomlevels();
@ -1848,6 +1911,42 @@ function waterfall_init()
var waterfall_dont_scale=0; 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) function waterfall_add(data)
{ {
if(!waterfall_setup_done) return; 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; 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 //Add line to waterfall image
oneline_image = canvas_context.createImageData(w,1); oneline_image = canvas_context.createImageData(w,1);
for(x=0;x<w;x++) 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; oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
} }
//Draw image //Draw image
canvas_context.putImageData(oneline_image, 0, canvas_actual_line--); canvas_context.putImageData(oneline_image, 0, canvas_actual_line--);
shift_canvases(); shift_canvases();
if(canvas_actual_line<0) add_canvas(); 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() function openwebrx_resize()
{ {
resize_canvases(); resize_canvases();
@ -1979,10 +2260,11 @@ function openwebrx_resize()
function openwebrx_init() 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"; (opb=e("openwebrx-play-button-text")).style.marginTop=(window.innerHeight/2-opb.clientHeight/2).toString()+"px";
init_rx_photo(); init_rx_photo();
open_websocket(); open_websocket();
secondary_demod_init();
place_panels(first_show_panel); place_panels(first_show_panel);
window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000); window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000);
window.addEventListener("resize",openwebrx_resize); window.addEventListener("resize",openwebrx_resize);
@ -2076,9 +2358,14 @@ function pop_bottommost_panel(from)
return to_return; 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; if(item.openwebrxDisableClick) return;
item.style.transitionDuration="599ms"; item.style.transitionDuration="599ms";
item.style.transitionDelay="0ms"; item.style.transitionDelay="0ms";
@ -2167,6 +2454,7 @@ function place_panels(function_apply)
p.style.visibility="visible"; 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); if(function_apply) function_apply(p);
//console.log(p.id, y, p.openwebrxPanelTransparent);
} }
y=hoffset; y=hoffset;
while(right_col.length>0) while(right_col.length>0)
@ -2199,3 +2487,331 @@ function progressbar_set(obj,val,text,over)
if(innerText==null) return; if(innerText==null) return;
innerText.innerHTML=text; 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 "&nbsp;";
if(y=="\n") return "&nbsp;";
//if(y=="\n") return "<br />";
if(c<32||c>126) return "";
if(y=="&") return "&amp;";
if(y=="<") return "&lt;";
if(y==">") return "&gt;";
if(y==" ") return "&nbsp;";
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();
}

View File

@ -20,13 +20,13 @@ print "" # python2.7 is required to run OpenWebRX instead of python3. Please run
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
sw_version="v0.14+" sw_version="v0.17"
#0.15 (added nmux)
import os import os
import code import code
import importlib import importlib
import plugins import csdr
import plugins.dsp
import thread import thread
import time import time
import datetime import datetime
@ -62,6 +62,7 @@ try: import __pypy__
except: pass except: pass
pypy="__pypy__" in globals() pypy="__pypy__" in globals()
"""
def import_all_plugins(directory): def import_all_plugins(directory):
for subdir in os.listdir(directory): for subdir in os.listdir(directory):
if os.path.isdir(directory+subdir) and not subdir[0]=="_": if os.path.isdir(directory+subdir) and not subdir[0]=="_":
@ -70,6 +71,7 @@ def import_all_plugins(directory):
importname=(directory+subdir+"/plugin").replace("/",".") importname=(directory+subdir+"/plugin").replace("/",".")
print "[openwebrx-import] Found plugin:",importname print "[openwebrx-import] Found plugin:",importname
importlib.import_module(importname) importlib.import_module(importname)
"""
class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer): class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer):
pass pass
@ -90,7 +92,8 @@ def handle_signal(sig, frame):
print print
for key in client._fields: for key in client._fields:
print "\t%s = %s"%(key,str(getattr(client,key))) print "\t%s = %s"%(key,str(getattr(client,key)))
elif sig == signal.SIGUSR2:
code.interact(local=globals())
else: else:
print "[openwebrx] Ctrl+C: aborting." print "[openwebrx] Ctrl+C: aborting."
cleanup_clients(True) cleanup_clients(True)
@ -126,12 +129,10 @@ def main():
#Set signal handler #Set signal handler
signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python
signal.signal(signal.SIGUSR1, handle_signal) signal.signal(signal.SIGUSR1, handle_signal)
signal.signal(signal.SIGUSR2, handle_signal)
#Load plugins
import_all_plugins("plugins/dsp/")
#Pypy #Pypy
if pypy: print "pypy detected (and now something completely different: a c code is expected to run at a speed of 3*10^8 m/s?)" if pypy: print "pypy detected (and now something completely different: c code is expected to run at a speed of 3*10^8 m/s?)"
#Change process name to "openwebrx" (to be seen in ps) #Change process name to "openwebrx" (to be seen in ps)
try: try:
@ -144,11 +145,21 @@ def main():
pass pass
#Start rtl thread #Start rtl thread
if os.system("ncat --version > /dev/null") == 32512: #check for ncat if os.system("csdr 2> /dev/null") == 32512: #check for csdr
print "[openwebrx-main] Error: ncat not detected, please install it! The ncat tool is a netcat alternative, used for distributing the I/Q data stream. It is usually available in the nmap package (sudo apt-get install nmap). For more explanation, look into the README.md" print "[openwebrx-main] You need to install \"csdr\" to run OpenWebRX!\n"
return
if os.system("nmux --help 2> /dev/null") == 32512: #check for nmux
print "[openwebrx-main] You need to install an up-to-date version of \"csdr\" that contains the \"nmux\" tool to run OpenWebRX! Please upgrade \"csdr\"!\n"
return return
if cfg.start_rtl_thread: if cfg.start_rtl_thread:
cfg.start_rtl_command += "| ncat -4l %d -k --send-only --allow 127.0.0.1" % cfg.iq_server_port nmux_bufcnt = nmux_bufsize = 0
while nmux_bufsize < cfg.samp_rate/4: nmux_bufsize += 4096
while nmux_bufsize * nmux_bufcnt < cfg.nmux_memory * 1e6: nmux_bufcnt += 1
if nmux_bufcnt == 0 or nmux_bufsize == 0:
print "[openwebrx-main] Error: nmux_bufsize or nmux_bufcnt is zero. These depend on nmux_memory and samp_rate options in config_webrx.py"
return
print "[openwebrx-main] nmux_bufsize = %d, nmux_bufcnt = %d" % (nmux_bufsize, nmux_bufcnt)
cfg.start_rtl_command += "| nmux --bufsize %d --bufcnt %d --port %d --address 127.0.0.1" % (nmux_bufsize, nmux_bufcnt, cfg.iq_server_port)
rtl_thread=threading.Thread(target = lambda:subprocess.Popen(cfg.start_rtl_command, shell=True), args=()) rtl_thread=threading.Thread(target = lambda:subprocess.Popen(cfg.start_rtl_command, shell=True), args=())
rtl_thread.start() rtl_thread.start()
print "[openwebrx-main] Started rtl_thread: "+cfg.start_rtl_command print "[openwebrx-main] Started rtl_thread: "+cfg.start_rtl_command
@ -281,12 +292,13 @@ def apply_csdr_cfg_to_dsp(dsp):
def spectrum_thread_function(): def spectrum_thread_function():
global clients, spectrum_dsp, spectrum_thread_watchdog_last_tick global clients, spectrum_dsp, spectrum_thread_watchdog_last_tick
spectrum_dsp=dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() spectrum_dsp=dsp=csdr.dsp()
dsp.nc_port=cfg.iq_server_port dsp.nc_port=cfg.iq_server_port
dsp.set_demodulator("fft") dsp.set_demodulator("fft")
dsp.set_samp_rate(cfg.samp_rate) dsp.set_samp_rate(cfg.samp_rate)
dsp.set_fft_size(cfg.fft_size) dsp.set_fft_size(cfg.fft_size)
dsp.set_fft_fps(cfg.fft_fps) dsp.set_fft_fps(cfg.fft_fps)
dsp.set_fft_averages(int(round(1.0 * cfg.samp_rate / cfg.fft_size / cfg.fft_fps / (1.0 - cfg.fft_voverlap_factor))) if cfg.fft_voverlap_factor>0 else 0)
dsp.set_fft_compression(cfg.fft_compression) dsp.set_fft_compression(cfg.fft_compression)
dsp.set_format_conversion(cfg.format_conversion) dsp.set_format_conversion(cfg.format_conversion)
apply_csdr_cfg_to_dsp(dsp) apply_csdr_cfg_to_dsp(dsp)
@ -415,6 +427,7 @@ class WebRXHandler(BaseHTTPRequestHandler):
# there's even another cool tip at http://stackoverflow.com/questions/4419650/how-to-implement-timeout-in-basehttpserver-basehttprequesthandler-python # there's even another cool tip at http://stackoverflow.com/questions/4419650/how-to-implement-timeout-in-basehttpserver-basehttprequesthandler-python
#if self.path[:5]=="/lock": cma("do_GET /lock/") # to test mutex_watchdog_thread. Do not uncomment in production environment! #if self.path[:5]=="/lock": cma("do_GET /lock/") # to test mutex_watchdog_thread. Do not uncomment in production environment!
if self.path[:4]=="/ws/": if self.path[:4]=="/ws/":
print "[openwebrx-ws] Client requested WebSocket connection"
if receiver_failed: self.send_error(500,"Internal server error") if receiver_failed: self.send_error(500,"Internal server error")
try: try:
# ========= WebSocket handshake ========= # ========= WebSocket handshake =========
@ -447,21 +460,22 @@ class WebRXHandler(BaseHTTPRequestHandler):
rxws.send(self, "MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} audio_compression={4} fft_compression={5} max_clients={6} setup".format(str(cfg.shown_center_freq),str(cfg.samp_rate),cfg.fft_size,cfg.fft_fps,cfg.audio_compression,cfg.fft_compression,cfg.max_clients)) rxws.send(self, "MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} audio_compression={4} fft_compression={5} max_clients={6} setup".format(str(cfg.shown_center_freq),str(cfg.samp_rate),cfg.fft_size,cfg.fft_fps,cfg.audio_compression,cfg.fft_compression,cfg.max_clients))
# ========= Initialize DSP ========= # ========= Initialize DSP =========
dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() dsp=csdr.dsp()
dsp_initialized=False dsp_initialized=False
dsp.set_audio_compression(cfg.audio_compression) dsp.set_audio_compression(cfg.audio_compression)
dsp.set_fft_compression(cfg.fft_compression) #used by secondary chains
dsp.set_format_conversion(cfg.format_conversion) dsp.set_format_conversion(cfg.format_conversion)
dsp.set_offset_freq(0) dsp.set_offset_freq(0)
dsp.set_bpf(-4000,4000) dsp.set_bpf(-4000,4000)
dsp.set_secondary_fft_size(cfg.digimodes_fft_size)
dsp.nc_port=cfg.iq_server_port dsp.nc_port=cfg.iq_server_port
apply_csdr_cfg_to_dsp(dsp) apply_csdr_cfg_to_dsp(dsp)
myclient.dsp=dsp myclient.dsp=dsp
do_secondary_demod=False
access_log("Started streaming to client: "+self.client_address[0]+"#"+myclient.id+" (users now: "+str(len(clients))+")") access_log("Started streaming to client: "+self.client_address[0]+"#"+myclient.id+" (users now: "+str(len(clients))+")")
myclient.loopstat=0
while True: while True:
myclient.loopstat=0
if myclient.closed[0]: if myclient.closed[0]:
print "[openwebrx-httpd:ws] client closed by other thread" print "[openwebrx-httpd:ws] client closed by other thread"
break break
@ -517,12 +531,34 @@ class WebRXHandler(BaseHTTPRequestHandler):
rxws.send(self,myclient.bcastmsg) rxws.send(self,myclient.bcastmsg)
myclient.bcastmsg="" myclient.bcastmsg=""
# ========= send secondary =========
if do_secondary_demod:
myclient.loopstat=41
while True:
try:
secondary_spectrum_data=dsp.read_secondary_fft(dsp.get_secondary_fft_bytes_to_read())
if len(secondary_spectrum_data) == 0: break
# print "len(secondary_spectrum_data)", len(secondary_spectrum_data) #TODO digimodes
rxws.send(self, secondary_spectrum_data, "FFTS")
except: break
myclient.loopstat=42
while True:
try:
myclient.loopstat=422
secondary_demod_data=dsp.read_secondary_demod(1)
myclient.loopstat=423
if len(secondary_demod_data) == 0: break
# print "len(secondary_demod_data)", len(secondary_demod_data), secondary_demod_data #TODO digimodes
rxws.send(self, secondary_demod_data, "DAT ")
except: break
# ========= process commands ========= # ========= process commands =========
while True: while True:
myclient.loopstat=50 myclient.loopstat=50
rdata=rxws.recv(self, False) rdata=rxws.recv(self, False)
if not rdata: break myclient.loopstat=51
#try: #try:
if not rdata: break
elif rdata[:3]=="SET": elif rdata[:3]=="SET":
print "[openwebrx-httpd:ws,%d] command: %s"%(client_i,rdata) print "[openwebrx-httpd:ws,%d] command: %s"%(client_i,rdata)
pairs=rdata[4:].split(" ") pairs=rdata[4:].split(" ")
@ -531,13 +567,13 @@ class WebRXHandler(BaseHTTPRequestHandler):
filter_limit=dsp.get_output_rate()/2 filter_limit=dsp.get_output_rate()/2
for pair in pairs: for pair in pairs:
param_name, param_value = pair.split("=") param_name, param_value = pair.split("=")
if param_name == "low_cut" and -filter_limit <= float(param_value) <= filter_limit: if param_name == "low_cut" and -filter_limit <= int(param_value) <= filter_limit:
bpf_set=True bpf_set=True
new_bpf[0]=int(param_value) new_bpf[0]=int(param_value)
elif param_name == "high_cut" and -filter_limit <= float(param_value) <= filter_limit: elif param_name == "high_cut" and -filter_limit <= int(param_value) <= filter_limit:
bpf_set=True bpf_set=True
new_bpf[1]=int(param_value) new_bpf[1]=int(param_value)
elif param_name == "offset_freq" and -cfg.samp_rate/2 <= float(param_value) <= cfg.samp_rate/2: elif param_name == "offset_freq" and -cfg.samp_rate/2 <= int(param_value) <= cfg.samp_rate/2:
myclient.loopstat=510 myclient.loopstat=510
dsp.set_offset_freq(int(param_value)) dsp.set_offset_freq(int(param_value))
elif param_name == "squelch_level" and float(param_value) >= 0: elif param_name == "squelch_level" and float(param_value) >= 0:
@ -560,6 +596,19 @@ class WebRXHandler(BaseHTTPRequestHandler):
myclient.loopstat=550 myclient.loopstat=550
dsp.start() dsp.start()
dsp_initialized=True dsp_initialized=True
elif param_name=="secondary_mod" and cfg.digimodes_enable:
if (dsp.get_secondary_demodulator() != param_value):
if dsp_initialized: dsp.stop()
if param_value == "off":
dsp.set_secondary_demodulator(None)
do_secondary_demod = False
else:
dsp.set_secondary_demodulator(param_value)
do_secondary_demod = True
rxws.send(self, "MSG secondary_fft_size={0} if_samp_rate={1} secondary_bw={2} secondary_setup".format(cfg.digimodes_fft_size, dsp.if_samp_rate(), dsp.secondary_bw()))
if dsp_initialized: dsp.start()
elif param_name=="secondary_offset_freq" and 0 <= int(param_value) <= dsp.if_samp_rate()/2 and cfg.digimodes_enable:
dsp.set_secondary_offset_freq(int(param_value))
else: else:
print "[openwebrx-httpd:ws] invalid parameter" print "[openwebrx-httpd:ws] invalid parameter"
if bpf_set: if bpf_set:
@ -567,16 +616,20 @@ class WebRXHandler(BaseHTTPRequestHandler):
dsp.set_bpf(*new_bpf) dsp.set_bpf(*new_bpf)
#code.interact(local=locals()) #code.interact(local=locals())
except: except:
myclient.loopstat=990
exc_type, exc_value, exc_traceback = sys.exc_info() exc_type, exc_value, exc_traceback = sys.exc_info()
if exc_value[0]==32: #"broken pipe", client disconnected print "[openwebrx-httpd:ws] exception: ",exc_type,exc_value
pass traceback.print_tb(exc_traceback) #TODO digimodes
elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected #if exc_value[0]==32: #"broken pipe", client disconnected
pass # pass
else: #elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected
print "[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value # pass
traceback.print_tb(exc_traceback) #else:
# print "[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value
# traceback.print_tb(exc_traceback)
#stop dsp for the disconnected client #stop dsp for the disconnected client
myclient.loopstat=991
try: try:
dsp.stop() dsp.stop()
del dsp del dsp
@ -584,6 +637,7 @@ class WebRXHandler(BaseHTTPRequestHandler):
print "[openwebrx-httpd] error in dsp.stop()" print "[openwebrx-httpd] error in dsp.stop()"
#delete disconnected client #delete disconnected client
myclient.loopstat=992
try: try:
cma("do_GET /ws/ delete disconnected") cma("do_GET /ws/ delete disconnected")
id_to_close=get_client_by_id(myclient.id,False) id_to_close=get_client_by_id(myclient.id,False)
@ -646,7 +700,12 @@ class WebRXHandler(BaseHTTPRequestHandler):
("%[START_MOD]",cfg.start_mod), ("%[START_MOD]",cfg.start_mod),
("%[WATERFALL_COLORS]",cfg.waterfall_colors), ("%[WATERFALL_COLORS]",cfg.waterfall_colors),
("%[WATERFALL_MIN_LEVEL]",str(cfg.waterfall_min_level)), ("%[WATERFALL_MIN_LEVEL]",str(cfg.waterfall_min_level)),
("%[WATERFALL_MAX_LEVEL]",str(cfg.waterfall_max_level)) ("%[WATERFALL_MAX_LEVEL]",str(cfg.waterfall_max_level)),
("%[WATERFALL_AUTO_LEVEL_MARGIN]","[%d,%d]"%cfg.waterfall_auto_level_margin),
("%[DIGIMODES_ENABLE]",("true" if cfg.digimodes_enable else "false")),
("%[MATHBOX_WATERFALL_FRES]",str(cfg.mathbox_waterfall_frequency_resolution)),
("%[MATHBOX_WATERFALL_THIST]",str(cfg.mathbox_waterfall_history_length)),
("%[MATHBOX_WATERFALL_COLORS]",cfg.mathbox_waterfall_colors)
) )
for rule in replace_dictionary: for rule in replace_dictionary:
while data.find(rule[0])!=-1: while data.find(rule[0])!=-1:

View File

View File

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -26,8 +26,8 @@ def run(continuously=True):
if not cfg.sdrhu_key: return if not cfg.sdrhu_key: return
firsttime="(Your receiver is soon getting listed on sdr.hu!)" firsttime="(Your receiver is soon getting listed on sdr.hu!)"
while True: 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" 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 print "[openwebrx-sdrhu]", cmd
returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate() returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
returned=returned[0] returned=returned[0]
#print returned #print returned
@ -48,4 +48,3 @@ def run(continuously=True):
if __name__=="__main__": if __name__=="__main__":
run(False) run(False)