Merged feature/digitalmods

This commit is contained in:
ha7ilm 2017-07-12 19:03:59 +02:00
commit c62f29ab5a
22 changed files with 3728 additions and 1129 deletions

2
.gitignore vendored
View File

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

View File

@ -3,16 +3,19 @@ OpenWebRX
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, HackRF, SDRplay, AirSpy and many other devices, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/">OpenWebRX Wiki</a>. - 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="http://openwebrx.org/bsc-thesis.pdf">available here.</a>
@ -32,13 +35,16 @@ It has the following features:
- *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. - *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. - 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-07)**
- OpenWebRX now has a BPSK31 demodulator and a 3D waterfall display.
> When upgrading OpenWebRX, please make sure that you also upgrade *csdr*! > 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

View File

@ -3,9 +3,9 @@
""" """
config_webrx: configuration options for OpenWebRX config_webrx: configuration options for OpenWebRX
This file is part of OpenWebRX, This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI. an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu> Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@ -20,15 +20,15 @@ config_webrx: configuration options for OpenWebRX
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
In addition, as a special exception, the copyright holders In addition, as a special exception, the copyright holders
state that config_rtl.py and config_webrx.py are not part of the state that config_rtl.py and config_webrx.py are not part of the
Corresponding Source defined in GNU AGPL version 3 section 1. Corresponding Source defined in GNU AGPL version 3 section 1.
(It means that you do not have to redistribute config_rtl.py and (It means that you do not have to redistribute config_rtl.py and
config_webrx.py if you make any changes to these two configuration files, config_webrx.py if you make any changes to these two configuration files,
and use them for running your web service with OpenWebRX.) and use them for running your web service with OpenWebRX.)
""" """
# NOTE: you can find additional information about configuring OpenWebRX in the Wiki: # NOTE: you can find additional information about configuring OpenWebRX in the Wiki:
@ -67,19 +67,22 @@ 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 #Should be power of 2 fft_size=4096 #Should be power of 2
fft_voverlap_factor=0.3 #If fft_voverlap_factor is above 0, multiple FFTs will be used for creating a line on the diagram. fft_voverlap_factor=0.3 #If fft_voverlap_factor is above 0, multiple FFTs will be used for creating a line on the diagram.
samp_rate = 250000 # samp_rate = 250000
center_freq = 145525000 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
""" """
@ -104,7 +107,9 @@ Note: if you experience audio underruns while CPU usage is 100%, you can:
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:
@ -127,9 +132,9 @@ To use a HackRF, compile the HackRF host tools from its "stdout" branch:
#format_conversion="csdr convert_s16_f | csdr gain_ff 30" #format_conversion="csdr convert_s16_f | csdr gain_ff 30"
# >> /dev/urandom test signal source # >> /dev/urandom test signal source
#samp_rate = 2400000 # samp_rate = 2400000
#start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate) # start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
#format_conversion="csdr convert_u8_f" # format_conversion="csdr convert_u8_f"
# >> Pre-recorded raw I/Q file as signal source # >> Pre-recorded raw I/Q file as signal source
# You will have to correctly specify: samp_rate, center_freq, format_conversion in order to correctly play an I/Q file. # You will have to correctly specify: samp_rate, center_freq, format_conversion in order to correctly play an I/Q file.
@ -182,15 +187,20 @@ waterfall_auto_level_margin = (5, 40)
##For the old colors, you might also want to set [fft_voverlap_factor] to 0. ##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: #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_min_level] = [current_min_power_level] - [waterfall_auto_level_margin[0]]
# [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin[1]] # [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin[1]]
# #
# ___|____________________________________|____________________________________|____________________________________|___> signal power # ___|____________________________________|____________________________________|____________________________________|___> signal power
# \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level | \_waterfall_auto_level_margin[1]_/ # \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level | \_waterfall_auto_level_margin[1]_/
# current_max_power_level __| # current_max_power_level __|
# ==== Experimental settings ===
#Warning! These are very experimental. # 3D view settings
mathbox_waterfall_frequency_resolution = 128 #bins
mathbox_waterfall_history_length = 10 #seconds
mathbox_waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]"
# === Experimental settings ===
#Warning! The settings below are very experimental.
csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr. csdr_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.

