merge recent openwebrx changes into our work
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,3 @@ | |||||||
| *.pyc | *.pyc | ||||||
|  | *.swp | ||||||
|  | tags | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,21 +1,26 @@ | |||||||
| OpenWebRX | OpenWebRX | ||||||
| ========= | ========= | ||||||
|  |  | ||||||
|  | [:floppy_disk: Setup guide for Ubuntu](http://blog.sdr.hu/2015/06/30/quick-setup-openwebrx.html)  |  [:blue_book: Knowledge base on the Wiki](https://github.com/simonyiszk/openwebrx/wiki/)  |  [:earth_americas: Receivers on SDR.hu](http://sdr.hu/)  | ||||||
|  |  | ||||||
| OpenWebRX is a multi-user SDR receiver software with a web interface. | OpenWebRX is a multi-user SDR receiver software with a web interface. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 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 <canvas>. | - it extensively uses HTML5 features like WebSocket, Web Audio API, and <canvas>, | ||||||
| - it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28), | - it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28), | ||||||
| - currently supports RTL-SDR and HackRF; other SDR hardware may be easily added. | - currently supports RTL-SDR, HackRF, SDRplay, AirSpy and many other devices, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/">OpenWebRX Wiki</a>, | ||||||
|  | - it has a 3D waterfall display: | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **News (2015-08-18)** | **News (2015-08-18)** | ||||||
| - My BSc. thesis written on OpenWebRX is <a href="http://openwebrx.org/bsc-thesis.pdf">available here.</a> | - My BSc. thesis written on OpenWebRX is <a href="https://sdr.hu/static/bsc-thesis.pdf">available here.</a> | ||||||
| - Several bugs were fixed to improve reliability and stability. | - Several bugs were fixed to improve reliability and stability. | ||||||
| - OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.) | - OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.) | ||||||
| - OpenWebRX now uses <a href="https://github.com/simonyiszk/csdr#sdrjs">sdr.js</a> (*libcsdr* compiled to JavaScript) for some client-side DSP tasks.  | - OpenWebRX now uses <a href="https://github.com/simonyiszk/csdr#sdrjs">sdr.js</a> (*libcsdr* compiled to JavaScript) for some client-side DSP tasks.  | ||||||
| @@ -28,13 +33,20 @@ It has the following features: | |||||||
| - OpenWebRX now supports URLs like: `http://localhost:8073/#freq=145555000,mod=usb` | - OpenWebRX now supports URLs like: `http://localhost:8073/#freq=145555000,mod=usb` | ||||||
| - UI improvements were made, thanks to John Seamons and Gnoxter. | - UI improvements were made, thanks to John Seamons and Gnoxter. | ||||||
|  |  | ||||||
| > When upgrading OpenWebRX, please make sure that you also upgrade *csdr*, and install the new dependency, *ncat*! | **News (2017-04-04)** | ||||||
|  | - *ncat* has been replaced with a custom implementation called *nmux* due to a bug that caused regular crashes on some machines. The *nmux* tool is part of the *csdr* package. | ||||||
|  | - Most consumer SDR devices are supported via <a href="https://github.com/rxseger/rx_tools">rx_tools</a>, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX">OpenWebRX Wiki</a> on that. | ||||||
|  |  | ||||||
|  | **News (2017-07-12)** | ||||||
|  | - OpenWebRX now has a BPSK31 demodulator and a 3D waterfall display. | ||||||
|  |  | ||||||
|  | > When upgrading OpenWebRX, please make sure that you also upgrade *csdr*! | ||||||
|  |  | ||||||
| ## OpenWebRX servers on SDR.hu | ## OpenWebRX servers on SDR.hu | ||||||
|  |  | ||||||
| [SDR.hu](http://sdr.hu) is a site which lists the active, public OpenWebRX servers. Your receiver [can also be part of it](http://sdr.hu/openwebrx), if you want. | [SDR.hu](http://sdr.hu) is a site which lists the active, public OpenWebRX servers. Your receiver [can also be part of it](http://sdr.hu/openwebrx), if you want. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Setup | ## Setup | ||||||
|  |  | ||||||
| @@ -44,11 +56,6 @@ First you will need to install the dependencies: | |||||||
|  |  | ||||||
| - <a href="https://github.com/simonyiszk/csdr">libcsdr</a> | - <a href="https://github.com/simonyiszk/csdr">libcsdr</a> | ||||||
| - <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</a> | - <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</a> | ||||||
| - ncat (On Debian/Ubuntu, it is in the *nmap* package).  |  | ||||||
|  |  | ||||||
| > By the way, *nmap* is a tool commonly used for auditing network security, and it is not used by OpenWebRX in any way. We need to install it, because the *ncat* command is packaged with it. |  | ||||||
| > |  | ||||||
| > *ncat* is a better *netcat* alternative, which is used by OpenWebRX for internally distributing the I/Q data stream. It also solves the problem of having different versions of *netcat* on different Linux distributions, which are not compatible by their command-line arguments. |  | ||||||
|  |  | ||||||
| After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server: | After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server: | ||||||
|  |  | ||||||
| @@ -62,7 +69,7 @@ Please note that the server is also listening on the following ports (on localho | |||||||
|  |  | ||||||
| Now the next step is to customize the parameters of your server in `config_webrx.py`. | Now the next step is to customize the parameters of your server in `config_webrx.py`. | ||||||
|  |  | ||||||
| Actually, if you do something cool with OpenWebRX (or just have a problem), please drop me a mail:   | Actually, if you do something cool with OpenWebRX, please drop me a mail:   | ||||||
| *Andras Retzler, HA7ILM <randras@sdr.hu>* | *Andras Retzler, HA7ILM <randras@sdr.hu>* | ||||||
|  |  | ||||||
| ## Usage tips | ## Usage tips | ||||||
| @@ -85,4 +92,4 @@ If you want to run OpenWebRX on a remote server instead of *localhost*, do not f | |||||||
|  |  | ||||||
| OpenWebRX is available under Affero GPL v3 license (<a href="https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)">summary</a>). | OpenWebRX is available under Affero GPL v3 license (<a href="https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)">summary</a>). | ||||||
|  |  | ||||||
| OpenWebRX is also available under a commercial license on request. Please contact me at the address *<randras@sdr.hu>* for other licensing options.  | OpenWebRX is also available under a commercial license on request. Please contact me at the address *<randras@sdr.hu>* for licensing options.  | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								config_webrx.py
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								config_webrx.py
									
									
									
									
									
								
							| @@ -3,9 +3,9 @@ | |||||||
| """ | """ | ||||||
| config_webrx: configuration options for OpenWebRX | 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,34 +67,49 @@ sdrhu_key = "" | |||||||
| sdrhu_public_listing = False | sdrhu_public_listing = False | ||||||
|  |  | ||||||
| # ==== DSP/RX settings ==== | # ==== DSP/RX settings ==== | ||||||
| dsp_plugin="csdr" |  | ||||||
| fft_fps=9 | fft_fps=9 | ||||||
| fft_size=4096 | fft_size=4096 #Should be power of 2 | ||||||
| samp_rate = 250000 | fft_voverlap_factor=0.3 #If fft_voverlap_factor is above 0, multiple FFTs will be used for creating a line on the diagram. | ||||||
|  |  | ||||||
| center_freq = 145525000 | # samp_rate = 250000 | ||||||
|  | samp_rate = 2400000 | ||||||
|  | center_freq = 144250000 | ||||||
| rf_gain = 5 #in dB. For an RTL-SDR, rf_gain=0 will set the tuner to auto gain mode, else it will be in manual gain mode. | rf_gain = 5 #in dB. For an RTL-SDR, rf_gain=0 will set the tuner to auto gain mode, else it will be in manual gain mode. | ||||||
| ppm = 0 | ppm = 0 | ||||||
|  |  | ||||||
| audio_compression="adpcm" #valid values: "adpcm", "none" | audio_compression="adpcm" #valid values: "adpcm", "none" | ||||||
| fft_compression="adpcm" #valid values: "adpcm", "none" | fft_compression="adpcm" #valid values: "adpcm", "none" | ||||||
|  |  | ||||||
|  | digimodes_enable=True #Decoding digimodes come with higher CPU usage.  | ||||||
|  | digimodes_fft_size=1024 | ||||||
|  |  | ||||||
| start_rtl_thread=True | start_rtl_thread=True | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | Note: if you experience audio underruns while CPU usage is 100%, you can:  | ||||||
|  | - decrease `samp_rate`, | ||||||
|  | - set `fft_voverlap_factor` to 0, | ||||||
|  | - decrease `fft_fps` and `fft_size`, | ||||||
|  | - limit the number of users by decreasing `max_clients`. | ||||||
|  | """ | ||||||
|  |  | ||||||
| # ==== I/Q sources ==== | # ==== I/Q sources ==== | ||||||
| # (Uncomment the appropriate by removing # characters at the beginning of the corresponding lines.) | # (Uncomment the appropriate by removing # characters at the beginning of the corresponding lines.) | ||||||
|  |  | ||||||
| # There are guides for setting may different SDR hardware including AirSpy, AFEDRI-SDR, RTL-SDR in direct sampling mode, etc. in the Wiki: | ################################################################################################# | ||||||
| #       https://github.com/simonyiszk/openwebrx/wiki | # Is my SDR hardware supported?                                                                 # | ||||||
|  | # Check here: https://github.com/simonyiszk/openwebrx/wiki#guides-for-receiver-hardware-support # | ||||||
|  | ################################################################################################# | ||||||
|  |  | ||||||
| # You can use other SDR hardware as well, by giving your own command that outputs the I/Q samples... Some examples of configuration are available here (default is RTL-SDR): | # You can use other SDR hardware as well, by giving your own command that outputs the I/Q samples... Some examples of configuration are available here (default is RTL-SDR): | ||||||
|  |  | ||||||
| # >> RTL-SDR via rtl_sdr | # >> RTL-SDR via rtl_sdr | ||||||
|  |  | ||||||
| start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) | start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) | ||||||
| format_conversion="csdr convert_u8_f" | format_conversion="csdr convert_u8_f" | ||||||
|  |  | ||||||
| #start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l16 -a0 -q -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) | #lna_gain=8 | ||||||
|  | #rf_amp=1 | ||||||
|  | #start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm, rf_amp=rf_amp, lna_gain=lna_gain) | ||||||
| #format_conversion="csdr convert_s8_f" | #format_conversion="csdr convert_s8_f" | ||||||
| """ | """ | ||||||
| To use a HackRF, compile the HackRF host tools from its "stdout" branch: | To use a HackRF, compile the HackRF host tools from its "stdout" branch: | ||||||
| @@ -117,20 +132,29 @@ To use a HackRF, compile the HackRF host tools from its "stdout" branch: | |||||||
| #format_conversion="csdr convert_s16_f | csdr gain_ff 30" | #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. | ||||||
| #start_rtl_command="(while true; do cat my_iq_file.raw; done) | csdr flowcontrol {sr} 20 ".format(sr=samp_rate*2*1.05) | #start_rtl_command="(while true; do cat my_iq_file.raw; done) | csdr flowcontrol {sr} 20 ".format(sr=samp_rate*2*1.05) | ||||||
| #format_conversion="csdr convert_u8_f" | #format_conversion="csdr convert_u8_f" | ||||||
|  |  | ||||||
|  | #>> The rx_sdr command works with a variety of SDR harware: RTL-SDR, HackRF, SDRplay, UHD, Airspy, Red Pitaya, audio devices, etc.  | ||||||
|  | # It will auto-detect your SDR hardware if the following tools are installed: | ||||||
|  | # * the vendor provided driver and library,  | ||||||
|  | # * the vendor-specific SoapySDR wrapper library,  | ||||||
|  | # * and SoapySDR itself. | ||||||
|  | # Check out this article on the OpenWebRX Wiki: https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX/ | ||||||
|  | #start_rtl_command="rx_sdr -F CF32 -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) | ||||||
|  | #format_conversion="" | ||||||
|  |  | ||||||
| # >> gr-osmosdr signal source using GNU Radio (follow this guide: https://github.com/simonyiszk/openwebrx/wiki/Using-GrOsmoSDR-as-signal-source) | # >> gr-osmosdr signal source using GNU Radio (follow this guide: https://github.com/simonyiszk/openwebrx/wiki/Using-GrOsmoSDR-as-signal-source) | ||||||
| #start_rtl_command="cat /tmp/osmocom_fifo" | #start_rtl_command="cat /tmp/osmocom_fifo" | ||||||
| #format_conversion="" | #format_conversion="" | ||||||
|  |  | ||||||
| # ==== Misc options ==== | # ==== Misc settings ==== | ||||||
|  |  | ||||||
| shown_center_freq = center_freq #you can change this if you use an upconverter | shown_center_freq = center_freq #you can change this if you use an upconverter | ||||||
|  |  | ||||||
| @@ -146,16 +170,43 @@ iq_server_port = 4951 #TCP port for ncat to listen on. It will send I/Q data ove | |||||||
|  |  | ||||||
| #access_log = "~/openwebrx_access.log" | #access_log = "~/openwebrx_access.log" | ||||||
|  |  | ||||||
| waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff,  0xfff775ff, 0xff8a8aff, 0xb20000ff]" | # ==== Color themes ==== | ||||||
| waterfall_min_level = -115 #in dB |  | ||||||
| waterfall_max_level = 0 |  | ||||||
| #A guide is available to help you set these values: https://github.com/simonyiszk/openwebrx/wiki/Calibrating-waterfall-display-levels | #A guide is available to help you set these values: https://github.com/simonyiszk/openwebrx/wiki/Calibrating-waterfall-display-levels | ||||||
|  |  | ||||||
|  | ### default theme by teejez: | ||||||
|  | waterfall_colors = "[0x000000ff,0x0000ffff,0x00ffffff,0x00ff00ff,0xffff00ff,0xff0000ff,0xff00ffff,0xffffffff]" | ||||||
|  | waterfall_min_level = -88 #in dB | ||||||
|  | waterfall_max_level = -20 | ||||||
|  | waterfall_auto_level_margin = (5, 40) | ||||||
|  | ### old theme by HA7ILM: | ||||||
|  | #waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff,  0xfff775ff, 0xff8a8aff, 0xb20000ff]" | ||||||
|  | #waterfall_min_level = -115 #in dB | ||||||
|  | #waterfall_max_level = 0 | ||||||
|  | #waterfall_auto_level_margin = (20, 30) | ||||||
|  | ##For the old colors, you might also want to set [fft_voverlap_factor] to 0. | ||||||
|  |  | ||||||
|  | #Note: When the auto waterfall level button is clicked, the following happens: | ||||||
|  | #   [waterfall_min_level] = [current_min_power_level] - [waterfall_auto_level_margin[0]] | ||||||
|  | #   [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin[1]] | ||||||
|  | # | ||||||
|  | #   ___|____________________________________|____________________________________|____________________________________|___> signal power | ||||||
|  | #        \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level          | \_waterfall_auto_level_margin[1]_/ | ||||||
|  | #                                                      current_max_power_level __| | ||||||
|  |  | ||||||
|  | # 3D view settings | ||||||
|  | mathbox_waterfall_frequency_resolution = 128 #bins | ||||||
|  | mathbox_waterfall_history_length = 10 #seconds | ||||||
|  | mathbox_waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff,  0xfff775ff, 0xff8a8aff, 0xb20000ff]" | ||||||
|  |  | ||||||
|  | # === Experimental settings === | ||||||
| #Warning! The settings below are very experimental. | #Warning! The settings below are very experimental. | ||||||
| csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr. | csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr. | ||||||
| csdr_print_bufsizes = False  # This prints the buffer sizes used for csdr processes. | csdr_print_bufsizes = False  # This prints the buffer sizes used for csdr processes. | ||||||
| csdr_through = False # Setting this True will print out how much data is going into the DSP chains. | csdr_through = False # Setting this True will print out how much data is going into the DSP chains. | ||||||
|  |  | ||||||
|  | nmux_memory = 50 #in megabytes. This sets the approximate size of the circular buffer used by nmux. | ||||||
|  |  | ||||||
| #Look up external IP address automatically from icanhazip.com, and use it as [server_hostname] | #Look up external IP address automatically from icanhazip.com, and use it as [server_hostname] | ||||||
| """ | """ | ||||||
| print "[openwebrx-config] Detecting external IP address..." | print "[openwebrx-config] Detecting external IP address..." | ||||||
|   | |||||||
							
								
								
									
										453
									
								
								csdr.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										453
									
								
								csdr.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,453 @@ | |||||||
|  | """ | ||||||
|  | OpenWebRX csdr plugin: do the signal processing with csdr | ||||||
|  |  | ||||||
|  |     This file is part of OpenWebRX, | ||||||
|  |     an open-source SDR receiver software with a web UI. | ||||||
|  |     Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu> | ||||||
|  |  | ||||||
|  |     This program is free software: you can redistribute it and/or modify | ||||||
|  |     it under the terms of the GNU Affero General Public License as | ||||||
|  |     published by the Free Software Foundation, either version 3 of the | ||||||
|  |     License, or (at your option) any later version. | ||||||
|  |  | ||||||
|  |     This program is distributed in the hope that it will be useful, | ||||||
|  |     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |     GNU Affero General Public License for more details. | ||||||
|  |  | ||||||
|  |     You should have received a copy of the GNU Affero General Public License | ||||||
|  |     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import subprocess | ||||||
|  | import time | ||||||
|  | import os | ||||||
|  | import code | ||||||
|  | import signal | ||||||
|  | import fcntl | ||||||
|  |  | ||||||
|  | class dsp: | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         self.samp_rate = 250000 | ||||||
|  |         self.output_rate = 11025 #this is default, and cannot be set at the moment | ||||||
|  |         self.fft_size = 1024 | ||||||
|  |         self.fft_fps = 5 | ||||||
|  |         self.offset_freq = 0 | ||||||
|  |         self.low_cut = -4000 | ||||||
|  |         self.high_cut = 4000 | ||||||
|  |         self.bpf_transition_bw = 320 #Hz, and this is a constant | ||||||
|  |         self.ddc_transition_bw_rate = 0.15 # of the IF sample rate | ||||||
|  |         self.running = False | ||||||
|  |         self.secondary_processes_running = False | ||||||
|  |         self.audio_compression = "none" | ||||||
|  |         self.fft_compression = "none" | ||||||
|  |         self.demodulator = "nfm" | ||||||
|  |         self.name = "csdr" | ||||||
|  |         self.format_conversion = "csdr convert_u8_f" | ||||||
|  |         self.base_bufsize = 512 | ||||||
|  |         self.nc_port = 4951 | ||||||
|  |         self.csdr_dynamic_bufsize = False | ||||||
|  |         self.csdr_print_bufsizes = False | ||||||
|  |         self.csdr_through = False | ||||||
|  |         self.squelch_level = 0 | ||||||
|  |         self.fft_averages = 50 | ||||||
|  |         self.iqtee = False | ||||||
|  |         self.iqtee2 = False | ||||||
|  |         self.secondary_demodulator = None | ||||||
|  |         self.secondary_fft_size = 1024 | ||||||
|  |         self.secondary_process_fft = None | ||||||
|  |         self.secondary_process_demod = None | ||||||
|  |         self.pipe_names=["bpf_pipe", "shift_pipe", "squelch_pipe", "smeter_pipe", "iqtee_pipe", "iqtee2_pipe"] | ||||||
|  |         self.secondary_pipe_names=["secondary_shift_pipe"] | ||||||
|  |         self.secondary_offset_freq = 1000 | ||||||
|  |  | ||||||
|  |     def chain(self,which): | ||||||
|  |         if which in [ "dmr", "dstar", "nxdn", "ysf" ]: | ||||||
|  |             self.set_output_rate(48000) | ||||||
|  |         else: | ||||||
|  |             self.set_output_rate(11025) | ||||||
|  |         any_chain_base="nc -v 127.0.0.1 {nc_port} | " | ||||||
|  |         if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | " | ||||||
|  |         if self.csdr_through: any_chain_base+="csdr through | " | ||||||
|  |         any_chain_base+=self.format_conversion+(" | " if  self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | " | ||||||
|  |         if which == "fft": | ||||||
|  |             fft_chain_base = any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | " + \ | ||||||
|  |                 ("csdr logpower_cf -70 | " if self.fft_averages == 0 else "csdr logaveragepower_cf -70 {fft_size} {fft_averages} | ") + \ | ||||||
|  |                 "csdr fft_exchange_sides_ff {fft_size}" | ||||||
|  |             if self.fft_compression=="adpcm": | ||||||
|  |                 return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}" | ||||||
|  |             else: | ||||||
|  |                 return fft_chain_base | ||||||
|  |         chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | " | ||||||
|  |         if self.secondary_demodulator: | ||||||
|  |             chain_begin+="csdr tee {iqtee_pipe} | " | ||||||
|  |             chain_begin+="csdr tee {iqtee2_pipe} | "  | ||||||
|  |         chain_end = "" | ||||||
|  |         if self.audio_compression=="adpcm": | ||||||
|  |             chain_end = " | csdr encode_ima_adpcm_i16_u8" | ||||||
|  |         if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr convert_f_s16"+chain_end | ||||||
|  |         if which in [ "dstar", "nxdn" ]: | ||||||
|  |             c = chain_begin | ||||||
|  |             c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16" | ||||||
|  |             if which == "dstar": | ||||||
|  |                 c += " | dsd -fd" | ||||||
|  |             elif which == "nxdn": | ||||||
|  |                 c += " | dsd -fi" | ||||||
|  |             c += " -i - -o - -u 2 -g 10" | ||||||
|  |             c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --input-buffer 160 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 220" | ||||||
|  |             c += chain_end | ||||||
|  |             return c | ||||||
|  |         elif which == "dmr": | ||||||
|  |             c = chain_begin | ||||||
|  |             c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16" | ||||||
|  |             c += " | rrc_filter | gfsk_demodulator | dmr_decoder --fifo {meta_pipe} | mbe_synthesizer" | ||||||
|  |             c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256" | ||||||
|  |             c += chain_end | ||||||
|  |             return c | ||||||
|  |         elif which == "ysf": | ||||||
|  |             c = chain_begin | ||||||
|  |             c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16" | ||||||
|  |             c += " | rrc_filter | gfsk_demodulator | ysf_decoder --fifo {meta_pipe} | mbe_synthesizer -y" | ||||||
|  |             c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256" | ||||||
|  |             c += chain_end | ||||||
|  |             return c | ||||||
|  |         elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end | ||||||
|  |         elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end | ||||||
|  |  | ||||||
|  |     def secondary_chain(self, which): | ||||||
|  |         secondary_chain_base="cat {input_pipe} | " | ||||||
|  |         if which == "fft": | ||||||
|  |             return secondary_chain_base+"csdr realpart_cf | csdr fft_fc {secondary_fft_input_size} {secondary_fft_block_size} | csdr logpower_cf -70 " + (" | csdr compress_fft_adpcm_f_u8 {secondary_fft_size}" if self.fft_compression=="adpcm" else "") | ||||||
|  |         elif which == "bpsk31": | ||||||
|  |             return secondary_chain_base + "csdr shift_addition_cc --fifo {secondary_shift_pipe} | " + \ | ||||||
|  |                     "csdr bandpass_fir_fft_cc $(csdr '=-(31.25)/{if_samp_rate}') $(csdr '=(31.25)/{if_samp_rate}') $(csdr '=31.25/{if_samp_rate}') | " + \ | ||||||
|  |                     "csdr simple_agc_cc 0.001 0.5 | " + \ | ||||||
|  |                     "csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q | " + \ | ||||||
|  |                     "CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8 | " + \ | ||||||
|  |                     "CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8" | ||||||
|  |  | ||||||
|  |     def set_secondary_demodulator(self, what): | ||||||
|  |         self.secondary_demodulator = what | ||||||
|  |  | ||||||
|  |     def secondary_fft_block_size(self): | ||||||
|  |         return (self.samp_rate/self.decimation)/(self.fft_fps*2) #*2 is there because we do FFT on real signal here | ||||||
|  |  | ||||||
|  |     def secondary_decimation(self): | ||||||
|  |         return 1 #currently unused | ||||||
|  |  | ||||||
|  |     def secondary_bpf_cutoff(self): | ||||||
|  |         if self.secondary_demodulator == "bpsk31": | ||||||
|  |              return (31.25/2) / self.if_samp_rate() | ||||||
|  |         return 0 | ||||||
|  |  | ||||||
|  |     def secondary_bpf_transition_bw(self): | ||||||
|  |         if self.secondary_demodulator == "bpsk31": | ||||||
|  |             return (31.25/2) / self.if_samp_rate() | ||||||
|  |         return 0 | ||||||
|  |  | ||||||
|  |     def secondary_samples_per_bits(self): | ||||||
|  |         if self.secondary_demodulator == "bpsk31": | ||||||
|  |             return int(round(self.if_samp_rate()/31.25))&~3 | ||||||
|  |         return 0 | ||||||
|  |  | ||||||
|  |     def secondary_bw(self): | ||||||
|  |         if self.secondary_demodulator == "bpsk31": | ||||||
|  |             return 31.25 | ||||||
|  |  | ||||||
|  |     def start_secondary_demodulator(self): | ||||||
|  |         if(not self.secondary_demodulator): return | ||||||
|  |         print "[openwebrx] starting secondary demodulator from IF input sampled at %d"%self.if_samp_rate() | ||||||
|  |         secondary_command_fft=self.secondary_chain("fft") | ||||||
|  |         secondary_command_demod=self.secondary_chain(self.secondary_demodulator) | ||||||
|  |         self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod + secondary_command_fft) | ||||||
|  |  | ||||||
|  |         secondary_command_fft=secondary_command_fft.format( \ | ||||||
|  |             input_pipe=self.iqtee_pipe, \ | ||||||
|  |             secondary_fft_input_size=self.secondary_fft_size, \ | ||||||
|  |             secondary_fft_size=self.secondary_fft_size, \ | ||||||
|  |             secondary_fft_block_size=self.secondary_fft_block_size(), \ | ||||||
|  |             ) | ||||||
|  |         secondary_command_demod=secondary_command_demod.format( \ | ||||||
|  |             input_pipe=self.iqtee2_pipe, \ | ||||||
|  |             secondary_shift_pipe=self.secondary_shift_pipe, \ | ||||||
|  |             secondary_decimation=self.secondary_decimation(), \ | ||||||
|  |             secondary_samples_per_bits=self.secondary_samples_per_bits(), \ | ||||||
|  |             secondary_bpf_cutoff=self.secondary_bpf_cutoff(), \ | ||||||
|  |             secondary_bpf_transition_bw=self.secondary_bpf_transition_bw(), \ | ||||||
|  |             if_samp_rate=self.if_samp_rate() | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         print "[openwebrx-dsp-plugin:csdr] secondary command (fft) =", secondary_command_fft | ||||||
|  |         print "[openwebrx-dsp-plugin:csdr] secondary command (demod) =", secondary_command_demod | ||||||
|  |         #code.interact(local=locals()) | ||||||
|  |         my_env=os.environ.copy() | ||||||
|  |         #if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1"; | ||||||
|  |         if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1"; | ||||||
|  |         self.secondary_process_fft = subprocess.Popen(secondary_command_fft, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) | ||||||
|  |         print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (fft)" | ||||||
|  |         self.secondary_process_demod = subprocess.Popen(secondary_command_demod, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) #TODO digimodes | ||||||
|  |         print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (demod)" #TODO digimodes | ||||||
|  |         self.secondary_processes_running = True | ||||||
|  |  | ||||||
|  |         #open control pipes for csdr and send initialization data | ||||||
|  |         # print "==========> 1" | ||||||
|  |         if self.secondary_shift_pipe != None: #TODO digimodes | ||||||
|  |             # print "==========> 2", self.secondary_shift_pipe | ||||||
|  |             self.secondary_shift_pipe_file=open(self.secondary_shift_pipe,"w") #TODO digimodes | ||||||
|  |             # print "==========> 3" | ||||||
|  |             self.set_secondary_offset_freq(self.secondary_offset_freq) #TODO digimodes | ||||||
|  |             # print "==========> 4" | ||||||
|  |  | ||||||
|  |         self.set_pipe_nonblocking(self.secondary_process_demod.stdout) | ||||||
|  |         self.set_pipe_nonblocking(self.secondary_process_fft.stdout) | ||||||
|  |  | ||||||
|  |     def set_secondary_offset_freq(self, value): | ||||||
|  |         self.secondary_offset_freq=value | ||||||
|  |         if self.secondary_processes_running: | ||||||
|  |             self.secondary_shift_pipe_file.write("%g\n"%(-float(self.secondary_offset_freq)/self.if_samp_rate())) | ||||||
|  |             self.secondary_shift_pipe_file.flush() | ||||||
|  |  | ||||||
|  |     def stop_secondary_demodulator(self): | ||||||
|  |         if self.secondary_processes_running == False: return | ||||||
|  |         self.try_delete_pipes(self.secondary_pipe_names) | ||||||
|  |         if self.secondary_process_fft: os.killpg(os.getpgid(self.secondary_process_fft.pid), signal.SIGTERM) | ||||||
|  |         if self.secondary_process_demod: os.killpg(os.getpgid(self.secondary_process_demod.pid), signal.SIGTERM) | ||||||
|  |         self.secondary_processes_running = False | ||||||
|  |  | ||||||
|  |     def read_secondary_demod(self, size): | ||||||
|  |         return self.secondary_process_demod.stdout.read(size) | ||||||
|  |  | ||||||
|  |     def read_secondary_fft(self, size): | ||||||
|  |         return self.secondary_process_fft.stdout.read(size) | ||||||
|  |  | ||||||
|  |     def get_secondary_demodulator(self): | ||||||
|  |         return self.secondary_demodulator | ||||||
|  |  | ||||||
|  |     def set_secondary_fft_size(self,secondary_fft_size): | ||||||
|  |         #to change this, restart is required | ||||||
|  |         self.secondary_fft_size=secondary_fft_size | ||||||
|  |  | ||||||
|  |     def set_audio_compression(self,what): | ||||||
|  |         self.audio_compression = what | ||||||
|  |  | ||||||
|  |     def set_fft_compression(self,what): | ||||||
|  |         self.fft_compression = what | ||||||
|  |  | ||||||
|  |     def get_fft_bytes_to_read(self): | ||||||
|  |         if self.fft_compression=="none": return self.fft_size*4 | ||||||
|  |         if self.fft_compression=="adpcm": return (self.fft_size/2)+(10/2) | ||||||
|  |  | ||||||
|  |     def get_secondary_fft_bytes_to_read(self): | ||||||
|  |         if self.fft_compression=="none": return self.secondary_fft_size*4 | ||||||
|  |         if self.fft_compression=="adpcm": return (self.secondary_fft_size/2)+(10/2) | ||||||
|  |  | ||||||
|  |     def set_samp_rate(self,samp_rate): | ||||||
|  |         #to change this, restart is required | ||||||
|  |         self.samp_rate=samp_rate | ||||||
|  |         self.decimation=1 | ||||||
|  |         while self.samp_rate/(self.decimation+1)>self.output_rate: | ||||||
|  |             self.decimation+=1 | ||||||
|  |         self.last_decimation=float(self.if_samp_rate())/self.output_rate | ||||||
|  |  | ||||||
|  |     def if_samp_rate(self): | ||||||
|  |         return self.samp_rate/self.decimation | ||||||
|  |  | ||||||
|  |     def get_name(self): | ||||||
|  |         return self.name | ||||||
|  |  | ||||||
|  |     def get_output_rate(self): | ||||||
|  |         return self.output_rate | ||||||
|  |  | ||||||
|  |     def set_output_rate(self,output_rate): | ||||||
|  |         self.output_rate=output_rate | ||||||
|  |         self.set_samp_rate(self.samp_rate) #as it depends on output_rate | ||||||
|  |  | ||||||
|  |     def set_demodulator(self,demodulator): | ||||||
|  |         #to change this, restart is required | ||||||
|  |         self.demodulator=demodulator | ||||||
|  |  | ||||||
|  |     def get_demodulator(self): | ||||||
|  |         return self.demodulator | ||||||
|  |  | ||||||
|  |     def set_fft_size(self,fft_size): | ||||||
|  |         #to change this, restart is required | ||||||
|  |         self.fft_size=fft_size | ||||||
|  |  | ||||||
|  |     def set_fft_fps(self,fft_fps): | ||||||
|  |         #to change this, restart is required | ||||||
|  |         self.fft_fps=fft_fps | ||||||
|  |  | ||||||
|  |     def set_fft_averages(self,fft_averages): | ||||||
|  |         #to change this, restart is required | ||||||
|  |         self.fft_averages=fft_averages | ||||||
|  |  | ||||||
|  |     def fft_block_size(self): | ||||||
|  |         if self.fft_averages == 0: return self.samp_rate/self.fft_fps | ||||||
|  |         else: return self.samp_rate/self.fft_fps/self.fft_averages | ||||||
|  |  | ||||||
|  |     def set_format_conversion(self,format_conversion): | ||||||
|  |         self.format_conversion=format_conversion | ||||||
|  |  | ||||||
|  |     def set_offset_freq(self,offset_freq): | ||||||
|  |         self.offset_freq=offset_freq | ||||||
|  |         if self.running: | ||||||
|  |             self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate)) | ||||||
|  |             self.shift_pipe_file.flush() | ||||||
|  |  | ||||||
|  |     def set_bpf(self,low_cut,high_cut): | ||||||
|  |         self.low_cut=low_cut | ||||||
|  |         self.high_cut=high_cut | ||||||
|  |         if self.running: | ||||||
|  |             self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) ) | ||||||
|  |             self.bpf_pipe_file.flush() | ||||||
|  |  | ||||||
|  |     def get_bpf(self): | ||||||
|  |         return [self.low_cut, self.high_cut] | ||||||
|  |  | ||||||
|  |     def set_squelch_level(self, squelch_level): | ||||||
|  |         self.squelch_level=squelch_level | ||||||
|  |         if self.running: | ||||||
|  |             self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) ) | ||||||
|  |             self.squelch_pipe_file.flush() | ||||||
|  |  | ||||||
|  |     def get_smeter_level(self): | ||||||
|  |         if self.running: | ||||||
|  |             line=self.smeter_pipe_file.readline() | ||||||
|  |             return float(line[:-1]) | ||||||
|  |  | ||||||
|  |     def mkfifo(self,path): | ||||||
|  |         try: | ||||||
|  |             os.unlink(path) | ||||||
|  |         except: | ||||||
|  |             pass | ||||||
|  |         os.mkfifo(path) | ||||||
|  |  | ||||||
|  |     def ddc_transition_bw(self): | ||||||
|  |         return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate)) | ||||||
|  |  | ||||||
|  |     def try_create_pipes(self, pipe_names, command_base): | ||||||
|  |         # print "try_create_pipes" | ||||||
|  |         for pipe_name in pipe_names: | ||||||
|  |             # print "\t"+pipe_name | ||||||
|  |             if "{"+pipe_name+"}" in command_base: | ||||||
|  |                 setattr(self, pipe_name, self.pipe_base_path+pipe_name) | ||||||
|  |                 self.mkfifo(getattr(self, pipe_name)) | ||||||
|  |             else: | ||||||
|  |                 setattr(self, pipe_name, None) | ||||||
|  |  | ||||||
|  |     def try_delete_pipes(self, pipe_names): | ||||||
|  |         for pipe_name in pipe_names: | ||||||
|  |             pipe_path = getattr(self,pipe_name,None) | ||||||
|  |             if pipe_path: | ||||||
|  |                 try: os.unlink(pipe_path) | ||||||
|  |                 except Exception as e: print "[openwebrx-dsp-plugin:csdr] try_delete_pipes() ::", e | ||||||
|  |  | ||||||
|  |     def set_pipe_nonblocking(self, pipe): | ||||||
|  |         flags = fcntl.fcntl(pipe, fcntl.F_GETFL) | ||||||
|  |         fcntl.fcntl(pipe, fcntl.F_SETFL, flags | os.O_NONBLOCK) | ||||||
|  |  | ||||||
|  |     def start(self): | ||||||
|  |         command_base=self.chain(self.demodulator) | ||||||
|  |  | ||||||
|  |         #create control pipes for csdr | ||||||
|  |         self.pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self)) | ||||||
|  |         # self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = None | ||||||
|  |  | ||||||
|  |         self.try_create_pipes(self.pipe_names, command_base) | ||||||
|  |  | ||||||
|  |         # if "{bpf_pipe}" in command_base: | ||||||
|  |             # self.bpf_pipe=pipe_base_path+"bpf" | ||||||
|  |             # self.mkfifo(self.bpf_pipe) | ||||||
|  |         # if "{shift_pipe}" in command_base: | ||||||
|  |             # self.shift_pipe=pipe_base_path+"shift" | ||||||
|  |             # self.mkfifo(self.shift_pipe) | ||||||
|  |         # if "{squelch_pipe}" in command_base: | ||||||
|  |             # self.squelch_pipe=pipe_base_path+"squelch" | ||||||
|  |             # self.mkfifo(self.squelch_pipe) | ||||||
|  |         # if "{smeter_pipe}" in command_base: | ||||||
|  |             # self.smeter_pipe=pipe_base_path+"smeter" | ||||||
|  |             # self.mkfifo(self.smeter_pipe) | ||||||
|  |         # if "{iqtee_pipe}" in command_base: | ||||||
|  |             # self.iqtee_pipe=pipe_base_path+"iqtee" | ||||||
|  |             # self.mkfifo(self.iqtee_pipe) | ||||||
|  |         # if "{iqtee2_pipe}" in command_base: | ||||||
|  |             # self.iqtee2_pipe=pipe_base_path+"iqtee2" | ||||||
|  |             # self.mkfifo(self.iqtee2_pipe) | ||||||
|  |  | ||||||
|  |         #run the command | ||||||
|  |         command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \ | ||||||
|  |             last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), fft_averages=self.fft_averages, \ | ||||||
|  |             bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(), ddc_transition_bw=self.ddc_transition_bw(), \ | ||||||
|  |             flowcontrol=int(self.samp_rate*2), start_bufsize=self.base_bufsize*self.decimation, nc_port=self.nc_port, \ | ||||||
|  |             squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe, iqtee_pipe=self.iqtee_pipe, iqtee2_pipe=self.iqtee2_pipe ) | ||||||
|  |  | ||||||
|  |         print "[openwebrx-dsp-plugin:csdr] Command =",command | ||||||
|  |         #code.interact(local=locals()) | ||||||
|  |         my_env=os.environ.copy() | ||||||
|  |         if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1"; | ||||||
|  |         if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1"; | ||||||
|  |         self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) | ||||||
|  |         self.running = True | ||||||
|  |  | ||||||
|  |         #open control pipes for csdr and send initialization data | ||||||
|  |         if self.bpf_pipe != None: | ||||||
|  |             self.bpf_pipe_file=open(self.bpf_pipe,"w") | ||||||
|  |             self.set_bpf(self.low_cut,self.high_cut) | ||||||
|  |         if self.shift_pipe != None: | ||||||
|  |             self.shift_pipe_file=open(self.shift_pipe,"w") | ||||||
|  |             self.set_offset_freq(self.offset_freq) | ||||||
|  |         if self.squelch_pipe != None: | ||||||
|  |             self.squelch_pipe_file=open(self.squelch_pipe,"w") | ||||||
|  |             self.set_squelch_level(self.squelch_level) | ||||||
|  |         if self.smeter_pipe != None: | ||||||
|  |             self.smeter_pipe_file=open(self.smeter_pipe,"r") | ||||||
|  |             self.set_pipe_nonblocking(self.smeter_pipe_file) | ||||||
|  |  | ||||||
|  |         self.start_secondary_demodulator() | ||||||
|  |  | ||||||
|  |     def read(self,size): | ||||||
|  |         return self.process.stdout.read(size) | ||||||
|  |  | ||||||
|  |     def stop(self): | ||||||
|  |         os.killpg(os.getpgid(self.process.pid), signal.SIGTERM) | ||||||
|  |         self.stop_secondary_demodulator() | ||||||
|  |         #if(self.process.poll()!=None):return # returns None while subprocess is running | ||||||
|  |         #while(self.process.poll()==None): | ||||||
|  |         #   #self.process.kill() | ||||||
|  |         #   print "killproc",os.getpgid(self.process.pid),self.process.pid | ||||||
|  |         #   os.killpg(self.process.pid, signal.SIGTERM) | ||||||
|  |         # | ||||||
|  |         #   time.sleep(0.1) | ||||||
|  |  | ||||||
|  |         self.try_delete_pipes(self.pipe_names) | ||||||
|  |  | ||||||
|  |         # if self.bpf_pipe: | ||||||
|  |             # try: os.unlink(self.bpf_pipe) | ||||||
|  |             # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe | ||||||
|  |         # if self.shift_pipe: | ||||||
|  |             # try: os.unlink(self.shift_pipe) | ||||||
|  |             # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe | ||||||
|  |         # if self.squelch_pipe: | ||||||
|  |             # try: os.unlink(self.squelch_pipe) | ||||||
|  |             # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe | ||||||
|  |         # if self.smeter_pipe: | ||||||
|  |             # try: os.unlink(self.smeter_pipe) | ||||||
|  |             # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe | ||||||
|  |         # if self.iqtee_pipe: | ||||||
|  |             # try: os.unlink(self.iqtee_pipe) | ||||||
|  |             # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee_pipe | ||||||
|  |         # if self.iqtee2_pipe: | ||||||
|  |             # try: os.unlink(self.iqtee2_pipe) | ||||||
|  |             # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee2_pipe | ||||||
|  |  | ||||||
|  |         self.running = False | ||||||
|  |  | ||||||
|  |     def restart(self): | ||||||
|  |         self.stop() | ||||||
|  |         self.start() | ||||||
|  |  | ||||||
|  |     def __del__(self): | ||||||
|  |         self.stop() | ||||||
|  |         del(self.process) | ||||||
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-3d-spectrum.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-3d-spectrum.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.9 KiB | 
							
								
								
									
										294
									
								
								htdocs/index.wrx
									
									
									
									
									
								
							
							
						
						
									
										294
									
								
								htdocs/index.wrx
									
									
									
									
									
								
							| @@ -1,9 +1,9 @@ | |||||||
| <!DOCTYPE HTML> | <!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,133 +20,171 @@ | |||||||
|  |  | ||||||
| --> | --> | ||||||
| <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]; | ||||||
| 		</script> |             var waterfall_auto_level_margin=%[WATERFALL_AUTO_LEVEL_MARGIN]; | ||||||
| 		<script src="sdr.js"></script> |             var server_enable_digimodes=%[DIGIMODES_ENABLE]; | ||||||
| 		<script src="openwebrx.js"></script> | 			var mathbox_waterfall_frequency_resolution=%[MATHBOX_WATERFALL_FRES]; | ||||||
| 		<link rel="stylesheet" type="text/css" href="openwebrx.css" /> | 			var mathbox_waterfall_history_length=%[MATHBOX_WATERFALL_THIST]; | ||||||
| 		<meta charset="utf-8"> | 			var mathbox_waterfall_colors=%[MATHBOX_WATERFALL_COLORS]; | ||||||
| 	</head> |         </script> | ||||||
| 	<body onload="openwebrx_init();"> |         <script src="sdr.js"></script> | ||||||
|  | 		<script src="mathbox-bundle.min.js"></script> | ||||||
|  |         <script src="openwebrx.js"></script> | ||||||
|  |         <script src="jquery-3.2.1.min.js"></script> | ||||||
|  |         <script src="jquery.nanoscroller.js"></script> | ||||||
|  |         <link rel="stylesheet" type="text/css" href="nanoscroller.css" /> | ||||||
|  |         <link rel="stylesheet" type="text/css" href="openwebrx.css" /> | ||||||
|  |         <meta charset="utf-8"> | ||||||
|  |     </head> | ||||||
|  |     <body onload="openwebrx_init();"> | ||||||
| <div id="webrx-page-container"> | <div id="webrx-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="https://sdr.hu/openwebrx" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a> | ||||||
| 			<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a> |             <a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a> | ||||||
| 			<img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/> |             <img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/> | ||||||
| 			<img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/> |             <img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/> | ||||||
| 			<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,260"> |                 <div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="259,115"> | ||||||
| 					<div id="webrx-actual-freq">---.--- MHz</div> |                     <div id="webrx-actual-freq">---.--- MHz</div> | ||||||
| 					<div id="webrx-mouse-freq">---.--- MHz</div> |                     <div id="webrx-mouse-freq">---.--- MHz</div> | ||||||
| 					<!--<div class="openwebrx-button" onclick="ws.send('SET mod=wfm');" >WFM</div>--> |                     <div class="openwebrx-panel-line"> | ||||||
| 					<div class="openwebrx-panel-line"> |                         <div class="openwebrx-button openwebrx-demodulator-button" 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 class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('dmr');">DMR</div> |                         <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-usb" | ||||||
| 						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('dstar');">DStar</div> |                             onclick="demodulator_analog_replace('usb');">USB</div> | ||||||
| 						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nxdn');">NXDN</div> |                         <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-cw" | ||||||
| 						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('ysf');">YSF</div> |                             onclick="demodulator_analog_replace('cw');">CW</div> | ||||||
| 					</div> | 						<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dmr" | ||||||
| 					<div class="openwebrx-panel-line"> | 						    onclick="demodulator_analog_replace('dmr');">DMR</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-dstar" | ||||||
| 						<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('dstar');">DStar</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-demodulator-button" id="openwebrx-button-nxdn" | ||||||
| 						<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()"> | 						    onclick="demodulator_analog_replace('nxdn');">NXDN</div> | ||||||
| 					</div> | 						<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-ysf" | ||||||
| 					<div class="openwebrx-panel-line"> | 						    onclick="demodulator_analog_replace('ysf');">YSF</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> | ||||||
| 						<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 class="openwebrx-panel-line"> | ||||||
| 						<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 class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dig" onclick="demodulator_digital_replace_last();">DIG</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()"> |                         <select id="openwebrx-secondary-demod-listbox" onchange="secondary_demod_listbox_changed();"> | ||||||
| 					</div> |                             <option value="none"></option> | ||||||
| 					<div class="openwebrx-panel-line"> |                             <option value="bpsk31">BPSK31</option> | ||||||
| 						<div class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();"  title="Zoom in one step">  <img src="gfx/openwebrx-zoom-in.png" /></div> |                         </select> | ||||||
| 						<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div> |                     </div> | ||||||
| 						<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();"  title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div> |                     <div class="openwebrx-panel-line"> | ||||||
| 						<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></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 id="openwebrx-smeter-db">0 dB</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> |                         <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-panel-line"> |                         <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 id="openwebrx-smeter-outer"> |                     </div> | ||||||
| 							<div id="openwebrx-smeter-bar"></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> |                         <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> |                         <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 class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="2" data-panel-size="619,142"> |                         <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 class="openwebrx-panel-inner" id="openwebrx-log-scroll"> |                     </div> | ||||||
| 						<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div> |                     <div class="openwebrx-panel-line"> | ||||||
| 						<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="zoomInOneStep();"  title="Zoom in one step">  <img src="gfx/openwebrx-zoom-in.png" /></div> | ||||||
| 						<div id="openwebrx-debugdiv"></div> |                         <div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div> | ||||||
| 					</div> |                         <div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();"  title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div> | ||||||
| 				</div> |                         <div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div> | ||||||
| 				<div class="openwebrx-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-button openwebrx-square-button" onclick="mathbox_toggle();" title="Toggle 3D view"><img src="gfx/openwebrx-3d-spectrum.png" /></div> | ||||||
| 					<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div> |                         <div id="openwebrx-smeter-db">0 dB</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 class="openwebrx-panel-line"> | ||||||
| 					<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 id="openwebrx-smeter-outer"> | ||||||
| 					<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 id="openwebrx-smeter-bar"></div> | ||||||
| 					<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div> |                         </div> | ||||||
| 				</div> |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,137"> | ||||||
|  |                     <div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll"> | ||||||
|  |                         <div class="nano-content"> | ||||||
|  |                             <div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div> | ||||||
|  |                             <span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/> | ||||||
|  |                             <div id="openwebrx-debugdiv"></div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true"> | ||||||
|  |                     <div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div> | ||||||
|  |                     <div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div> | ||||||
|  |                     <div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div> | ||||||
|  |                     <div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div> | ||||||
|  |                     <div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div> | ||||||
|  |                     <div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="background-color: Red;"> | ||||||
|  |                     <span style="font-size: 15pt; font-weight: bold;">Under construction</span> | ||||||
|  |                     <br />We're working on the code right now, so the application might fail. | ||||||
|  |                 </div> | ||||||
|  |                 <div class="openwebrx-panel" id="openwebrx-panel-digimodes" data-panel-name="digimodes" data-panel-pos="left" data-panel-order="2" data-panel-size="619,210"> | ||||||
|  |                     <div id="openwebrx-digimode-canvas-container"> | ||||||
|  |                         <div id="openwebrx-digimode-select-channel"></div> | ||||||
|  |                     </div> | ||||||
|  |                     <div id="openwebrx-digimode-content-container"> | ||||||
|  |                         <div class="gradient"></div> | ||||||
|  |                         <div id="openwebrx-digimode-content"> | ||||||
|  |                         <span id="openwebrx-cursor-blink"></span> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|                 <div class="openwebrx-panel" data-panel-name="metadata" data-panel-pos="left" data-panel-order="1" data-panel-size="615,36"> |                 <div class="openwebrx-panel" data-panel-name="metadata" data-panel-pos="left" data-panel-order="1" data-panel-size="615,36"> | ||||||
|                 </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;"> |             </div> | ||||||
| 					<span style="font-size: 15pt; font-weight: bold;">Under construction</span> |     </div> | ||||||
| 					<br />We're working on the code right now, so the application might fail. |  | ||||||
| 				</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
									
								
							
							
						
						
									
										4
									
								
								htdocs/jquery-3.2.1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1000
									
								
								htdocs/jquery.nanoscroller.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1000
									
								
								htdocs/jquery.nanoscroller.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										33
									
								
								htdocs/mathbox-bundle.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										461
									
								
								htdocs/mathbox.css
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										55
									
								
								htdocs/nanoscroller.css
									
									
									
									
									
										Normal 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; | ||||||