424
csdr.py Executable file
View File

@ -0,0 +1,424 @@
"""
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):
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 fastagc_ff 1024 | csdr convert_f_s16"+chain_end
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

@ -1,9 +1,9 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<!-- <!--
This file is part of OpenWebRX, This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI. an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu> Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@ -20,128 +20,161 @@
--> -->
<html> <html>
<head> <head>
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title> <title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
<script type="text/javascript"> <script type="text/javascript">
//Global variables //Global variables
var client_id="%[CLIENT_ID]"; var client_id="%[CLIENT_ID]";
var ws_url="%[WS_URL]"; var ws_url="%[WS_URL]";
var rx_photo_height=%[RX_PHOTO_HEIGHT]; var rx_photo_height=%[RX_PHOTO_HEIGHT];
var audio_buffering_fill_to=%[AUDIO_BUFSIZE]; var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
var starting_mod = "%[START_MOD]"; var starting_mod="%[START_MOD]";
var starting_offset_frequency = %[START_OFFSET_FREQ]; var starting_offset_frequency = %[START_OFFSET_FREQ];
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 waterfall_auto_level_margin=%[WATERFALL_AUTO_LEVEL_MARGIN];
</script> var server_enable_digimodes=%[DIGIMODES_ENABLE];
<script src="sdr.js"></script> var mathbox_waterfall_frequency_resolution=%[MATHBOX_WATERFALL_FRES];
<script src="openwebrx.js"></script> var mathbox_waterfall_history_length=%[MATHBOX_WATERFALL_THIST];
<link rel="stylesheet" type="text/css" href="openwebrx.css" /> var mathbox_waterfall_colors=%[MATHBOX_WATERFALL_COLORS];
<meta charset="utf-8"> </script>
</head> <script src="sdr.js"></script>
<body onload="openwebrx_init();"> <script src="mathbox-bundle.min.js"></script>
<script src="openwebrx.js"></script>
<script src="jquery-3.2.1.min.js"></script>
<script src="jquery.nanoscroller.js"></script>
<link rel="stylesheet" type="text/css" href="nanoscroller.css" />
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
<meta charset="utf-8">
</head>
<body onload="openwebrx_init();">
<div id="webrx-page-container"> <div id="webrx-page-container">
<div id="webrx-top-container"> <div id="webrx-top-container">
<div id="webrx-top-photo-clip"> <div id="webrx-top-photo-clip">
<img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/> <img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>
<div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div> <div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div>
<div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div> <div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div>
</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="http://openwebrx.org/" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a> <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();"/>
<div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div> <div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div>
<div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div> <div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div>
<div id="openwebrx-rx-details-arrow"> <div id="openwebrx-rx-details-arrow">
<a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a> <a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
<a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a> <a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
</div> </div>
<section id="openwebrx-main-buttons"> <section id="openwebrx-main-buttons">
<ul> <ul>
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li> <li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li> <li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li> <li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
</ul> </ul>
</section> </section>
</div> </div>
</div> </div>
<div id="webrx-main-container"> <div id="webrx-main-container">
<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="webrx-canvas-container"> <div id="openwebrx-mathbox-container"> </div>
<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,115"> <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" id="openwebrx-button-nfm"
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nfm');">FM</div> onclick="demodulator_analog_replace('nfm');">FM</div>
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('am');">AM</div> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-am"
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('lsb');">LSB</div> onclick="demodulator_analog_replace('am');">AM</div>
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('usb');">USB</div> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-lsb"
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('cw');">CW</div> onclick="demodulator_analog_replace('lsb');">LSB</div>
</div> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-usb"
<div class="openwebrx-panel-line"> onclick="demodulator_analog_replace('usb');">USB</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> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-cw"
<input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()"> onclick="demodulator_analog_replace('cw');">CW</div>
<div title="Auto-adjust waterfall colors" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></div> </div>
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()"> <div class="openwebrx-panel-line">
</div> <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dig" onclick="demodulator_digital_replace_last();">DIG</div>
<div class="openwebrx-panel-line"> <select id="openwebrx-secondary-demod-listbox" onchange="secondary_demod_listbox_changed();">
<div title="Auto-set squelch level" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div> <option value="none"></option>
<input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()"> <option value="bpsk31">BPSK31</option>
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></div> </select>
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()"> </div>
</div> <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 class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();" title="Zoom in one step"> <img src="gfx/openwebrx-zoom-in.png" /></div> <input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()">
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div> <div title="Auto-adjust waterfall colors" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div> <input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()">
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div> </div>
<div id="openwebrx-smeter-db">0 dB</div> <div class="openwebrx-panel-line">
</div> <div title="Auto-set squelch level" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
<div class="openwebrx-panel-line"> <input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()">
<div id="openwebrx-smeter-outer"> <div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></div>
<div id="openwebrx-smeter-bar"></div> <input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
</div> </div>
</div> <div class="openwebrx-panel-line">
</div> <div class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();" title="Zoom in one step"> <img src="gfx/openwebrx-zoom-in.png" /></div>
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,142"> <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-panel-inner" id="openwebrx-log-scroll"> <div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div> <div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
<span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/> <div class="openwebrx-button openwebrx-square-button" onclick="mathbox_toggle();" title="Toggle 3D view"><img src="gfx/openwebrx-3d-spectrum.png" /></div>
<div id="openwebrx-debugdiv"></div> <div id="openwebrx-smeter-db">0 dB</div>
</div> </div>
</div> <div class="openwebrx-panel-line">
<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 id="openwebrx-smeter-outer">
<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 id="openwebrx-smeter-bar"></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>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div> </div>
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div> </div>
<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-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-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div> <div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
</div> <div class="nano-content">
<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 id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
<span style="font-size: 15pt; font-weight: bold;">Under construction</span> <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/>
<br />We're working on the code right now, so the application might fail. <div id="openwebrx-debugdiv"></div>
</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-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
</div>
<div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="background-color: Red;">
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
<br />We're working on the code right now, so the application might fail.
</div>
<div class="openwebrx-panel" id="openwebrx-panel-digimodes" data-panel-name="digimodes" data-panel-pos="left" data-panel-order="2" data-panel-size="619,210">
<div id="openwebrx-digimode-canvas-container">
<div id="openwebrx-digimode-select-channel"></div>
</div>
<div id="openwebrx-digimode-content-container">
<div class="gradient"></div>
<div id="openwebrx-digimode-content">
<span id="openwebrx-cursor-blink"></span>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<div id="openwebrx-big-grey" onclick="iosPlayButtonClick();"> <div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
<div id="openwebrx-play-button-text"> <div id="openwebrx-play-button-text">
<img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" /> <img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" />
<br /><br />Start OpenWebRX <br /><br />Start OpenWebRX
</div> </div>
</div> </div>
</body> </body>
</html> </html>

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: 35px; 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)
{ {
@ -549,6 +550,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;
@ -565,6 +567,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)
@ -579,8 +582,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)
{ {
@ -588,6 +600,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)
@ -1055,7 +1068,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();
} }
@ -1087,9 +1100,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;
@ -1126,14 +1147,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)
@ -1143,9 +1165,10 @@ function on_ws_recv(evt)
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 && (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")
{ {
@ -1154,9 +1177,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
{*/ {*/
@ -1180,6 +1211,18 @@ function on_ws_recv(evt)
case "fft_size": case "fft_size":
fft_size=parseInt(param[1]); fft_size=parseInt(param[1]);
break; break;
case "secondary_fft_size":
secondary_fft_size=parseInt(param[1]);
break;
case "secondary_setup":
secondary_demod_init_canvases();
break;
case "if_samp_rate":
if_samp_rate=parseInt(param[1]);
break;
case "secondary_bw":
secondary_bw=parseFloat(param[1]);
break;
case "fft_fps": case "fft_fps":
fft_fps=parseInt(param[1]); fft_fps=parseInt(param[1]);
break; break;
@ -1279,8 +1322,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;
@ -1516,19 +1561,27 @@ 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")
console.log(parseInt(harr[1])); {
console.log(center_freq); e("openwebrx-panel-squelch").value=harr[1];
starting_offset_frequency = parseInt(harr[1])-center_freq; updateSquelch();
}
else if(harr[0]=="freq")
{
console.log(parseInt(harr[1]));
console.log(center_freq);
starting_offset_frequency = parseInt(harr[1])-center_freq;
} }
}); });
@ -1569,6 +1622,8 @@ function audio_preinit()
function audio_init() function audio_init()
{ {
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();
@ -1613,6 +1668,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()
@ -1653,17 +1709,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)
@ -1686,7 +1743,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;
@ -1706,9 +1763,11 @@ function add_canvas()
canvases.push(new_canvas); canvases.push(new_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);
@ -1765,7 +1824,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();
@ -1774,6 +1833,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;
@ -1850,6 +1945,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++)
@ -1859,13 +1962,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");
} }
/* /*
@ -1895,6 +1998,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();
@ -1909,6 +2186,7 @@ function openwebrx_init()
(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);
@ -2002,9 +2280,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";
@ -2093,6 +2376,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)
@ -2101,7 +2385,7 @@ function place_panels(function_apply)
p.style.right=(e("webrx-canvas-container").offsetWidth-e("webrx-canvas-container").clientWidth).toString()+"px"; //get scrollbar width p.style.right=(e("webrx-canvas-container").offsetWidth-e("webrx-canvas-container").clientWidth).toString()+"px"; //get scrollbar width
p.style.bottom=y.toString()+"px"; p.style.bottom=y.toString()+"px";
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);
} }
} }
@ -2125,3 +2409,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();
}

File diff suppressed because it is too large Load Diff

View File

View File

@ -1,251 +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
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
self.fft_averages = 50
def chain(self,which):
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 | "
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 fastagc_ff 1024 | csdr convert_f_s16"+chain_end
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 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 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 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 = 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)
#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 )
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)
def read(self,size):
return self.process.stdout.read(size)
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
self.running = False
def restart(self):
self.stop()
self.start()
def __del__(self):
self.stop()
del(self.process)

230
rxws.py
View File

@ -1,9 +1,9 @@
""" """
rxws: WebSocket methods implemented for OpenWebRX rxws: WebSocket methods implemented for OpenWebRX
This file is part of OpenWebRX, This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI. an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu> Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@ -26,110 +26,110 @@ import select
import code import code
class WebSocketException(Exception): class WebSocketException(Exception):
pass pass
def handshake(myself): def handshake(myself):
my_client_id=myself.path[4:] my_client_id=myself.path[4:]
my_headers=myself.headers.items() my_headers=myself.headers.items()
my_header_keys=map(lambda x:x[0],my_headers) my_header_keys=map(lambda x:x[0],my_headers)
h_key_exists=lambda x:my_header_keys.count(x) h_key_exists=lambda x:my_header_keys.count(x)
h_value=lambda x:my_headers[my_header_keys.index(x)][1] h_value=lambda x:my_headers[my_header_keys.index(x)][1]
#print "The Lambdas(tm)" #print "The Lambdas(tm)"
#print h_key_exists("upgrade") #print h_key_exists("upgrade")
#print h_value("upgrade") #print h_value("upgrade")
#print h_key_exists("sec-websocket-key") #print h_key_exists("sec-websocket-key")
if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")): if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")):
raise WebSocketException raise WebSocketException
ws_key=h_value("sec-websocket-key") ws_key=h_value("sec-websocket-key")
ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest()) ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest())
#A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')] #A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')]
myself.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n") myself.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n")
def get_header(size): def get_header(size):
#this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php #this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php
ws_first_byte=0b10000010 # FIN=1, OP=2 ws_first_byte=0b10000010 # FIN=1, OP=2
if(size>125): if(size>125):
ws_second_byte=126 # The following two bytes will indicate frame size ws_second_byte=126 # The following two bytes will indicate frame size
extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP
else: else:
ws_second_byte=size ws_second_byte=size
#256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data] #256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data]
extended_size="" extended_size=""
return chr(ws_first_byte)+chr(ws_second_byte)+extended_size return chr(ws_first_byte)+chr(ws_second_byte)+extended_size
def code_payload(data, masking_key=""): def code_payload(data, masking_key=""):
# both encode or decode # both encode or decode
if masking_key=="": if masking_key=="":
key = (61, 84, 35, 6) key = (61, 84, 35, 6)
else: else:
key = [ord(i) for i in masking_key] key = [ord(i) for i in masking_key]
encoded="" encoded=""
for i in range(0,len(data)): for i in range(0,len(data)):
encoded+=chr(ord(data[i])^key[i%4]) encoded+=chr(ord(data[i])^key[i%4])
return encoded return encoded
def xxdg(data): def xxdg(data):
output="" output=""
for i in range(0,len(data)/8): for i in range(0,len(data)/8):
output+=xxd(data[i:i+8]) output+=xxd(data[i:i+8])
if i%2: output+="\n" if i%2: output+="\n"
else: output+=" " else: output+=" "
return output return output
def xxd(data): def xxd(data):
#diagnostic purposes only #diagnostic purposes only
output="" output=""
for d in data: for d in data:
output+=hex(ord(d))[2:].zfill(2)+" " output+=hex(ord(d))[2:].zfill(2)+" "
return output return output
#for R/W the WebSocket, use recv/send #for R/W the WebSocket, use recv/send
#for reading the TCP socket, use readsock #for reading the TCP socket, use readsock
#for writing the TCP socket, use myself.wfile.write and flush #for writing the TCP socket, use myself.wfile.write and flush
def readsock(myself,size,blocking): def readsock(myself,size,blocking):
#http://thenestofheliopolis.blogspot.hu/2011/01/how-to-implement-non-blocking-two-way.html #http://thenestofheliopolis.blogspot.hu/2011/01/how-to-implement-non-blocking-two-way.html
if blocking: if blocking:
return myself.rfile.read(size) return myself.rfile.read(size)
else: else:
poll = select.poll() poll = select.poll()
poll.register(myself.rfile.fileno(), select.POLLIN or select.POLLPRI) poll.register(myself.rfile.fileno(), select.POLLIN or select.POLLPRI)
fd = poll.poll(0) #timeout is 0 fd = poll.poll(0) #timeout is 0
if len(fd): if len(fd):
f = fd[0] f = fd[0]
if f[1] > 0: if f[1] > 0:
return myself.rfile.read(size) return myself.rfile.read(size)
return "" return ""
def recv(myself, blocking=False, debug=False): def recv(myself, blocking=False, debug=False):
bufsize=70000 bufsize=70000
#myself.connection.setblocking(blocking) #umm... we cannot do that with rfile #myself.connection.setblocking(blocking) #umm... we cannot do that with rfile
if debug: print "ws_recv begin" if debug: print "ws_recv begin"
try: try:
data=readsock(myself,6,blocking) data=readsock(myself,6,blocking)
#print "rxws.recv bytes:",xxd(data) #print "rxws.recv bytes:",xxd(data)
except: except:
if debug: print "ws_recv error" if debug: print "ws_recv error"
return "" return ""
if debug: print "ws_recv recved" if debug: print "ws_recv recved"
if(len(data)==0): return "" if(len(data)==0): return ""
fin=ord(data[0])&128!=0 fin=ord(data[0])&128!=0
is_text_frame=ord(data[0])&15==1 is_text_frame=ord(data[0])&15==1
length=ord(data[1])&0x7f length=ord(data[1])&0x7f
data+=readsock(myself,length,blocking) data+=readsock(myself,length,blocking)
#print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data) #print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data)
has_one_byte_length=length<125 has_one_byte_length=length<125
masked=ord(data[1])&0x80!=0 masked=ord(data[1])&0x80!=0
#print "len=", length, len(data)-2 #print "len=", length, len(data)-2
#print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked) #print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked)
#print xxd(data) #print xxd(data)
if fin and is_text_frame and has_one_byte_length: if fin and is_text_frame and has_one_byte_length:
if masked: if masked:
return code_payload(data[6:], data[2:6]) return code_payload(data[6:], data[2:6])
else: else:
return data[2:] return data[2:]
#Useful links for ideas on WebSockets: #Useful links for ideas on WebSockets:
# http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side # http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
@ -138,34 +138,34 @@ def recv(myself, blocking=False, debug=False):
def flush(myself): def flush(myself):
myself.wfile.flush() myself.wfile.flush()
#or the socket, not the rfile: #or the socket, not the rfile:
#lR,lW,lX = select.select([],[myself.connection,],[],60) #lR,lW,lX = select.select([],[myself.connection,],[],60)
def send(myself, data, begin_id="", debug=0): def send(myself, data, begin_id="", debug=0):
base_frame_size=35000 #could guess by MTU? base_frame_size=35000 #could guess by MTU?
debug=0 debug=0
#try: #try:
while True: while True:
counter=0 counter=0
from_end=len(data)-counter from_end=len(data)-counter
if from_end+len(begin_id)>base_frame_size: if from_end+len(begin_id)>base_frame_size:
data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)] data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)]
header=get_header(len(data_to_send)) header=get_header(len(data_to_send))
flush(myself) flush(myself)
myself.wfile.write(header+data_to_send) myself.wfile.write(header+data_to_send)
flush(myself) flush(myself)
if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header)) if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header))
else: else:
data_to_send=begin_id+data[counter:] data_to_send=begin_id+data[counter:]
header=get_header(len(data_to_send)) header=get_header(len(data_to_send))
flush(myself) flush(myself)
myself.wfile.write(header+data_to_send) myself.wfile.write(header+data_to_send)
flush(myself) flush(myself)
if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header)) if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header))
#if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send) #if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send)
break break
counter+=base_frame_size-len(begin_id) counter+=base_frame_size-len(begin_id)
#except: #except:
# pass # pass

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -1,9 +1,9 @@
#!/usr/bin/python2 #!/usr/bin/python2
""" """
This file is part of OpenWebRX, This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI. an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu> Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@ -23,29 +23,28 @@
import config_webrx as cfg, time, subprocess import config_webrx as cfg, time, subprocess
def run(continuously=True): 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 -qO- http://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1"
#print "[openwebrx-sdrhu]", cmd #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
if "UPDATE:" in returned: if "UPDATE:" in returned:
retrytime_mins = 20 retrytime_mins = 20
value=returned.split("UPDATE:")[1].split("\n",1)[0] value=returned.split("UPDATE:")[1].split("\n",1)[0]
if value.startswith("SUCCESS"): if value.startswith("SUCCESS"):
print "[openwebrx-sdrhu] Update succeeded! "+firsttime print "[openwebrx-sdrhu] Update succeeded! "+firsttime
firsttime="" firsttime=""
else: else:
print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value
else: else:
retrytime_mins = 2 retrytime_mins = 2
print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!" print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!"
if not continuously: break if not continuously: break
time.sleep(60*retrytime_mins) time.sleep(60*retrytime_mins)
if __name__=="__main__": if __name__=="__main__":
run(False) run(False)