|  | } | ||||||
| @@ -28,6 +28,11 @@ html, body | |||||||
| 	overflow: hidden; | 	overflow: hidden; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | select | ||||||
|  | { | ||||||
|  | 	font-family: "DejaVu Sans", Verdana, Geneva, sans-serif; | ||||||
|  | } | ||||||
|  |  | ||||||
| input | input | ||||||
| { | { | ||||||
| 	vertical-align:middle; | 	vertical-align:middle; | ||||||
| @@ -394,6 +399,13 @@ input[type=range]:focus::-ms-fill-upper | |||||||
| 	border-style: none; | 	border-style: none; | ||||||
| 	image-rendering: crisp-edges; | 	image-rendering: crisp-edges; | ||||||
| 	image-rendering: -webkit-optimize-contrast; | 	image-rendering: -webkit-optimize-contrast; | ||||||
|  |     /*transition: left 200ms, width 200ms;*/ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-mathbox-container | ||||||
|  | { | ||||||
|  | 	overflow: none; | ||||||
|  | 	display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #openwebrx-phantom-canvas | #openwebrx-phantom-canvas | ||||||
| @@ -410,11 +422,15 @@ input[type=range]:focus::-ms-fill-upper | |||||||
| 	height: 396px; | 	height: 396px; | ||||||
| }*/ | }*/ | ||||||
|  |  | ||||||
| /*#webrx-debugdiv | #openwebrx-log-scroll | ||||||
| { | { | ||||||
| 	font-size: 10pt; |     /*overflow-y:auto;*/ | ||||||
| 	/*overflow-y:scroll;*/ |     height: 125px; | ||||||
| /*}*/ |     width: 619px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .nano .nano-pane   { background: #444; } | ||||||
|  | .nano .nano-slider { background: #eee !important;  } | ||||||
|  |  | ||||||
| #webrx-main-container | #webrx-main-container | ||||||
| { | { | ||||||
| @@ -499,7 +515,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
|  |  | ||||||
| .openwebrx-panel | .openwebrx-panel | ||||||
| { | { | ||||||
| 	transform: perspective( 600px ); | 	transform: perspective( 600px ) rotateX( 90deg ); | ||||||
| 	visibility: hidden; | 	visibility: hidden; | ||||||
| 	background-color: #575757; | 	background-color: #575757; | ||||||
| 	padding: 10px; | 	padding: 10px; | ||||||
| @@ -526,7 +542,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
| .openwebrx-button | .openwebrx-button | ||||||
| { | { | ||||||
| 	background-color: #373737; | 	background-color: #373737; | ||||||
| 	padding: 5px; | 	padding: 4.2px; | ||||||
| 	border-radius: 5px; | 	border-radius: 5px; | ||||||
| 	-moz-border-radius: 5px; | 	-moz-border-radius: 5px; | ||||||
| 	color: White; | 	color: White; | ||||||
| @@ -544,7 +560,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
| 	display: inline-block; | 	display: inline-block; | ||||||
| } | } | ||||||
|  |  | ||||||
| .openwebrx-button:hover | .openwebrx-button:hover, .openwebrx-demodulator-button.highlighted | ||||||
| { | { | ||||||
| 	/*background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0	, #3F3F3F), color-stop(1, #777777) ); | 	/*background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0	, #3F3F3F), color-stop(1, #777777) ); | ||||||
| 	background:-moz-linear-gradient( center top, #373737 5%, #4F4F4F 100% );*/ | 	background:-moz-linear-gradient( center top, #373737 5%, #4F4F4F 100% );*/ | ||||||
| @@ -560,7 +576,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
|  |  | ||||||
| .openwebrx-demodulator-button | .openwebrx-demodulator-button | ||||||
| { | { | ||||||
| 	width: 50px; | 	width: 38px; | ||||||
| 	height: 19px; | 	height: 19px; | ||||||
| 	font-size: 12pt; | 	font-size: 12pt; | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
| @@ -568,7 +584,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
|  |  | ||||||
| .openwebrx-square-button img | .openwebrx-square-button img | ||||||
| { | { | ||||||
| 	height: 30px; | 	height: 27px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .openwebrx-round-button | .openwebrx-round-button | ||||||
| @@ -732,7 +748,7 @@ img.openwebrx-mirror-img | |||||||
| { | { | ||||||
| 	position: relative; | 	position: relative; | ||||||
| 	top: -2px; | 	top: -2px; | ||||||
| 	width:91px; | 	width: 95px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .openwebrx-sliderbtn-img | .openwebrx-sliderbtn-img | ||||||
| @@ -776,7 +792,7 @@ img.openwebrx-mirror-img | |||||||
| 	font-size: 10pt; | 	font-size: 10pt; | ||||||
| 	float: right; | 	float: right; | ||||||
| 	margin-right: 5px; | 	margin-right: 5px; | ||||||
| 	margin-top: 29px; | 	margin-top: 24px; | ||||||
| 	font-family: 'expletus-sans-medium'; | 	font-family: 'expletus-sans-medium'; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -806,3 +822,152 @@ img.openwebrx-mirror-img | |||||||
| { | { | ||||||
| 	width: 150px; | 	width: 150px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-canvas-container | ||||||
|  | { | ||||||
|  | 	/*margin: -10px -10px 10px -10px;*/ | ||||||
|  |     margin: -10px -10px 0px -10px; | ||||||
|  | 	border-radius: 15px; | ||||||
|  | 	height: 150px; | ||||||
|  | 	background-color: #333; | ||||||
|  |     position: relative; | ||||||
|  |     overflow: hidden; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-canvas-container canvas | ||||||
|  | { | ||||||
|  |     position: absolute; | ||||||
|  |     pointer-events: none; | ||||||
|  |     transition: width 500ms, left 500ms; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-secondary-demod-listbox | ||||||
|  | { | ||||||
|  | 	width: 201px; | ||||||
|  | 	height: 27px; | ||||||
|  | 	border-radius: 5px; | ||||||
|  | 	background-color: #373737; | ||||||
|  | 	color: White; | ||||||
|  | 	font-weight: normal; | ||||||
|  | 	font-size: 13pt; | ||||||
|  | 	margin-right: 1px; | ||||||
|  | 	background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0	, #373737), color-stop(1, #4F4F4F) ); | ||||||
|  | 	background:-moz-linear-gradient( center top, #373737 0%, #4F4F4F 100% ); | ||||||
|  | 	border-color: transparent; | ||||||
|  | 	border-width: 0px; | ||||||
|  | 	-moz-appearance: none; | ||||||
|  | 	padding-left:3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-secondary-demod-listbox option | ||||||
|  | { | ||||||
|  | 	border-width: 0px; | ||||||
|  | 	background-color: #373737; | ||||||
|  | 	color: White; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-cursor-blink | ||||||
|  | { | ||||||
|  | 	animation: cursor-blink 1s infinite; | ||||||
|  | 	/*animation: cursor-3d 2s infinite;*/ | ||||||
|  | 	animation-timing-function: linear; | ||||||
|  | 	animation-direction: alternate; | ||||||
|  | 	height: 1em; | ||||||
|  | 	width: 8px; | ||||||
|  | 	background-color: White; | ||||||
|  | 	display: inline-block; | ||||||
|  |     position: relative; | ||||||
|  |     top: 1px; | ||||||
|  | 	/*perspective: 60px;*/ | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes cursor-blink | ||||||
|  | { | ||||||
|  | 	0%{ opacity: 0; } | ||||||
|  | 	50% { opacity: 1; } | ||||||
|  | 	100%{ opacity: 0; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes cursor-3d | ||||||
|  | { | ||||||
|  | 	0%{ transform: rotateX(0deg) rotateX(Ydeg); } | ||||||
|  | 	50% { transform: rotateX(180deg) rotateY(360deg); opacity: 0.1; } | ||||||
|  | 	100%{ transform: rotateX(360deg) rotateY(720deg); } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-content | ||||||
|  | { | ||||||
|  |     word-wrap: break-word; | ||||||
|  |     position: absolute; | ||||||
|  |     bottom: 0; | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-content-container | ||||||
|  | { | ||||||
|  |     overflow-y: hidden; | ||||||
|  |     display: block; | ||||||
|  |     height: 50px; | ||||||
|  |     position: relative; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-content-container .gradient | ||||||
|  | { | ||||||
|  |     width: 100%; | ||||||
|  |     height: 20px; | ||||||
|  |     background: linear-gradient(to top,  rgba(87,87,87,0) 0%,rgba(87,87,87,1) 100%); | ||||||
|  |     position: absolute; | ||||||
|  |     top: 0; | ||||||
|  |     z-index: 10; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-content .part | ||||||
|  | { | ||||||
|  |     perspective: 700px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-content .part | ||||||
|  | { | ||||||
|  |     animation: new-digimode-data-3d 100ms; | ||||||
|  | 	animation-timing-function: linear; | ||||||
|  |     display: inline-block; | ||||||
|  |     perspective-origin: 50% 50%; | ||||||
|  |     transform-origin: 0% 50%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-content .part .subpart | ||||||
|  | { | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @keyframes new-digimode-data | ||||||
|  | { | ||||||
|  | 	0%{ opacity: 0; } | ||||||
|  | 	100%{ opacity: 1; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes new-digimode-data-3d | ||||||
|  | { | ||||||
|  | 	0%{ transform: rotateX(0deg) rotateY(-90deg) translateX(-5px) scale(1.3); } | ||||||
|  | 	100%{ transform: rotateX(0deg) rotateY(0deg) translateX(0) scale(1); } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-digimode-select-channel | ||||||
|  | { | ||||||
|  |     transition: all 500ms; | ||||||
|  |     background-color: Yellow; | ||||||
|  |     display: block; | ||||||
|  |     position: absolute; | ||||||
|  |     pointer-events: none; | ||||||
|  |     height: 100%; | ||||||
|  |     width: 0px; | ||||||
|  |     top: 0px; | ||||||
|  |     left: 0px; | ||||||
|  |     opacity: 0.7; | ||||||
|  |     border-style: solid; | ||||||
|  |     border-width: 0px; | ||||||
|  |     border-color: Red; | ||||||
|  | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ var audio_compression="none"; | |||||||
| var waterfall_setup_done=0; | var waterfall_setup_done=0; | ||||||
| var waterfall_queue = []; | var waterfall_queue = []; | ||||||
| var waterfall_timer; | var waterfall_timer; | ||||||
|  | var secondary_fft_size; | ||||||
|  |  | ||||||
| /*function fade(something,from,to,time_ms,fps) | /*function fade(something,from,to,time_ms,fps) | ||||||
| { | { | ||||||
| @@ -72,7 +73,8 @@ var rx_photo_state=1; | |||||||
|  |  | ||||||
| function e(what) { return document.getElementById(what); } | function e(what) { return document.getElementById(what); } | ||||||
|  |  | ||||||
| ios = /iPad|iPod|iPhone/.test(navigator.userAgent); | ios = /iPad|iPod|iPhone|Chrome/.test(navigator.userAgent); | ||||||
|  | is_chrome = /Chrome/.test(navigator.userAgent); | ||||||
| //alert("ios="+ios.toString()+"  "+navigator.userAgent); | //alert("ios="+ios.toString()+"  "+navigator.userAgent); | ||||||
|  |  | ||||||
| function init_rx_photo() | function init_rx_photo() | ||||||
| @@ -183,8 +185,8 @@ function waterfallColorsDefault() | |||||||
|  |  | ||||||
| function waterfallColorsAuto() | function waterfallColorsAuto() | ||||||
| { | { | ||||||
| 	e("openwebrx-waterfall-color-min").value=(waterfall_measure_minmax_min-20).toString(); | 	e("openwebrx-waterfall-color-min").value=(waterfall_measure_minmax_min-waterfall_auto_level_margin[0]).toString(); | ||||||
| 	e("openwebrx-waterfall-color-max").value=(waterfall_measure_minmax_max+30).toString(); | 	e("openwebrx-waterfall-color-max").value=(waterfall_measure_minmax_max+waterfall_auto_level_margin[1]).toString(); | ||||||
| 	updateWaterfallColors(0); | 	updateWaterfallColors(0); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -559,6 +561,7 @@ function demodulator_default_analog(offset_frequency,subtype) | |||||||
|  |  | ||||||
| 	this.envelope.drag_end=function(x) | 	this.envelope.drag_end=function(x) | ||||||
| 	{ //in this demodulator we've already changed values in the drag_move() function so we shouldn't do too much here. | 	{ //in this demodulator we've already changed values in the drag_move() function so we shouldn't do too much here. | ||||||
|  | 		demodulator_buttons_update(); | ||||||
| 		to_return=this.dragged_range!=demodulator.draggable_ranges.none; //this part is required for cliking anywhere on the scale to set offset | 		to_return=this.dragged_range!=demodulator.draggable_ranges.none; //this part is required for cliking anywhere on the scale to set offset | ||||||
| 		this.dragged_range=demodulator.draggable_ranges.none; | 		this.dragged_range=demodulator.draggable_ranges.none; | ||||||
| 		return to_return; | 		return to_return; | ||||||
| @@ -575,6 +578,7 @@ function mkenvelopes(visible_range) //called from mkscale | |||||||
| 	{ | 	{ | ||||||
| 		demodulators[i].envelope.draw(visible_range); | 		demodulators[i].envelope.draw(visible_range); | ||||||
| 	} | 	} | ||||||
|  |     if(demodulators.length) secondary_demod_waterfall_set_zoom(demodulators[0].low_cut, demodulators[0].high_cut); | ||||||
| } | } | ||||||
|  |  | ||||||
| function demodulator_remove(which) | function demodulator_remove(which) | ||||||
| @@ -589,8 +593,17 @@ function demodulator_add(what) | |||||||
| 	mkenvelopes(get_visible_freq_range()); | 	mkenvelopes(get_visible_freq_range()); | ||||||
| } | } | ||||||
|  |  | ||||||
| function demodulator_analog_replace(subtype) | last_analog_demodulator_subtype = 'nfm'; | ||||||
|  | last_digital_demodulator_subtype = 'bpsk31'; | ||||||
|  |  | ||||||
|  | function demodulator_analog_replace(subtype, for_digital) | ||||||
| { //this function should only exist until the multi-demodulator capability is added | { //this function should only exist until the multi-demodulator capability is added | ||||||
|  |     if(!(typeof for_digital !== "undefined" && for_digital && secondary_demod))  | ||||||
|  |     {  | ||||||
|  |         secondary_demod_close_window();  | ||||||
|  |         secondary_demod_listbox_update();  | ||||||
|  |     } | ||||||
|  |     last_analog_demodulator_subtype = subtype; | ||||||
| 	var temp_offset=0; | 	var temp_offset=0; | ||||||
| 	if(demodulators.length) | 	if(demodulators.length) | ||||||
| 	{ | 	{ | ||||||
| @@ -598,6 +611,7 @@ function demodulator_analog_replace(subtype) | |||||||
| 		demodulator_remove(0); | 		demodulator_remove(0); | ||||||
| 	} | 	} | ||||||
| 	demodulator_add(new demodulator_default_analog(temp_offset,subtype)); | 	demodulator_add(new demodulator_default_analog(temp_offset,subtype)); | ||||||
|  | 	demodulator_buttons_update(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function demodulator_set_offset_frequency(which,to_what) | function demodulator_set_offset_frequency(which,to_what) | ||||||
| @@ -1028,7 +1042,7 @@ function canvas_mousewheel(evt) | |||||||
|  |  | ||||||
|  |  | ||||||
| zoom_max_level_hps=33; //Hz/pixel | zoom_max_level_hps=33; //Hz/pixel | ||||||
| zoom_levels_count=5; | zoom_levels_count=14; | ||||||
|  |  | ||||||
| function get_zoom_coeff_from_hps(hps) | function get_zoom_coeff_from_hps(hps) | ||||||
| { | { | ||||||
| @@ -1050,8 +1064,10 @@ function mkzoomlevels() | |||||||
| 	zoom_levels=[1]; | 	zoom_levels=[1]; | ||||||
| 	maxc=get_zoom_coeff_from_hps(zoom_max_level_hps); | 	maxc=get_zoom_coeff_from_hps(zoom_max_level_hps); | ||||||
| 	if(maxc<1) return; | 	if(maxc<1) return; | ||||||
|  | 	// logarithmic interpolation | ||||||
|  | 	zoom_ratio = Math.pow(maxc, 1/zoom_levels_count); | ||||||
| 	for(i=1;i<zoom_levels_count;i++) | 	for(i=1;i<zoom_levels_count;i++) | ||||||
| 		zoom_levels.push(1+(maxc-1)*(i/(zoom_levels_count-1))); | 		zoom_levels.push(Math.pow(zoom_ratio, i)); | ||||||
| } | } | ||||||
|  |  | ||||||
| function zoom_step(out, where, onscreen) | function zoom_step(out, where, onscreen) | ||||||
| @@ -1063,7 +1079,7 @@ function zoom_step(out, where, onscreen) | |||||||
| 	zoom_center_rel=canvas_get_freq_offset(where); | 	zoom_center_rel=canvas_get_freq_offset(where); | ||||||
| 	//console.log("zoom_step || zlevel: "+zoom_level.toString()+" zlevel_val: "+zoom_levels[zoom_level].toString()+" zoom_center_rel: "+zoom_center_rel.toString()); | 	//console.log("zoom_step || zlevel: "+zoom_level.toString()+" zlevel_val: "+zoom_levels[zoom_level].toString()+" zoom_center_rel: "+zoom_center_rel.toString()); | ||||||
| 	zoom_center_where=onscreen; | 	zoom_center_where=onscreen; | ||||||
| 	console.log(zoom_center_where, zoom_center_rel, where); | 	//console.log(zoom_center_where, zoom_center_rel, where); | ||||||
| 	resize_canvases(true); | 	resize_canvases(true); | ||||||
| 	mkscale(); | 	mkscale(); | ||||||
| } | } | ||||||
| @@ -1095,9 +1111,17 @@ function zoom_calc() | |||||||
| function resize_waterfall_container(check_init) | function resize_waterfall_container(check_init) | ||||||
| { | { | ||||||
| 	if(check_init&&!waterfall_setup_done) return; | 	if(check_init&&!waterfall_setup_done) return; | ||||||
| 	canvas_container.style.height=(window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px"; | 	var numHeight; | ||||||
| } | 	mathbox_container.style.height=canvas_container.style.height=(numHeight=window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px"; | ||||||
|  | 	if(mathbox) | ||||||
|  | 	{ | ||||||
|  | 		//mathbox.three.camera.aspect = document.body.offsetWidth / numHeight; | ||||||
|  |   		//mathbox.three.camera.updateProjectionMatrix(); | ||||||
|  | 		mathbox.three.renderer.setSize(document.body.offsetWidth, numHeight); | ||||||
|  | 		console.log(document.body.offsetWidth, numHeight); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| audio_server_output_rate=11025; | audio_server_output_rate=11025; | ||||||
| audio_client_resampling_factor=4; | audio_client_resampling_factor=4; | ||||||
| @@ -1134,14 +1158,15 @@ function on_ws_recv(evt) | |||||||
| 	if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; } | 	if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; } | ||||||
| 	// | 	// | ||||||
| 	debug_ws_data_received+=evt.data.byteLength/1000; | 	debug_ws_data_received+=evt.data.byteLength/1000; | ||||||
| 	firstChars=getFirstChars(evt.data,3); | 	first4Chars=getFirstChars(evt.data,4); | ||||||
| 	if(firstChars=="CLI") |     first3Chars=first4Chars.slice(0,3); | ||||||
|  | 	if(first3Chars=="CLI") | ||||||
| 	{ | 	{ | ||||||
| 		var stringData=arrayBufferToString(evt.data); | 		var stringData=arrayBufferToString(evt.data); | ||||||
| 		if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Acknowledged WebSocket connection: "+stringData); | 		if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Server acknowledged WebSocket connection."); | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| 	if(firstChars=="AUD") | 	if(first3Chars=="AUD") | ||||||
| 	{ | 	{ | ||||||
| 		var audio_data; | 		var audio_data; | ||||||
| 		if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4) | 		if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4) | ||||||
| @@ -1149,11 +1174,12 @@ function on_ws_recv(evt) | |||||||
| 		audio_prepare(audio_data); | 		audio_prepare(audio_data); | ||||||
| 		audio_buffer_current_size_debug+=audio_data.length; | 		audio_buffer_current_size_debug+=audio_data.length; | ||||||
| 		audio_buffer_all_size_debug+=audio_data.length; | 		audio_buffer_all_size_debug+=audio_data.length; | ||||||
| 		if(!ios && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init() | 		if(!(ios||is_chrome) && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init() | ||||||
| 	} | 	} | ||||||
| 	else if(firstChars=="FFT") | 	else if(first3Chars=="FFT") | ||||||
| 	{ | 	{ | ||||||
| 		//alert("Yupee! Doing FFT"); | 		//alert("Yupee! Doing FFT"); | ||||||
|  |         //if(first4Chars=="FFTS") console.log("FFTS");  | ||||||
| 		if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4)); | 		if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4)); | ||||||
| 		else if(fft_compression="adpcm") | 		else if(fft_compression="adpcm") | ||||||
| 		{ | 		{ | ||||||
| @@ -1162,9 +1188,17 @@ function on_ws_recv(evt) | |||||||
| 			var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4)); | 			var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4)); | ||||||
| 			var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N); | 			var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N); | ||||||
| 			for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100; | 			for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100; | ||||||
| 			waterfall_add_queue(waterfall_f32); |             if(first4Chars=="FFTS") secondary_demod_waterfall_add_queue(waterfall_f32); //TODO digimodes | ||||||
|  |             else waterfall_add_queue(waterfall_f32); | ||||||
| 		} | 		} | ||||||
| 	} else if(firstChars=="MSG") | 	}  | ||||||
|  |     else if(first3Chars=="DAT") | ||||||
|  |     { | ||||||
|  |         //secondary_demod_push_binary_data(new Uint8Array(evt.data,4)); | ||||||
|  |         secondary_demod_push_data(arrayBufferToString(evt.data).substring(4)); | ||||||
|  |         //console.log("DAT"); | ||||||
|  | 	}  | ||||||
|  |     else if(first3Chars=="MSG") | ||||||
| 	{ | 	{ | ||||||
| 		/*try | 		/*try | ||||||
| 		{*/ | 		{*/ | ||||||
| @@ -1188,6 +1222,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; | ||||||
| @@ -1336,8 +1382,10 @@ function divlog(what, is_error) | |||||||
| 		if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present | 		if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present | ||||||
| 	} | 	} | ||||||
| 	e("openwebrx-debugdiv").innerHTML+=what+"<br />"; | 	e("openwebrx-debugdiv").innerHTML+=what+"<br />"; | ||||||
| 	var wls=e("openwebrx-log-scroll"); | 	//var wls=e("openwebrx-log-scroll"); | ||||||
| 	wls.scrollTop=wls.scrollHeight; //scroll to bottom | 	//wls.scrollTop=wls.scrollHeight; //scroll to bottom | ||||||
|  |     $(".nano").nanoScroller(); | ||||||
|  |     $(".nano").nanoScroller({ scroll: 'bottom' }); | ||||||
| } | } | ||||||
|  |  | ||||||
| var audio_context; | var audio_context; | ||||||
| @@ -1585,19 +1633,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; | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| @@ -1638,6 +1694,9 @@ function audio_preinit() | |||||||
|  |  | ||||||
| function audio_init() | function audio_init() | ||||||
| { | { | ||||||
|  |     if(is_chrome) audio_context.resume() | ||||||
|  | 	if(starting_mute) toggleMute(); | ||||||
|  |  | ||||||
| 	if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor... | 	if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor... | ||||||
|  |  | ||||||
| 	audio_debug_time_start=(new Date()).getTime(); | 	audio_debug_time_start=(new Date()).getTime(); | ||||||
| @@ -1682,6 +1741,7 @@ function audio_init() | |||||||
| 			//window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200) | 			//window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200) | ||||||
| 		} | 		} | ||||||
| 	},2000); | 	},2000); | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function on_ws_closed() | function on_ws_closed() | ||||||
| @@ -1722,17 +1782,18 @@ function open_websocket() | |||||||
| 	ws.onerror = on_ws_error; | 	ws.onerror = on_ws_error; | ||||||
| } | } | ||||||
|  |  | ||||||
| function waterfall_mkcolor(db_value) | function waterfall_mkcolor(db_value, waterfall_colors_arg) | ||||||
| { | { | ||||||
|  | 	if(typeof waterfall_colors_arg === 'undefined') waterfall_colors_arg = waterfall_colors; | ||||||
| 	if(db_value<waterfall_min_level) db_value=waterfall_min_level; | 	if(db_value<waterfall_min_level) db_value=waterfall_min_level; | ||||||
| 	if(db_value>waterfall_max_level) db_value=waterfall_max_level; | 	if(db_value>waterfall_max_level) db_value=waterfall_max_level; | ||||||
| 	full_scale=waterfall_max_level-waterfall_min_level; | 	full_scale=waterfall_max_level-waterfall_min_level; | ||||||
| 	relative_value=db_value-waterfall_min_level; | 	relative_value=db_value-waterfall_min_level; | ||||||
| 	value_percent=relative_value/full_scale; | 	value_percent=relative_value/full_scale; | ||||||
| 	percent_for_one_color=1/(waterfall_colors.length-1); | 	percent_for_one_color=1/(waterfall_colors_arg.length-1); | ||||||
| 	index=Math.floor(value_percent/percent_for_one_color); | 	index=Math.floor(value_percent/percent_for_one_color); | ||||||
| 	remain=(value_percent-percent_for_one_color*index)/percent_for_one_color; | 	remain=(value_percent-percent_for_one_color*index)/percent_for_one_color; | ||||||
| 	return color_between(waterfall_colors[index+1],waterfall_colors[index],remain); | 	return color_between(waterfall_colors_arg[index+1],waterfall_colors_arg[index],remain); | ||||||
| } | } | ||||||
|  |  | ||||||
| function color_between(first, second, percent) | function color_between(first, second, percent) | ||||||
| @@ -1755,7 +1816,7 @@ var canvas_phantom; | |||||||
|  |  | ||||||
| function add_canvas() | function add_canvas() | ||||||
| { | { | ||||||
| 	new_canvas = document.createElement("canvas"); | 	var new_canvas = document.createElement("canvas"); | ||||||
| 	new_canvas.width=fft_size; | 	new_canvas.width=fft_size; | ||||||
| 	new_canvas.height=canvas_default_height; | 	new_canvas.height=canvas_default_height; | ||||||
| 	canvas_actual_line=canvas_default_height-1; | 	canvas_actual_line=canvas_default_height-1; | ||||||
| @@ -1780,9 +1841,11 @@ function add_canvas() | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| function init_canvas_container() | function init_canvas_container() | ||||||
| { | { | ||||||
| 	canvas_container=e("webrx-canvas-container"); | 	canvas_container=e("webrx-canvas-container"); | ||||||
|  | 	mathbox_container=e("openwebrx-mathbox-container"); | ||||||
| 	canvas_container.addEventListener("mouseout",canvas_container_mouseout, false); | 	canvas_container.addEventListener("mouseout",canvas_container_mouseout, false); | ||||||
| 	//window.addEventListener("mouseout",window_mouseout,false); | 	//window.addEventListener("mouseout",window_mouseout,false); | ||||||
| 	//document.body.addEventListener("mouseup",body_mouseup,false); | 	//document.body.addEventListener("mouseup",body_mouseup,false); | ||||||
| @@ -1839,7 +1902,7 @@ function resize_canvases(zoom) | |||||||
| function waterfall_init() | function waterfall_init() | ||||||
| { | { | ||||||
| 	init_canvas_container(); | 	init_canvas_container(); | ||||||
| 	waterfall_timer = window.setInterval(waterfall_dequeue,900/fft_fps); | 	waterfall_timer = window.setInterval(()=>{waterfall_dequeue(); secondary_demod_waterfall_dequeue();},900/fft_fps); | ||||||
| 	resize_waterfall_container(false); /* then */ resize_canvases(); | 	resize_waterfall_container(false); /* then */ resize_canvases(); | ||||||
| 	scale_setup(); | 	scale_setup(); | ||||||
| 	mkzoomlevels(); | 	mkzoomlevels(); | ||||||
| @@ -1848,6 +1911,42 @@ function waterfall_init() | |||||||
|  |  | ||||||
| var waterfall_dont_scale=0; | var waterfall_dont_scale=0; | ||||||
|  |  | ||||||
|  | var mathbox_shift = function() | ||||||
|  | { | ||||||
|  | 	if(mathbox_data_current_depth < mathbox_data_max_depth) mathbox_data_current_depth++; | ||||||
|  | 	if(mathbox_data_index+1>=mathbox_data_max_depth) mathbox_data_index = 0; | ||||||
|  | 	else mathbox_data_index++; | ||||||
|  | 	mathbox_data_global_index++; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var mathbox_clear_data = function() | ||||||
|  | { | ||||||
|  | 	mathbox_data_index = 50; | ||||||
|  | 	mathbox_data_current_depth = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //var mathbox_get_data_line = function(x) //x counts from 0 to mathbox_data_current_depth | ||||||
|  | //{ | ||||||
|  | //	return (mathbox_data_max_depth + mathbox_data_index - mathbox_data_current_depth + x - 1) % mathbox_data_max_depth; | ||||||
|  | //} | ||||||
|  | // | ||||||
|  | //var mathbox_data_index_valid = function(x) //x counts from 0 to mathbox_data_current_depth | ||||||
|  | //{ | ||||||
|  | //	return x<mathbox_data_current_depth; | ||||||
|  | //} | ||||||
|  |  | ||||||
|  | var mathbox_get_data_line = function(x) | ||||||
|  | { | ||||||
|  | 	return (mathbox_data_max_depth + mathbox_data_index + x - 1) % mathbox_data_max_depth; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var mathbox_data_index_valid = function(x) | ||||||
|  | { | ||||||
|  | 	return x>mathbox_data_max_depth-mathbox_data_current_depth; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| function waterfall_add(data) | function waterfall_add(data) | ||||||
| { | { | ||||||
| 	if(!waterfall_setup_done) return; | 	if(!waterfall_setup_done) return; | ||||||
| @@ -1924,6 +2023,14 @@ function waterfall_add(data) | |||||||
| 			waterfall_image.data[base+x*4+i] = ((color>>>0)>>((3-i)*8))&0xff; | 			waterfall_image.data[base+x*4+i] = ((color>>>0)>>((3-i)*8))&0xff; | ||||||
| 	}*/ | 	}*/ | ||||||
|  |  | ||||||
|  | 	if(mathbox_mode==MATHBOX_MODES.WATERFALL) | ||||||
|  | 	{ | ||||||
|  | 		//Handle mathbox | ||||||
|  | 		for(var i=0;i<fft_size;i++) mathbox_data[i+mathbox_data_index*fft_size]=data[i]; | ||||||
|  | 		mathbox_shift(); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
| 	//Add line to waterfall image | 	//Add line to waterfall image | ||||||
| 	oneline_image = canvas_context.createImageData(w,1); | 	oneline_image = canvas_context.createImageData(w,1); | ||||||
| 	for(x=0;x<w;x++) | 	for(x=0;x<w;x++) | ||||||
| @@ -1933,13 +2040,13 @@ function waterfall_add(data) | |||||||
| 			oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff; | 			oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	//Draw image | 	//Draw image | ||||||
| 	canvas_context.putImageData(oneline_image, 0, canvas_actual_line--); | 	canvas_context.putImageData(oneline_image, 0, canvas_actual_line--); | ||||||
| 	shift_canvases(); | 	shift_canvases(); | ||||||
| 	if(canvas_actual_line<0) add_canvas(); | 	if(canvas_actual_line<0) add_canvas(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	//divlog("Drawn FFT"); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -1969,6 +2076,180 @@ function check_top_bar_congestion() | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var MATHBOX_MODES = | ||||||
|  | { | ||||||
|  | 	UNINITIALIZED: 0, | ||||||
|  | 	NONE: 1, | ||||||
|  | 	WATERFALL: 2, | ||||||
|  | 	CONSTELLATION: 3 | ||||||
|  | }; | ||||||
|  | var mathbox_mode = MATHBOX_MODES.UNINITIALIZED; | ||||||
|  | var mathbox; | ||||||
|  | var mathbox_element; | ||||||
|  |  | ||||||
|  | function mathbox_init() | ||||||
|  | { | ||||||
|  | 	//mathbox_waterfall_history_length is defined in the config | ||||||
|  | 	mathbox_data_max_depth = fft_fps * mathbox_waterfall_history_length; //how many lines can the buffer store | ||||||
|  | 	mathbox_data_current_depth = 0; //how many lines are in the buffer currently | ||||||
|  | 	mathbox_data_index = 0; //the index of the last empty line / the line to be overwritten | ||||||
|  | 	mathbox_data = new Float32Array(fft_size * mathbox_data_max_depth); | ||||||
|  | 	mathbox_data_global_index = 0; | ||||||
|  | 	mathbox_correction_for_z = 0; | ||||||
|  |  | ||||||
|  | 	mathbox = mathBox({ | ||||||
|  |       plugins: ['core', 'controls', 'cursor', 'stats'], | ||||||
|  |       controls: { klass: THREE.OrbitControls }, | ||||||
|  |     }); | ||||||
|  |     three = mathbox.three; | ||||||
|  |     if(typeof three == "undefined") divlog("3D waterfall cannot be initialized because WebGL is not supported in your browser.", true); | ||||||
|  |  | ||||||
|  |     three.renderer.setClearColor(new THREE.Color(0x808080), 1.0); | ||||||
|  | 	mathbox_container.appendChild((mathbox_element=three.renderer.domElement)); | ||||||
|  |     view = mathbox | ||||||
|  |     .set({ | ||||||
|  |       scale: 1080, | ||||||
|  |       focus: 3, | ||||||
|  |     }) | ||||||
|  |     .camera({ | ||||||
|  |       proxy: true, | ||||||
|  |       position: [-2, 1, 3], | ||||||
|  |     }) | ||||||
|  |     .cartesian({ | ||||||
|  |       range: [[-1, 1], [0, 1], [0, 1]], | ||||||
|  |       scale: [2, 2/3, 1], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     view.axis({ | ||||||
|  |       axis: 1, | ||||||
|  |       width: 3, | ||||||
|  | 	  color: "#fff", | ||||||
|  |   }); | ||||||
|  |     view.axis({ | ||||||
|  |       axis: 2, | ||||||
|  |       width: 3, | ||||||
|  | 	  color: "#fff", | ||||||
|  | 	  //offset: [0, 0, 0], | ||||||
|  |   }); | ||||||
|  |     view.axis({ | ||||||
|  |       axis: 3, | ||||||
|  |       width: 3, | ||||||
|  | 	  color: "#fff", | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |     view.grid({ | ||||||
|  |       width: 2, | ||||||
|  |       opacity: 0.5, | ||||||
|  |       axes: [1, 3], | ||||||
|  |       zOrder: 1, | ||||||
|  | 	  color: "#fff", | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     //var remap = function (v) { return Math.sqrt(.5 + .5 * v); }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	var remap = function(x,z,t) | ||||||
|  | 	{ | ||||||
|  | 		var currentTimePos = mathbox_data_global_index/(fft_fps*1.0); | ||||||
|  | 		var realZAdd = (-(t-currentTimePos)/mathbox_waterfall_history_length); | ||||||
|  | 		var zAdd = realZAdd - mathbox_correction_for_z; | ||||||
|  | 		if(zAdd<-0.2 || zAdd>0.2) { mathbox_correction_for_z = realZAdd; } | ||||||
|  |  | ||||||
|  | 		var xIndex = Math.trunc(((x+1)/2.0)*fft_size); //x: frequency | ||||||
|  | 		var zIndex = Math.trunc(z*(mathbox_data_max_depth-1)); //z: time | ||||||
|  | 		var realZIndex = mathbox_get_data_line(zIndex); | ||||||
|  | 		if(!mathbox_data_index_valid(zIndex)) return {y: undefined, dBValue: undefined, zAdd: 0 }; | ||||||
|  | 		//if(realZIndex>=(mathbox_data_max_depth-1)) console.log("realZIndexundef", realZIndex, zIndex); | ||||||
|  | 		var index = Math.trunc(xIndex + realZIndex * fft_size); | ||||||
|  | 		/*if(mathbox_data[index]==undefined) console.log("Undef", index, mathbox_data.length, zIndex, | ||||||
|  | 				realZIndex, mathbox_data_max_depth, | ||||||
|  | 				mathbox_data_current_depth, mathbox_data_index);*/ | ||||||
|  | 		var dBValue = mathbox_data[index]; | ||||||
|  | 		//y=1; | ||||||
|  | 		if(dBValue>waterfall_max_level) y = 1; | ||||||
|  | 		else if(dBValue<waterfall_min_level) y = 0; | ||||||
|  | 		else y = (dBValue-waterfall_min_level)/(waterfall_max_level-waterfall_min_level); | ||||||
|  | 		mathbox_dbg = { dbv: dBValue, indexval: index, mbd: mathbox_data.length, yval: y }; | ||||||
|  | 		if(!y) y=0; | ||||||
|  | 		return {y: y, dBValue: dBValue, zAdd: zAdd}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     var points = view.area({ | ||||||
|  |       expr: function (emit, x, z, i, j, t) { | ||||||
|  | 		var y; | ||||||
|  | 		remapResult=remap(x,z,t); | ||||||
|  | 		if((y=remapResult.y)==undefined) return; | ||||||
|  |         emit(x, y, z+remapResult.zAdd); | ||||||
|  |       }, | ||||||
|  |       width:  mathbox_waterfall_frequency_resolution, | ||||||
|  |       height: mathbox_data_max_depth - 1, | ||||||
|  |       channels: 3, | ||||||
|  |       axes: [1, 3], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     var colors = view.area({ | ||||||
|  |       expr: function (emit, x, z, i, j, t) { | ||||||
|  | 		var dBValue; | ||||||
|  | 		if((dBValue=remap(x,z,t).dBValue)==undefined) return; | ||||||
|  | 		var color=waterfall_mkcolor(dBValue, mathbox_waterfall_colors); | ||||||
|  |         var b = (color&0xff)/255.0; | ||||||
|  |         var g = ((color&0xff00)>>8)/255.0; | ||||||
|  |         var r = ((color&0xff0000)>>16)/255.0; | ||||||
|  |         emit(r, g, b, 1.0); | ||||||
|  |       }, | ||||||
|  |       width:  mathbox_waterfall_frequency_resolution, | ||||||
|  |       height: mathbox_data_max_depth - 1, | ||||||
|  |       channels: 4, | ||||||
|  |       axes: [1, 3], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     view.surface({ | ||||||
|  |       shaded: true, | ||||||
|  |       points: '<<', | ||||||
|  |       colors: '<', | ||||||
|  |       color: 0xFFFFFF, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     view.surface({ | ||||||
|  |       fill: false, | ||||||
|  |       lineX: false, | ||||||
|  |       lineY: false, | ||||||
|  |       points: '<<', | ||||||
|  |       colors: '<', | ||||||
|  |       color: 0xFFFFFF, | ||||||
|  |       width: 2, | ||||||
|  |       blending: 'add', | ||||||
|  |       opacity: .25, | ||||||
|  |       zBias: 5, | ||||||
|  |     }); | ||||||
|  | 	mathbox_mode = MATHBOX_MODES.NONE; | ||||||
|  |  | ||||||
|  | 	//mathbox_element.style.width="100%"; | ||||||
|  | 	//mathbox_element.style.height="100%"; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function mathbox_toggle() | ||||||
|  | { | ||||||
|  |  | ||||||
|  | 	if(mathbox_mode == MATHBOX_MODES.UNINITIALIZED) mathbox_init(); | ||||||
|  | 	mathbox_mode = (mathbox_mode == MATHBOX_MODES.NONE) ? MATHBOX_MODES.WATERFALL : MATHBOX_MODES.NONE; | ||||||
|  | 	mathbox_container.style.display = (mathbox_mode == MATHBOX_MODES.WATERFALL) ? "block" : "none"; | ||||||
|  | 	mathbox_clear_data(); | ||||||
|  | 	waterfall_clear(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function waterfall_clear() | ||||||
|  | { | ||||||
|  | 	while(canvases.length) //delete all canvases | ||||||
|  | 	{ | ||||||
|  | 		var x=canvases.shift(); | ||||||
|  | 		x.parentNode.removeChild(x); | ||||||
|  | 		delete x; | ||||||
|  | 	} | ||||||
|  | 	add_canvas(); | ||||||
|  | } | ||||||
|  |  | ||||||
| function openwebrx_resize() | function openwebrx_resize() | ||||||
| { | { | ||||||
| 	resize_canvases(); | 	resize_canvases(); | ||||||
| @@ -1979,10 +2260,11 @@ function openwebrx_resize() | |||||||
|  |  | ||||||
| function openwebrx_init() | function openwebrx_init() | ||||||
| { | { | ||||||
| 	if(ios) e("openwebrx-big-grey").style.display="table-cell"; | 	if(ios||is_chrome) e("openwebrx-big-grey").style.display="table-cell"; | ||||||
| 	(opb=e("openwebrx-play-button-text")).style.marginTop=(window.innerHeight/2-opb.clientHeight/2).toString()+"px"; | 	(opb=e("openwebrx-play-button-text")).style.marginTop=(window.innerHeight/2-opb.clientHeight/2).toString()+"px"; | ||||||
| 	init_rx_photo(); | 	init_rx_photo(); | ||||||
| 	open_websocket(); | 	open_websocket(); | ||||||
|  |     secondary_demod_init(); | ||||||
| 	place_panels(first_show_panel); | 	place_panels(first_show_panel); | ||||||
| 	window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000); | 	window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000); | ||||||
| 	window.addEventListener("resize",openwebrx_resize); | 	window.addEventListener("resize",openwebrx_resize); | ||||||
| @@ -2076,9 +2358,14 @@ function pop_bottommost_panel(from) | |||||||
| 	return to_return; | 	return to_return; | ||||||
| } | } | ||||||
|  |  | ||||||
| function toggle_panel(what) | function toggle_panel(what, on) | ||||||
| { | { | ||||||
| 	var item=e(what); |     var item=e(what); | ||||||
|  |     if(typeof on !== "undefined")  | ||||||
|  |     { | ||||||
|  |         if(item.openwebrxHidden && !on) return; | ||||||
|  |         if(!item.openwebrxHidden && on) return; | ||||||
|  |     } | ||||||
| 	if(item.openwebrxDisableClick) return; | 	if(item.openwebrxDisableClick) return; | ||||||
| 	item.style.transitionDuration="599ms"; | 	item.style.transitionDuration="599ms"; | ||||||
| 	item.style.transitionDelay="0ms"; | 	item.style.transitionDelay="0ms"; | ||||||
| @@ -2167,6 +2454,7 @@ function place_panels(function_apply) | |||||||
| 		p.style.visibility="visible"; | 		p.style.visibility="visible"; | ||||||
| 		y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin; | 		y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin; | ||||||
| 		if(function_apply) function_apply(p); | 		if(function_apply) function_apply(p); | ||||||
|  |         //console.log(p.id, y, p.openwebrxPanelTransparent); | ||||||
| 	} | 	} | ||||||
| 	y=hoffset; | 	y=hoffset; | ||||||
| 	while(right_col.length>0) | 	while(right_col.length>0) | ||||||
| @@ -2175,7 +2463,7 @@ function place_panels(function_apply) | |||||||
| 		p.style.right=(e("webrx-canvas-container").offsetWidth-e("webrx-canvas-container").clientWidth).toString()+"px"; //get scrollbar width | 		p.style.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); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -2199,3 +2487,331 @@ function progressbar_set(obj,val,text,over) | |||||||
| 	if(innerText==null) return; | 	if(innerText==null) return; | ||||||
| 	innerText.innerHTML=text; | 	innerText.innerHTML=text; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function demodulator_buttons_update() | ||||||
|  | { | ||||||
|  | 	$(".openwebrx-demodulator-button").removeClass("highlighted"); | ||||||
|  |     if(secondary_demod) $("#openwebrx-button-dig").addClass("highlighted"); | ||||||
|  |     else switch(demodulators[0].subtype) | ||||||
|  | 	{ | ||||||
|  | 	case "nfm": | ||||||
|  | 		$("#openwebrx-button-nfm").addClass("highlighted"); | ||||||
|  | 		break; | ||||||
|  | 	case "am": | ||||||
|  | 		$("#openwebrx-button-am").addClass("highlighted"); | ||||||
|  | 		break; | ||||||
|  | 	case "lsb": | ||||||
|  | 	case "usb": | ||||||
|  | 	case "cw": | ||||||
|  | 		if(demodulators[0].high_cut-demodulators[0].low_cut<300) | ||||||
|  | 			$("#openwebrx-button-cw").addClass("highlighted"); | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			if(demodulators[0].high_cut<0)  | ||||||
|  | 				$("#openwebrx-button-lsb").addClass("highlighted"); | ||||||
|  | 			else if(demodulators[0].low_cut>0)  | ||||||
|  | 				$("#openwebrx-button-usb").addClass("highlighted"); | ||||||
|  | 			else $("#openwebrx-button-lsb, #openwebrx-button-usb").addClass("highlighted"); | ||||||
|  | 		} | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | function demodulator_analog_replace_last() { demodulator_analog_replace(last_analog_demodulator_subtype); } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |   _____  _       _                     _            | ||||||
|  |  |  __ \(_)     (_)                   | |           | ||||||
|  |  | |  | |_  __ _ _ _ __ ___   ___   __| | ___  ___  | ||||||
|  |  | |  | | |/ _` | | '_ ` _ \ / _ \ / _` |/ _ \/ __| | ||||||
|  |  | |__| | | (_| | | | | | | | (_) | (_| |  __/\__ \ | ||||||
|  |  |_____/|_|\__, |_|_| |_| |_|\___/ \__,_|\___||___/ | ||||||
|  |             __/ |                                   | ||||||
|  |            |___/                                    | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | secondary_demod = false; | ||||||
|  | secondary_demod_offset_freq = 0; | ||||||
|  | secondary_demod_waterfall_queue = []; | ||||||
|  |  | ||||||
|  | function demodulator_digital_replace_last()  | ||||||
|  | {  | ||||||
|  |     demodulator_digital_replace(last_digital_demodulator_subtype);  | ||||||
|  |     secondary_demod_listbox_update(); | ||||||
|  | } | ||||||
|  | function demodulator_digital_replace(subtype) | ||||||
|  | { | ||||||
|  |     switch(subtype)  | ||||||
|  |     { | ||||||
|  |     case "bpsk31": | ||||||
|  |     case "rtty": | ||||||
|  |         secondary_demod_start(subtype); | ||||||
|  |         demodulator_analog_replace('usb', true); | ||||||
|  |         demodulator_buttons_update(); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     toggle_panel("openwebrx-panel-digimodes", true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_create_canvas() | ||||||
|  | { | ||||||
|  | 	var new_canvas = document.createElement("canvas"); | ||||||
|  | 	new_canvas.width=secondary_fft_size; | ||||||
|  | 	new_canvas.height=$(secondary_demod_canvas_container).height(); | ||||||
|  | 	new_canvas.style.width=$(secondary_demod_canvas_container).width()+"px"; | ||||||
|  | 	new_canvas.style.height=$(secondary_demod_canvas_container).height()+"px"; | ||||||
|  |     console.log(new_canvas.width, new_canvas.height, new_canvas.style.width, new_canvas.style.height); | ||||||
|  | 	secondary_demod_current_canvas_actual_line=new_canvas.height-1; | ||||||
|  | 	$(secondary_demod_canvas_container).children().last().before(new_canvas); | ||||||
|  |     return new_canvas; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_remove_canvases() | ||||||
|  | { | ||||||
|  |     $(secondary_demod_canvas_container).children("canvas").remove(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_init_canvases() | ||||||
|  | { | ||||||
|  |     secondary_demod_remove_canvases(); | ||||||
|  |     secondary_demod_canvases=[]; | ||||||
|  |     secondary_demod_canvases.push(secondary_demod_create_canvas()); | ||||||
|  |     secondary_demod_canvases.push(secondary_demod_create_canvas()); | ||||||
|  |     secondary_demod_canvases[0].openwebrx_top=-$(secondary_demod_canvas_container).height(); | ||||||
|  |     secondary_demod_canvases[1].openwebrx_top=0; | ||||||
|  |     secondary_demod_canvases_update_top(); | ||||||
|  |     secondary_demod_current_canvas_context = secondary_demod_canvases[0].getContext("2d"); | ||||||
|  |     secondary_demod_current_canvas_actual_line=$(secondary_demod_canvas_container).height()-1; | ||||||
|  |     secondary_demod_current_canvas_index=0; | ||||||
|  |     secondary_demod_canvases_initialized=true; | ||||||
|  |     //secondary_demod_update_channel_freq_from_event(); | ||||||
|  |     mkscale(); //so that the secondary waterfall zoom level will be initialized | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_canvases_update_top() | ||||||
|  | { | ||||||
|  |     for(var i=0;i<2;i++) secondary_demod_canvases[i].style.top=secondary_demod_canvases[i].openwebrx_top+"px"; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_swap_canvases() | ||||||
|  | { | ||||||
|  |     console.log("swap"); | ||||||
|  |     secondary_demod_canvases[0+!secondary_demod_current_canvas_index].openwebrx_top-=$(secondary_demod_canvas_container).height()*2; | ||||||
|  |     secondary_demod_current_canvas_index=0+!secondary_demod_current_canvas_index; | ||||||
|  |     secondary_demod_current_canvas_context = secondary_demod_canvases[secondary_demod_current_canvas_index].getContext("2d"); | ||||||
|  |     secondary_demod_current_canvas_actual_line=$(secondary_demod_canvas_container).height()-1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_init() | ||||||
|  | { | ||||||
|  |     $("#openwebrx-panel-digimodes")[0].openwebrxHidden = true; | ||||||
|  |     secondary_demod_canvas_container = $("#openwebrx-digimode-canvas-container")[0]; | ||||||
|  |     $(secondary_demod_canvas_container) | ||||||
|  |         .mousemove(secondary_demod_canvas_container_mousemove) | ||||||
|  |         .mouseup(secondary_demod_canvas_container_mouseup) | ||||||
|  |         .mousedown(secondary_demod_canvas_container_mousedown) | ||||||
|  |         .mouseenter(secondary_demod_canvas_container_mousein) | ||||||
|  |         .mouseleave(secondary_demod_canvas_container_mouseout); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_start(subtype)  | ||||||
|  | {  | ||||||
|  |     secondary_demod_canvases_initialized = false; | ||||||
|  |     ws.send("SET secondary_mod="+subtype);  | ||||||
|  |     secondary_demod = subtype;  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_set() | ||||||
|  | { | ||||||
|  |     ws.send("SET secondary_offset_freq="+secondary_demod_offset_freq.toString()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_stop() | ||||||
|  | { | ||||||
|  |     ws.send("SET secondary_mod=off"); | ||||||
|  |     secondary_demod = false;  | ||||||
|  |     secondary_demod_waterfall_queue = []; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_waterfall_add_queue(x) | ||||||
|  | { | ||||||
|  |     secondary_demod_waterfall_queue.push(x); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_push_binary_data(x) | ||||||
|  | { | ||||||
|  |     secondary_demod_push_data(Array.from(x).map( y => (y)?"1":"0" ).join("")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_push_data(x) | ||||||
|  | { | ||||||
|  |     x=Array.from(x).map((y)=>{ | ||||||
|  |         var c=y.charCodeAt(0); | ||||||
|  |         if(y=="\r") return " "; | ||||||
|  |         if(y=="\n") return " "; | ||||||
|  |         //if(y=="\n") return "<br />"; | ||||||
|  |         if(c<32||c>126) return ""; | ||||||
|  |         if(y=="&") return "&"; | ||||||
|  |         if(y=="<") return "<"; | ||||||
|  |         if(y==">") return ">"; | ||||||
|  |         if(y==" ") return " "; | ||||||
|  |         return y; | ||||||
|  |     }).join(""); | ||||||
|  |     $("#openwebrx-cursor-blink").before("<span class=\"part\"><span class=\"subpart\">"+x+"</span></span>"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_data_clear() | ||||||
|  | { | ||||||
|  |     $("#openwebrx-cursor-blink").prevAll().remove(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_close_window() | ||||||
|  | { | ||||||
|  |     secondary_demod_stop(); | ||||||
|  |     toggle_panel("openwebrx-panel-digimodes", false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | secondary_demod_fft_offset_db=30; //need to calculate that later | ||||||
|  |  | ||||||
|  | function secondary_demod_waterfall_add(data) | ||||||
|  | { | ||||||
|  |     if(!secondary_demod) return; | ||||||
|  | 	var w=secondary_fft_size; | ||||||
|  |  | ||||||
|  | 	//Add line to waterfall image | ||||||
|  | 	var oneline_image = secondary_demod_current_canvas_context.createImageData(w,1); | ||||||
|  | 	for(x=0;x<w;x++) | ||||||
|  | 	{ | ||||||
|  | 		var color=waterfall_mkcolor(data[x]+secondary_demod_fft_offset_db); | ||||||
|  | 		for(i=0;i<4;i++) oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Draw image | ||||||
|  | 	secondary_demod_current_canvas_context.putImageData(oneline_image, 0, secondary_demod_current_canvas_actual_line--); | ||||||
|  |     secondary_demod_canvases.map((x)=>{x.openwebrx_top += 1;}); | ||||||
|  |     secondary_demod_canvases_update_top(); | ||||||
|  | 	if(secondary_demod_current_canvas_actual_line<0) secondary_demod_swap_canvases(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var secondary_demod_canvases_initialized = false; | ||||||
|  |  | ||||||
|  | function secondary_demod_waterfall_dequeue() | ||||||
|  | { | ||||||
|  |     if(!secondary_demod || !secondary_demod_canvases_initialized) return; | ||||||
|  | 	if(secondary_demod_waterfall_queue.length) secondary_demod_waterfall_add(secondary_demod_waterfall_queue.shift()); | ||||||
|  | 	if(secondary_demod_waterfall_queue.length>Math.max(fft_fps/2,20)) //in case of fft overflow | ||||||
|  | 	{ | ||||||
|  | 		console.log("secondary waterfall overflow, queue length:", secondary_demod_waterfall_queue.length); | ||||||
|  | 		while(secondary_demod_waterfall_queue.length) secondary_demod_waterfall_add(secondary_demod_waterfall_queue.shift()); | ||||||
|  | 	} | ||||||
|  | }  | ||||||
|  |  | ||||||
|  | secondary_demod_listbox_updating = false; | ||||||
|  | function secondary_demod_listbox_changed() | ||||||
|  | { | ||||||
|  |     if(secondary_demod_listbox_updating) return; | ||||||
|  |     switch ($("#openwebrx-secondary-demod-listbox")[0].value) | ||||||
|  |     { | ||||||
|  |         case "none": | ||||||
|  |             demodulator_analog_replace_last(); | ||||||
|  |             break; | ||||||
|  |         case "bpsk31": | ||||||
|  |             demodulator_digital_replace('bpsk31'); | ||||||
|  |             break; | ||||||
|  |         case "rtty": | ||||||
|  |             demodulator_digital_replace('rtty'); | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_listbox_update() | ||||||
|  | { | ||||||
|  |     secondary_demod_listbox_updating = true; | ||||||
|  |     $("#openwebrx-secondary-demod-listbox").val((secondary_demod)?secondary_demod:"none"); | ||||||
|  |     console.log("update"); | ||||||
|  |     secondary_demod_listbox_updating = false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | secondary_demod_channel_freq=1000; | ||||||
|  | function secondary_demod_update_marker() | ||||||
|  | { | ||||||
|  |     var width =  Math.max( (secondary_bw / (if_samp_rate/2)) * secondary_demod_canvas_width, 5); | ||||||
|  |     var center_at = (secondary_demod_channel_freq / (if_samp_rate/2)) * secondary_demod_canvas_width + secondary_demod_canvas_left; | ||||||
|  |     var left = center_at-width/2; | ||||||
|  |     //console.log("sdum", width, left); | ||||||
|  |     $("#openwebrx-digimode-select-channel").width(width).css("left",left+"px")  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | secondary_demod_waiting_for_set = false; | ||||||
|  | function secondary_demod_update_channel_freq_from_event(evt) | ||||||
|  | { | ||||||
|  |     if(typeof evt !== "undefined") | ||||||
|  |     { | ||||||
|  |         var relativeX=(evt.offsetX)?evt.offsetX:evt.layerX; | ||||||
|  |         secondary_demod_channel_freq=secondary_demod_low_cut +  | ||||||
|  |             (relativeX/$(secondary_demod_canvas_container).width()) * (secondary_demod_high_cut-secondary_demod_low_cut); | ||||||
|  |     } | ||||||
|  |     //console.log("toset:", secondary_demod_channel_freq); | ||||||
|  |     if(!secondary_demod_waiting_for_set) | ||||||
|  |     { | ||||||
|  |         secondary_demod_waiting_for_set = true; | ||||||
|  |         window.setTimeout(()=>{ | ||||||
|  |             ws.send("SET secondary_offset_freq="+Math.floor(secondary_demod_channel_freq)); | ||||||
|  |             //console.log("doneset:", secondary_demod_channel_freq); | ||||||
|  |             secondary_demod_waiting_for_set = false; | ||||||
|  |         }, 50); | ||||||
|  |     } | ||||||
|  |     secondary_demod_update_marker(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | secondary_demod_mousedown=false; | ||||||
|  | function secondary_demod_canvas_container_mousein() | ||||||
|  | { | ||||||
|  |     $("#openwebrx-digimode-select-channel").css("opacity","0.7"); //.css("border-width", "1px"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_canvas_container_mouseout() | ||||||
|  | { | ||||||
|  |     $("#openwebrx-digimode-select-channel").css("opacity","0"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_canvas_container_mousemove(evt) | ||||||
|  | { | ||||||
|  |     if(secondary_demod_mousedown) secondary_demod_update_channel_freq_from_event(evt); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_canvas_container_mousedown(evt) | ||||||
|  | { | ||||||
|  |     if(evt.which==1) secondary_demod_mousedown=true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function secondary_demod_canvas_container_mouseup(evt) | ||||||
|  | { | ||||||
|  |     if(evt.which==1) secondary_demod_mousedown=false; | ||||||
|  |     secondary_demod_update_channel_freq_from_event(evt); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function secondary_demod_waterfall_set_zoom(low_cut, high_cut) | ||||||
|  | { | ||||||
|  |     if(!secondary_demod || !secondary_demod_canvases_initialized) return; | ||||||
|  |     if(low_cut<0 && high_cut<0) | ||||||
|  |     { | ||||||
|  |         var hctmp = high_cut; | ||||||
|  |         var lctmp = low_cut; | ||||||
|  |         low_cut = -hctmp; | ||||||
|  |         low_cut = -lctmp; | ||||||
|  |     } | ||||||
|  |     else if(low_cut<0 && high_cut>0) | ||||||
|  |     { | ||||||
|  |         high_cut=Math.max(Math.abs(high_cut), Math.abs(low_cut)); | ||||||
|  |         low_cut=0; | ||||||
|  |     } | ||||||
|  |     secondary_demod_low_cut = low_cut; | ||||||
|  |     secondary_demod_high_cut = high_cut; | ||||||
|  |     var shown_bw = high_cut-low_cut; | ||||||
|  |     secondary_demod_canvas_width = $(secondary_demod_canvas_container).width()  * (if_samp_rate/2)/shown_bw; | ||||||
|  |     secondary_demod_canvas_left = -secondary_demod_canvas_width*(low_cut/(if_samp_rate/2)); | ||||||
|  |     //console.log("setzoom", secondary_demod_canvas_width, secondary_demod_canvas_left, low_cut, high_cut); | ||||||
|  |     secondary_demod_canvases.map((x)=>{$(x).css("left",secondary_demod_canvas_left+"px").css("width",secondary_demod_canvas_width+"px");}); | ||||||
|  |     secondary_demod_update_channel_freq_from_event(); | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										1087
									
								
								openwebrx.py
									
									
									
									
									
								
							
							
						
						
									
										1087
									
								
								openwebrx.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,294 +0,0 @@ | |||||||
| """ |  | ||||||
| OpenWebRX csdr plugin: do the signal processing with csdr |  | ||||||
|  |  | ||||||
| 	This file is part of OpenWebRX, |  | ||||||
| 	an open-source SDR receiver software with a web UI. |  | ||||||
| 	Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu> |  | ||||||
|  |  | ||||||
|     This program is free software: you can redistribute it and/or modify |  | ||||||
|     it under the terms of the GNU Affero General Public License as |  | ||||||
|     published by the Free Software Foundation, either version 3 of the |  | ||||||
|     License, or (at your option) any later version. |  | ||||||
|  |  | ||||||
|     This program is distributed in the hope that it will be useful, |  | ||||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|     GNU Affero General Public License for more details. |  | ||||||
|  |  | ||||||
|     You should have received a copy of the GNU Affero General Public License |  | ||||||
|     along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import subprocess |  | ||||||
| import time |  | ||||||
| import os |  | ||||||
| import code |  | ||||||
| import signal |  | ||||||
| import fcntl |  | ||||||
| import select |  | ||||||
|  |  | ||||||
| class dsp_plugin: |  | ||||||
|  |  | ||||||
| 	def __init__(self): |  | ||||||
| 		self.samp_rate = 250000 |  | ||||||
| 		self.output_rate = 11025 #this is default, and cannot be set at the moment |  | ||||||
| 		self.fft_size = 1024 |  | ||||||
| 		self.fft_fps = 5 |  | ||||||
| 		self.offset_freq = 0 |  | ||||||
| 		self.low_cut = -4000 |  | ||||||
| 		self.high_cut = 4000 |  | ||||||
| 		self.bpf_transition_bw = 320 #Hz, and this is a constant |  | ||||||
| 		self.ddc_transition_bw_rate = 0.15 # of the IF sample rate |  | ||||||
| 		self.running = False |  | ||||||
| 		self.audio_compression = "none" |  | ||||||
| 		self.fft_compression = "none" |  | ||||||
| 		self.demodulator = "nfm" |  | ||||||
| 		self.name = "csdr" |  | ||||||
| 		self.format_conversion = "csdr convert_u8_f" |  | ||||||
| 		self.base_bufsize = 512 |  | ||||||
| 		self.nc_port = 4951 |  | ||||||
| 		self.csdr_dynamic_bufsize = False |  | ||||||
| 		self.csdr_print_bufsizes = False |  | ||||||
| 		self.csdr_through = False |  | ||||||
| 		self.squelch_level = 0 |  | ||||||
|  |  | ||||||
| 	def chain(self,which): |  | ||||||
| 		if which in [ "dmr", "dstar", "nxdn", "ysf" ]: |  | ||||||
| 			self.set_output_rate(48000) |  | ||||||
| 		else: |  | ||||||
| 			self.set_output_rate(11025) |  | ||||||
| 		any_chain_base="ncat -v 127.0.0.1 {nc_port} | " |  | ||||||
| 		if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | " |  | ||||||
| 		if self.csdr_through: any_chain_base+="csdr through | " |  | ||||||
| 		any_chain_base+=self.format_conversion+(" | " if  self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | " |  | ||||||
| 		if which == "fft": |  | ||||||
| 			fft_chain_base = any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | csdr logpower_cf -70 | csdr fft_exchange_sides_ff {fft_size}" |  | ||||||
| 			if self.fft_compression=="adpcm": |  | ||||||
| 				return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}" |  | ||||||
| 			else: |  | ||||||
| 				return fft_chain_base |  | ||||||
| 		chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | " |  | ||||||
| 		chain_end = "" |  | ||||||
| 		if self.audio_compression=="adpcm": |  | ||||||
| 			chain_end = " | csdr encode_ima_adpcm_i16_u8" |  | ||||||
| 		if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr convert_f_s16"+chain_end |  | ||||||
| 		if which in [ "dstar", "nxdn" ]: |  | ||||||
| 			c = chain_begin |  | ||||||
| 			c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16" |  | ||||||
| 			if which == "dstar": |  | ||||||
| 				c += " | dsd -fd" |  | ||||||
| 			elif which == "nxdn": |  | ||||||
| 				c += " | dsd -fi" |  | ||||||
| 			c += " -i - -o - -u 2 -g 10" |  | ||||||
| 			c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --input-buffer 160 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 220" |  | ||||||
| 			c += chain_end |  | ||||||
| 			return c |  | ||||||
| 		elif which == "dmr": |  | ||||||
| 			c = chain_begin |  | ||||||
| 			c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16" |  | ||||||
| 			c += " | rrc_filter | gfsk_demodulator | dmr_decoder --fifo {meta_pipe} | mbe_synthesizer" |  | ||||||
| 			c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256" |  | ||||||
| 			c += chain_end |  | ||||||
| 			return c |  | ||||||
| 		elif which == "ysf": |  | ||||||
| 			c = chain_begin |  | ||||||
| 			c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16" |  | ||||||
| 			c += " | rrc_filter | gfsk_demodulator | ysf_decoder --fifo {meta_pipe} | mbe_synthesizer -y" |  | ||||||
| 			c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256" |  | ||||||
| 			c += chain_end |  | ||||||
| 			return c |  | ||||||
| 		elif which == "am":	return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end |  | ||||||
| 		elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end |  | ||||||
|  |  | ||||||
| 	def set_audio_compression(self,what): |  | ||||||
| 		self.audio_compression = what |  | ||||||
|  |  | ||||||
| 	def set_fft_compression(self,what): |  | ||||||
| 		self.fft_compression = what |  | ||||||
|  |  | ||||||
| 	def get_fft_bytes_to_read(self): |  | ||||||
| 		if self.fft_compression=="none": return self.fft_size*4 |  | ||||||
| 		if self.fft_compression=="adpcm": return (self.fft_size/2)+(10/2) |  | ||||||
|  |  | ||||||
| 	def set_samp_rate(self,samp_rate): |  | ||||||
| 		#to change this, restart is required |  | ||||||
| 		self.samp_rate=samp_rate |  | ||||||
| 		self.decimation=1 |  | ||||||
| 		while self.samp_rate/(self.decimation+1)>=self.output_rate: |  | ||||||
| 			self.decimation+=1 |  | ||||||
| 		self.last_decimation=float(self.if_samp_rate())/self.output_rate |  | ||||||
|  |  | ||||||
| 	def if_samp_rate(self): |  | ||||||
| 		return self.samp_rate/self.decimation |  | ||||||
|  |  | ||||||
| 	def get_name(self): |  | ||||||
| 		return self.name |  | ||||||
|  |  | ||||||
| 	def get_output_rate(self): |  | ||||||
| 		return self.output_rate |  | ||||||
|  |  | ||||||
| 	def set_output_rate(self,output_rate): |  | ||||||
| 		self.output_rate=output_rate |  | ||||||
| 		self.set_samp_rate(self.samp_rate) #as it depends on output_rate |  | ||||||
|  |  | ||||||
| 	def set_demodulator(self,demodulator): |  | ||||||
| 		#to change this, restart is required |  | ||||||
| 		self.demodulator=demodulator |  | ||||||
|  |  | ||||||
| 	def get_demodulator(self): |  | ||||||
| 		return self.demodulator |  | ||||||
|  |  | ||||||
| 	def set_fft_size(self,fft_size): |  | ||||||
| 		#to change this, restart is required |  | ||||||
| 		self.fft_size=fft_size |  | ||||||
|  |  | ||||||
| 	def set_fft_fps(self,fft_fps): |  | ||||||
| 		#to change this, restart is required |  | ||||||
| 		self.fft_fps=fft_fps |  | ||||||
|  |  | ||||||
| 	def fft_block_size(self): |  | ||||||
| 		return self.samp_rate/self.fft_fps |  | ||||||
|  |  | ||||||
| 	def set_format_conversion(self,format_conversion): |  | ||||||
| 		self.format_conversion=format_conversion |  | ||||||
|  |  | ||||||
| 	def set_offset_freq(self,offset_freq): |  | ||||||
| 		self.offset_freq=offset_freq |  | ||||||
| 		if self.running: |  | ||||||
| 			self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate)) |  | ||||||
| 			self.shift_pipe_file.flush() |  | ||||||
|  |  | ||||||
| 	def set_bpf(self,low_cut,high_cut): |  | ||||||
| 		self.low_cut=low_cut |  | ||||||
| 		self.high_cut=high_cut |  | ||||||
| 		if self.running: |  | ||||||
| 			self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) ) |  | ||||||
| 			self.bpf_pipe_file.flush() |  | ||||||
|  |  | ||||||
| 	def get_bpf(self): |  | ||||||
| 		return [self.low_cut, self.high_cut] |  | ||||||
|  |  | ||||||
| 	def set_squelch_level(self, squelch_level): |  | ||||||
| 		self.squelch_level=squelch_level |  | ||||||
| 		if self.running: |  | ||||||
| 			self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) ) |  | ||||||
| 			self.squelch_pipe_file.flush() |  | ||||||
|  |  | ||||||
| 	def get_smeter_level(self): |  | ||||||
| 		if self.running: |  | ||||||
| 			line=self.smeter_pipe_file.readline() |  | ||||||
| 			return float(line[:-1]) |  | ||||||
|  |  | ||||||
| 	def get_metadata(self): |  | ||||||
| 		if self.running and self.meta_pipe: |  | ||||||
| 			return self.meta_pipe_file.readline() |  | ||||||
|  |  | ||||||
| 	def mkfifo(self,path): |  | ||||||
| 		try: |  | ||||||
| 			os.unlink(path) |  | ||||||
| 		except: |  | ||||||
| 			pass |  | ||||||
| 		os.mkfifo(path) |  | ||||||
|  |  | ||||||
| 	def ddc_transition_bw(self): |  | ||||||
| 		return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate)) |  | ||||||
|  |  | ||||||
| 	def start(self): |  | ||||||
| 		command_base=self.chain(self.demodulator) |  | ||||||
|  |  | ||||||
| 		#create control pipes for csdr |  | ||||||
| 		pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self)) |  | ||||||
| 		self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = self.meta_pipe = None |  | ||||||
| 		if "{bpf_pipe}" in command_base: |  | ||||||
| 			self.bpf_pipe=pipe_base_path+"bpf" |  | ||||||
| 			self.mkfifo(self.bpf_pipe) |  | ||||||
| 		if "{shift_pipe}" in command_base: |  | ||||||
| 			self.shift_pipe=pipe_base_path+"shift" |  | ||||||
| 			self.mkfifo(self.shift_pipe) |  | ||||||
| 		if "{squelch_pipe}" in command_base: |  | ||||||
| 			self.squelch_pipe=pipe_base_path+"squelch" |  | ||||||
| 			self.mkfifo(self.squelch_pipe) |  | ||||||
| 		if "{smeter_pipe}" in command_base: |  | ||||||
| 			self.smeter_pipe=pipe_base_path+"smeter" |  | ||||||
| 			self.mkfifo(self.smeter_pipe) |  | ||||||
| 		if "{meta_pipe}" in command_base: |  | ||||||
| 			self.meta_pipe=pipe_base_path+"meta" |  | ||||||
| 			self.mkfifo(self.meta_pipe) |  | ||||||
| 		else: |  | ||||||
| 			self.meta_pipe=None |  | ||||||
|  |  | ||||||
| 		#run the command |  | ||||||
| 		command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \ |  | ||||||
| 			last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), \ |  | ||||||
| 			bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(), ddc_transition_bw=self.ddc_transition_bw(), \ |  | ||||||
| 			flowcontrol=int(self.samp_rate*2), start_bufsize=self.base_bufsize*self.decimation, nc_port=self.nc_port, \ |  | ||||||
| 			squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe, meta_pipe=self.meta_pipe ) |  | ||||||
|  |  | ||||||
| 		print "[openwebrx-dsp-plugin:csdr] Command =",command |  | ||||||
| 		#code.interact(local=locals()) |  | ||||||
| 		my_env=os.environ.copy() |  | ||||||
| 		if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1"; |  | ||||||
| 		if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1"; |  | ||||||
| 		self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) |  | ||||||
| 		self.running = True |  | ||||||
|  |  | ||||||
| 		#open control pipes for csdr and send initialization data |  | ||||||
| 		if self.bpf_pipe != None: |  | ||||||
| 			self.bpf_pipe_file=open(self.bpf_pipe,"w") |  | ||||||
| 			self.set_bpf(self.low_cut,self.high_cut) |  | ||||||
| 		if self.shift_pipe != None: |  | ||||||
| 			self.shift_pipe_file=open(self.shift_pipe,"w") |  | ||||||
| 			self.set_offset_freq(self.offset_freq) |  | ||||||
| 		if self.squelch_pipe != None: |  | ||||||
| 			self.squelch_pipe_file=open(self.squelch_pipe,"w") |  | ||||||
| 			self.set_squelch_level(self.squelch_level) |  | ||||||
| 		if self.smeter_pipe != None: |  | ||||||
| 			self.smeter_pipe_file=open(self.smeter_pipe,"r") |  | ||||||
| 			fcntl.fcntl(self.smeter_pipe_file, fcntl.F_SETFL, os.O_NONBLOCK) |  | ||||||
| 		if self.meta_pipe != None: |  | ||||||
| 			self.meta_pipe_file=open(self.meta_pipe,"r") |  | ||||||
| 			fcntl.fcntl(self.meta_pipe_file, fcntl.F_SETFL, os.O_NONBLOCK) |  | ||||||
|  |  | ||||||
| 	def read(self,size): |  | ||||||
| 		return self.process.stdout.read(size) |  | ||||||
|  |  | ||||||
| 	def read_async(self, size): |  | ||||||
| 		if (select.select([self.process.stdout], [], [], 0)[0] != []): |  | ||||||
| 			return self.process.stdout.read(size) |  | ||||||
| 		else: |  | ||||||
| 			return None |  | ||||||
|  |  | ||||||
| 	def stop(self): |  | ||||||
| 		os.killpg(os.getpgid(self.process.pid), signal.SIGTERM) |  | ||||||
| 		#if(self.process.poll()!=None):return # returns None while subprocess is running |  | ||||||
| 		#while(self.process.poll()==None): |  | ||||||
| 		#	#self.process.kill() |  | ||||||
| 		#	print "killproc",os.getpgid(self.process.pid),self.process.pid |  | ||||||
| 		#	os.killpg(self.process.pid, signal.SIGTERM) |  | ||||||
| 		# |  | ||||||
| 		#	time.sleep(0.1) |  | ||||||
| 		if self.bpf_pipe: |  | ||||||
| 			try: os.unlink(self.bpf_pipe) |  | ||||||
| 			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe |  | ||||||
| 		if self.shift_pipe: |  | ||||||
| 			try: os.unlink(self.shift_pipe) |  | ||||||
| 			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe |  | ||||||
| 		if self.squelch_pipe: |  | ||||||
| 			try: os.unlink(self.squelch_pipe) |  | ||||||
| 			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe |  | ||||||
| 		if self.smeter_pipe: |  | ||||||
| 			try: os.unlink(self.smeter_pipe) |  | ||||||
| 			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe |  | ||||||
| 		if self.meta_pipe: |  | ||||||
| 			try: os.unlink(self.meta_pipe) |  | ||||||
| 			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.meta_pipe |  | ||||||
| 		self.running = False |  | ||||||
|  |  | ||||||
| 	def restart(self): |  | ||||||
| 		self.stop() |  | ||||||
| 		self.start() |  | ||||||
|  |  | ||||||
| 	def __del__(self): |  | ||||||
| 		self.stop() |  | ||||||
| 		del(self.process) |  | ||||||
							
								
								
									
										230
									
								
								rxws.py
									
									
									
									
									
								
							
							
						
						
									
										230
									
								
								rxws.py
									
									
									
									
									
								
							| @@ -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 | 
							
								
								
									
										
											BIN
										
									
								
								screenshot.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								screenshot.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.4 MiB | 
							
								
								
									
										51
									
								
								sdrhu.py
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								sdrhu.py
									
									
									
									
									
								
							| @@ -1,9 +1,9 @@ | |||||||
| #!/usr/bin/python2 | #!/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 -4qO- https://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1" | ||||||
| 		#print "[openwebrx-sdrhu]", cmd |         print "[openwebrx-sdrhu]", cmd | ||||||
| 		returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate() |         returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate() | ||||||
| 		returned=returned[0] |         returned=returned[0] | ||||||
| 		#print returned |         #print returned | ||||||
| 		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) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jakob Ketterl
					Jakob Ketterl