many fixes and new features like IMA ADPCM compression

This commit is contained in:
ha7ilm 2015-08-17 20:32:58 +02:00
parent f388e80624
commit 0713f57bb4
32 changed files with 12695 additions and 272 deletions

View File

@ -14,6 +14,14 @@ It has the following features:
- 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 only supports RTL-SDR, but other SDR hardware may be easily added. - currently only supports RTL-SDR, but other SDR hardware may be easily added.
**News:**
- My BSc. thesis written on OpenWebRX is <a href="http://openwebrx.org/bsc-thesis.pdf">available here.</a>
- Several bugs were fixed to improve reliability and stability.
- OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.)
- OpenWebRX now uses <a href="https://github.com/simonyiszk/csdr#sdr.js">sdr.js</a> (*libcsdr* compiled to JavaScript) for some client-side DSP tasks.
- Auto-update capability for <a href="http://sdr.hu/">sdr.hu</a> added (currently only <a href="http://sdr.hu/">sdr.hu</a> beta testers can use it).
- License for OpenWebRX is now Affero GPL v3.
## Setup ## Setup
OpenWebRX currently requires Linux and python 2.7 to run. OpenWebRX currently requires Linux and python 2.7 to run.

44
config_rtl.py Executable file → Normal file
View File

@ -1,20 +1,30 @@
''' '''
This file is part of RTL Multi-User Server, This file is part of RTL Multi-User Server,
that makes multi-user access to your DVB-T dongle used as an SDR. that makes multi-user access to your DVB-T dongle used as an SDR.
Copyright (c) 2013-2014 by Andras Retzler <randras@sdr.hu> Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
RTL Multi-User Server 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 General Public License as published by it under the terms of the GNU Affero General Public License as
the Free Software Foundation, either version 3 of the License, or published by the Free Software Foundation, either version 3 of the
(at your option) any later version. License, or (at your option) any later version.
RTL Multi-User Server is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with RTL Multi-User Server. 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
state that config_rtl.py and config_webrx.py are not part of the
Corresponding Source defined in GNU AGPL version 3 section 1.
(It means that you do not have to redistribute config_rtl.py and
config_webrx.py if you make any changes to these two configuration files,
and use them for running your own web service with OpenWebRX.)
''' '''
my_ip='127.0.0.1' # leave blank for listening on all interfaces my_ip='127.0.0.1' # leave blank for listening on all interfaces
@ -70,11 +80,11 @@ Example DSP commands:
* Decompress FLAC-coded I/Q data: * Decompress FLAC-coded I/Q data:
flac --force-raw-format --decode --endian=little --sign=unsigned - - flac --force-raw-format --decode --endian=little --sign=unsigned - -
''' '''
watchdog_interval=1.5 watchdog_interval=0
reconnect_interval=10 reconnect_interval=10
''' '''
If there's no input I/Q data after N seconds, input will be filled with zero samples, If there's no input I/Q data after N seconds, input will be filled with zero samples,
so that GNU Radio won't fail in openwebrx. It may reconnect rtl_tcp_tread. so that GNU Radio won't fail in OpenWebRX. It may reconnect rtl_tcp_thread.
If watchdog_interval is 0, then watchdog thread is not started. If watchdog_interval is 0, then watchdog thread is not started.
''' '''
@ -85,3 +95,9 @@ cache_full_behaviour=2
2 = openwebrx: don't care about that client until it wants samples again (gr-osmosdr bug workaround) 2 = openwebrx: don't care about that client until it wants samples again (gr-osmosdr bug workaround)
''' '''
rtl_tcp_password=None
'''
This one applies to a special version of rtl_tcp that has authentication.
# You can find more info here: https://github.com/ha7ilm/rtl-sdr
# If it is set to a string (e.g. rtl_tcp_password="changeme"), rtl_mus will try to authenticate against the rtl_tcp server.
'''

91
config_webrx.py Executable file → Normal file
View File

@ -3,36 +3,46 @@
""" """
config_webrx: configuration options for OpenWebRX config_webrx: configuration options for OpenWebRX
OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu> This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This file is part of OpenWebRX. 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.
OpenWebRX is free software: you can redistribute it and/or modify This program is distributed in the hope that it will be useful,
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with OpenWebRX. 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
state that config_rtl.py and config_webrx.py are not part of the
Corresponding Source defined in GNU AGPL version 3 section 1.
(It means that you do not have to redistribute config_rtl.py and
config_webrx.py if you make any changes to these two configuration files,
and use them for running your web service with OpenWebRX.)
""" """
#Server settings # ==== Server settings ====
web_port=8073 web_port=8073
server_hostname="localhost" # If this contains an incorrect value, the web UI may freeze on load (it can't open websocket) server_hostname="localhost" # If this contains an incorrect value, the web UI may freeze on load (it can't open websocket)
max_clients=20
#Web GUI configuration # ==== Web GUI configuration ====
receiver_name="[Callsign]" receiver_name="[Callsign]"
receiver_location="Budapest, Hungary" receiver_location="Budapest, Hungary"
receiver_qra="JN97ML" receiver_qra="JN97ML"
receiver_asl=182 receiver_asl=200
receiver_ant="Longwire" receiver_ant="Longwire"
receiver_device="RTL-SDR" receiver_device="RTL-SDR"
receiver_admin="localhost@localhost" receiver_admin="example@example.com"
receiver_gps=(47.000000,19.000000) receiver_gps=(47.000000,19.000000)
photo_height=350 photo_height=350
photo_title="Panorama of Budapest from Schönherz Zoltán Dormitory" photo_title="Panorama of Budapest from Schönherz Zoltán Dormitory"
@ -44,16 +54,57 @@ Antenna: %[RX_ANT]<br />
Website: <a href="http://localhost" target="_blank">http://localhost</a> Website: <a href="http://localhost" target="_blank">http://localhost</a>
""" """
#DSP/RX settings # ==== sdr.hu listing ====
# (This feature is only available to sdr.hu beta testers by now.)
# If you want your ham receiver to be listed publicly on sdr.hu, then take the following steps:
# 1. Register at: http://sdr.hu/register
# 2. You will get an unique key by email. Copy it and paste here:
sdrhu_key = ""
# 3. Set this setting to True to enable listing:
sdrhu_public_listing = False
# ==== DSP/RX settings ====
dsp_plugin="csdr" dsp_plugin="csdr"
fft_fps=9 fft_fps=9
fft_size=4096 fft_size=4096
samp_rate = 250000 samp_rate = 250000
center_freq = 145525000 center_freq = 145525000
rf_gain = 5 rf_gain = 5
ppm = 0
start_rtl_thread=True #rtl_sdr is more stable than rtl_tcp... audio_compression="adpcm" #valid values: "adpcm", "none"
start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} - | nc -vvl 127.0.0.1 -p 8888".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate) fft_compression="adpcm" #valid values: "adpcm", "none"
#start_rtl_tcp_command="rtl_tcp -s 250000 -f 145525000 -g 0 -p 8888"
#You can use other SDR hardware as well, but if the command above outputs samples in a format other than [unsigned char], then the dsp plugin has to be slightly modified (at the csdr convert_u8_f part).
start_rtl_thread=True
# ==== I/Q sources (uncomment the appropriate) ====
# >> RTL-SDR via rtl_sdr
start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} - | nc -vvl 127.0.0.1 8888".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
format_conversion="csdr convert_u8_f"
# >> Sound card SDR (needs ALSA)
#I did not have the chance to properly test it.
#samp_rate = 96000
#start_rtl_command="arecord -f S16_LE -r {samp_rate} -c2 - | nc -vvl 127.0.0.1 8888".format(samp_rate=samp_rate)
#format_conversion="csdr convert_i16_f | csdr gain_ff 30"
# >> RTL_SDR via rtl_tcp
#start_rtl_command="rtl_tcp -s {samp_rate} -f {center_freq} -g {rf_gain} -P {ppm} -p 8888".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
#format_conversion="csdr convert_u8_f"
# >> /dev/urandom test signal source
#samp_rate = 2400000
#start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1) | nc -vvl 127.0.0.1 8888".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
#format_conversion="csdr convert_u8_f"
#You can use other SDR hardware as well, by giving your own command that outputs the I/Q samples...
shown_center_freq = center_freq #you can change this if you use an upconverter
client_audio_buffer_size = 4
#increasing client_audio_buffer_size will:
# - also increase the latency
# - decrease the chance of audio underruns

0
htdocs/gfx/openwebrx-avatar-background.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 459 B

After

Width:  |  Height:  |  Size: 459 B

0
htdocs/gfx/openwebrx-avatar.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 742 B

After

Width:  |  Height:  |  Size: 742 B

0
htdocs/gfx/openwebrx-background-cool-blue.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

0
htdocs/gfx/openwebrx-background-lingrad.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 679 B

After

Width:  |  Height:  |  Size: 679 B

0
htdocs/gfx/openwebrx-ha5kfu-top-logo.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

0
htdocs/gfx/openwebrx-logo-big.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

0
htdocs/gfx/openwebrx-rx-details-arrow-up.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 518 B

0
htdocs/gfx/openwebrx-rx-details-arrow.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 505 B

After

Width:  |  Height:  |  Size: 505 B

0
htdocs/gfx/openwebrx-scale-background.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

0
htdocs/gfx/openwebrx-top-logo.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

0
htdocs/gfx/openwebrx-top-photo.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

51
htdocs/index.wrx Executable file → Normal file
View File

@ -1,31 +1,35 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<!-- <!--
OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu>
This file is part of OpenWebRX. This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
OpenWebRX 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 General Public License as published by it under the terms of the GNU Affero General Public License as
the Free Software Foundation, either version 3 of the License, or published by the Free Software Foundation, either version 3 of the
(at your option) any later version. License, or (at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. 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/>.
You should have received a copy of the GNU General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<html> <html>
<head> <head>
<title>OpenWebRX | Open Source Web-based SDR for everyone!</title> <title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
<script type="text/javascript"> <script type="text/javascript">
//Local variables //Local variables
client_id="%[CLIENT_ID]"; client_id="%[CLIENT_ID]";
ws_url="%[WS_URL]"; ws_url="%[WS_URL]";
rx_photo_height=%[RX_PHOTO_HEIGHT]; rx_photo_height=%[RX_PHOTO_HEIGHT];
var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
</script> </script>
<script src="sdr.js"></script>
<script src="openwebrx.js"></script> <script src="openwebrx.js"></script>
<link rel="stylesheet" type="text/css" href="openwebrx.css" /> <link rel="stylesheet" type="text/css" href="openwebrx.css" />
<meta charset="utf-8"> <meta charset="utf-8">
@ -50,6 +54,13 @@ This file is part of OpenWebRX.
<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">
<ul>
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
</ul>
</section>
</div> </div>
</div> </div>
<div id="webrx-main-container"> <div id="webrx-main-container">
@ -61,7 +72,7 @@ This file is part of OpenWebRX.
<!-- 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" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="215,70"> <div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="215,70">
<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-button" onclick="ws.send('SET mod=wfm');" >WFM</div>-->
@ -71,17 +82,23 @@ This file is part of OpenWebRX.
<div class="openwebrx-button" onclick="demodulator_analog_replace('usb');">USB</div> <div class="openwebrx-button" onclick="demodulator_analog_replace('usb');">USB</div>
<div class="openwebrx-button" onclick="demodulator_analog_replace('cw');">CW</div> <div class="openwebrx-button" onclick="demodulator_analog_replace('cw');">CW</div>
</div> </div>
<div class="openwebrx-panel" id="webrx-config" data-panel-name="debug" data-panel-pos="left" data-panel-order="0" data-panel-size="585,130"> <div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,142">
<div class="openwebrx-panel-inner"> <div class="openwebrx-panel-inner" id="openwebrx-log-scroll">
<div id="openwebrx-client-log-title">openwebrx.js (beta) client log </strong><span id="openwebrx-problems"></span></div> <div id="openwebrx-client-log-title">OpenWebRX (beta) client log<span style="color: #ff5900;"></span> </strong><span id="openwebrx-problems"></span></div>
Author: <a href="javascript:sendmail2('pi7qtu=alz$pc');">HA7ILM</a>. Please send me bug reports and suggestions.<br/> Author: <a href="javascript:sendmail2('pi7qtu=alz$pc');">HA7ILM</a>. Please send me bug reports and suggestions.<br/>
Client status: <span id="openwebrx-client-status">
<span id="openwebrx-audio-sps"></span><br/>
<!--Server status: <span id="openwebrx-server-status">no information</span><br/>--> <!--Server status: <span id="openwebrx-server-status">no information</span><br/>-->
Your client ID is: <em>%[CLIENT_ID]</em><br /> Your client ID is: <em>%[CLIENT_ID]</em><br />
<div id="openwebrx-debugdiv"></div> <div id="openwebrx-debugdiv"></div>
</div> </div>
</div> </div>
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network speed [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;"> <div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="background-color: Red;">
<span style="font-size: 15pt; font-weight: bold;">Under construction</span> <span style="font-size: 15pt; font-weight: bold;">Under construction</span>
<br />We're working on the code right now, so the application might fail. <br />We're working on the code right now, so the application might fail.

132
htdocs/openwebrx.css Executable file → Normal file
View File

@ -1,20 +1,22 @@
/* /*
OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu>
This file is part of OpenWebRX. This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
OpenWebRX 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 General Public License as published by it under the terms of the GNU Affero General Public License as
the Free Software Foundation, either version 3 of the License, or published by the Free Software Foundation, either version 3 of the
(at your option) any later version. License, or (at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. 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/>.
You should have received a copy of the GNU General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
*/ */
html, body html, body
@ -52,9 +54,14 @@ html, body
{ {
margin:0; margin:0;
padding:0; padding:0;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
} }
#webrx-top-logo #webrx-top-logo
{ {
position: absolute; position: absolute;
@ -65,7 +72,7 @@ html, body
#webrx-ha5kfu-top-logo #webrx-ha5kfu-top-logo
{ {
position: absolute; position: absolute;
top: 19px; top: 15px;
right: 15px; right: 15px;
} }
@ -177,8 +184,10 @@ html, body
#webrx-rx-photo-desc a #webrx-rx-photo-desc a
{ {
/*color: #007df1;*/
color: #5ca8ff; color: #5ca8ff;
text-shadow: none; text-shadow: none;
/*text-shadow: 0px 0px 7px #fff;*/
} }
#webrx-rx-title #webrx-rx-title
@ -269,6 +278,8 @@ html, body
{ {
position: absolute; position: absolute;
border-style: none; border-style: none;
image-rendering: crisp-edges;
image-rendering: -webkit-optimize-contrast;
} }
#openwebrx-phantom-canvas #openwebrx-phantom-canvas
@ -410,6 +421,12 @@ html, body
cursor: pointer; cursor: pointer;
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0 , #373737), color-stop(1, #4F4F4F) ); 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% ); background:-moz-linear-gradient( center top, #373737 0%, #4F4F4F 100% );
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
} }
.openwebrx-button:hover .openwebrx-button:hover
@ -431,3 +448,94 @@ html, body
margin-bottom: 5px; margin-bottom: 5px;
font-weight: bold; font-weight: bold;
} }
.openwebrx-progressbar
{
position: relative;
border-radius: 5px;
background-color: #003850; /*#006235;*/
display: inline-block;
text-align: center;
font-size: 8pt;
font-weight: bold;
text-shadow: 0px 0px 4px #000000;
cursor: default;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.openwebrx-progressbar-bar
{
border-radius: 5px;
height: 100%;
width: 100%;
}
.openwebrx-progressbar-text
{
position: absolute;
left:0px;
top:4px;
width: inherit;
}
#openwebrx-panel-status
{
margin: 0px;
padding: 0px;
background-color:rgba(0, 0, 0, 0);
}
#openwebrx-panel-status div.openwebrx-progressbar
{
width: 200px;
height: 20px;
}
#openwebrx-main-buttons img
{
}
#openwebrx-main-buttons ul
{
display: table;
margin:0;
}
#openwebrx-main-buttons ul li
{
display: table-cell;
padding-left: 5px;
padding-right: 5px;
cursor:pointer;
}
#openwebrx-main-buttons li:hover
{
background-color: rgba(255, 255, 255, 0.3);
}
#openwebrx-main-buttons li:active
{
background-color: rgba(255, 255, 255, 0.55);
}
#openwebrx-main-buttons
{
position: absolute;
right: 133px;
top: 3px;
margin:0;
color: white;
text-shadow: 0px 0px 4px #000000;
text-align: center;
font-size: 9pt;
font-weight: bold;
}

379
htdocs/openwebrx.js Executable file → Normal file
View File

@ -1,21 +1,23 @@
/* /*
OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu> This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This file is part of OpenWebRX. 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.
OpenWebRX is free software: you can redistribute it and/or modify This program is distributed in the hope that it will be useful,
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
*/ */
@ -43,6 +45,9 @@ var audio_buffer_current_count_debug=0;
var audio_buffer_current_size=0; var audio_buffer_current_size=0;
var fft_size; var fft_size;
var fft_fps; var fft_fps;
var fft_compression="none";
var fft_codec=new sdrjs.ImaAdpcm();
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;
@ -270,7 +275,7 @@ demodulator.draggable_ranges={none: 0, beginning:1 /*from*/, ending: 2 /*to*/, a
// This can be used as a base for basic audio demodulators. // This can be used as a base for basic audio demodulators.
// It already supports most basic modulations used for ham radio and commercial services: AM/FM/LSB/USB // It already supports most basic modulations used for ham radio and commercial services: AM/FM/LSB/USB
demodulator_response_time=100; demodulator_response_time=50;
//in ms; if we don't limit the number of SETs sent to the server, audio will underrun (possibly output buffer is cleared on SETs in GNU Radio //in ms; if we don't limit the number of SETs sent to the server, audio will underrun (possibly output buffer is cleared on SETs in GNU Radio
function demodulator_default_analog(offset_frequency,subtype) function demodulator_default_analog(offset_frequency,subtype)
@ -703,27 +708,21 @@ function mkscale()
var text_measured=scale_ctx.measureText(text_to_draw); var text_measured=scale_ctx.measureText(text_to_draw);
scale_ctx.textAlign = "center"; scale_ctx.textAlign = "center";
//advanced text drawing begins //advanced text drawing begins
if(zoom_level==0&&range.start+spacing.smallbw*spacing.ratio>marker_hz) if( zoom_level==0 && (range.start+spacing.smallbw*spacing.ratio>marker_hz) && (x<text_measured.width/2) )
{ //if this is the first overall marker when zoomed out { //if this is the first overall marker when zoomed out... and if it would be clipped off the screen...
if(x<text_measured.width/2) if(scale_px_from_freq(marker_hz+spacing.smallbw*spacing.ratio,range)-text_measured.width>=scale_min_space_bw_texts)
{ //and if it would be clipped off the screen { //and if we have enough space to draw it correctly without clipping
if(scale_px_from_freq(marker_hz+spacing.smallbw*spacing.ratio,range)-text_measured.width>=scale_min_space_bw_texts) scale_ctx.textAlign = "left";
{ //and if we have enough space to draw it correctly without clipping scale_ctx.fillText(text_to_draw, 0, text_h_pos);
scale_ctx.textAlign = "left";
scale_ctx.fillText(text_to_draw, 0, text_h_pos);
}
} }
} }
else if(zoom_level==0&&range.end-spacing.smallbw*spacing.ratio<marker_hz) else if( zoom_level==0 && (range.end-spacing.smallbw*spacing.ratio<marker_hz) && (x>window.innerWidth-text_measured.width/2) )
{ //if this is the last overall marker when zoomed out { // if this is the last overall marker when zoomed out... and if it would be clipped off the screen...
if(x>window.innerWidth-text_measured.width/2) if(window.innerWidth-text_measured.width-scale_px_from_freq(marker_hz-spacing.smallbw*spacing.ratio,range)>=scale_min_space_bw_texts)
{ //and if it would be clipped off the screen { //and if we have enough space to draw it correctly without clipping
if(window.innerWidth-text_measured.width-scale_px_from_freq(marker_hz-spacing.smallbw*spacing.ratio,range)>=scale_min_space_bw_texts) scale_ctx.textAlign = "right";
{ //and if we have enough space to draw it correctly without clipping scale_ctx.fillText(text_to_draw, window.innerWidth, text_h_pos);
scale_ctx.textAlign = "right"; }
scale_ctx.fillText(text_to_draw, window.innerWidth, text_h_pos);
}
}
} }
else scale_ctx.fillText(text_to_draw, x, text_h_pos); //draw text normally else scale_ctx.fillText(text_to_draw, x, text_h_pos); //draw text normally
} }
@ -960,11 +959,16 @@ function resize_waterfall_container(check_init)
canvas_container.style.height=(window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px"; canvas_container.style.height=(window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px";
} }
debug_ws_data_received=0;
max_clients_num=0;
var COMPRESS_FFT_PAD_N=10; //should be the same as in csdr.c
function on_ws_recv(evt) 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;
firstChars=getFirstChars(evt.data,3); firstChars=getFirstChars(evt.data,3);
if(firstChars=="CLI") if(firstChars=="CLI")
{ {
@ -973,7 +977,9 @@ function on_ws_recv(evt)
} }
if(firstChars=="AUD") if(firstChars=="AUD")
{ {
var audio_data=new Int16Array(evt.data,4); var audio_data;
if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4)
else audio_data=new Int16Array(evt.data,4);
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;
@ -982,8 +988,15 @@ function on_ws_recv(evt)
else if(firstChars=="FFT") else if(firstChars=="FFT")
{ {
//alert("Yupee! Doing FFT"); //alert("Yupee! Doing FFT");
var floatArray = new Float32Array(evt.data,4); if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4));
waterfall_add_queue(floatArray); else if(fft_compression="adpcm")
{
fft_codec.reset();
var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4));
var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N);
for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100;
waterfall_add_queue(waterfall_f32);
}
} else if(firstChars=="MSG") } else if(firstChars=="MSG")
{ {
/*try /*try
@ -999,18 +1012,36 @@ function on_ws_recv(evt)
waterfall_init(); waterfall_init();
break; break;
case "bandwidth": case "bandwidth":
bandwidth=parseInt(param[1]) bandwidth=parseInt(param[1]);
break; break;
case "center_freq": case "center_freq":
center_freq=parseInt(param[1]) center_freq=parseInt(param[1]); //there was no ; and it was no problem... why?
break; break;
case "fft_size": case "fft_size":
fft_size=parseInt(param[1]) fft_size=parseInt(param[1]);
break; break;
case "fft_fps": case "fft_fps":
fft_fps=parseInt(param[1]) fft_fps=parseInt(param[1]);
break;
case "audio_compression":
audio_compression=param[1];
divlog( "Audio stream is "+ ((audio_compression=="adpcm")?"compressed":"uncompressed")+"." )
break;
case "fft_compression":
fft_compression=param[1];
divlog( "FFT stream is "+ ((fft_compression=="adpcm")?"compressed":"uncompressed")+"." )
break;
case "cpu_usage":
var server_cpu_usage=parseInt(param[1]);
progressbar_set(e("openwebrx-bar-server-cpu"),server_cpu_usage/100,"Server CPU ["+param[1]+"%]",server_cpu_usage>85);
break;
case "clients":
var clients_num=parseInt(param[1]);
progressbar_set(e("openwebrx-bar-clients"),clients_num/max_clients_num,"Clients ["+param[1]+"]",clients_num>max_clients_num*0.85);
break;
case "max_clients":
max_clients_num=parseInt(param[1]);
break; break;
} }
} }
/*} /*}
@ -1032,17 +1063,33 @@ function add_problem(what)
window.setTimeout(function(ps,ns) { ps.removeChild(ns); }, 1000,problems_span,new_span); window.setTimeout(function(ps,ns) { ps.removeChild(ns); }, 1000,problems_span,new_span);
} }
waterfall_measure_minmax=false;
waterfall_measure_minmax_min=1e100;
waterfall_measure_minmax_max=-1e100;
function waterfall_measure_minmax_do(what)
{
waterfall_measure_minmax_min=Math.min(waterfall_measure_minmax_min,Math.min.apply(Math,what));
waterfall_measure_minmax_max=Math.max(waterfall_measure_minmax_max,Math.max.apply(Math,what));
}
function waterfall_measure_minmax_print()
{
console.log("Waterfall | min = "+waterfall_measure_minmax_min.toString()+" dB | max = "+waterfall_measure_minmax_max.toString()+" dB");
}
function waterfall_add_queue(what) function waterfall_add_queue(what)
{ {
if(waterfall_measure_minmax) waterfall_measure_minmax_do(what);
waterfall_queue.push(what); waterfall_queue.push(what);
} }
function waterfall_dequeue() function waterfall_dequeue()
{ {
if(waterfall_queue.length) waterfall_add(waterfall_queue.shift()); if(waterfall_queue.length) waterfall_add(waterfall_queue.shift());
if(waterfall_queue.length>Math.max(fft_fps/2,8)) //in case of emergency if(waterfall_queue.length>Math.max(fft_fps/2,20)) //in case of emergency
{ {
console.log(waterfall_queue.length); console.log("waterfall queue length:", waterfall_queue.length);
add_problem("fft overflow"); add_problem("fft overflow");
while(waterfall_queue.length) waterfall_add(waterfall_queue.shift()); while(waterfall_queue.length) waterfall_add(waterfall_queue.shift());
} }
@ -1054,10 +1101,20 @@ function on_ws_opened()
divlog("WebSocket opened to "+ws_url); divlog("WebSocket opened to "+ws_url);
} }
var was_error=0;
function divlog(what, is_error) function divlog(what, is_error)
{ {
if(typeof is_error !== undefined && is_error == 1) what="<span class=\"webrx-error\">"+what+"</span>"; is_error=!!is_error;
was_error |= is_error;
if(is_error)
{
what="<span class=\"webrx-error\">"+what+"</span>";
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");
wls.scrollTop=wls.scrollHeight; //scroll to bottom
} }
var audio_context; var audio_context;
@ -1065,54 +1122,105 @@ var audio_initialized=0;
var audio_received = Array(); var audio_received = Array();
var audio_buffer_index = 0; var audio_buffer_index = 0;
var audio_resampler; var audio_resampler=new sdrjs.RationalResamplerFF(4,1);
var audio_codec=new sdrjs.ImaAdpcm();
var audio_compression="unknown";
var audio_node; var audio_node;
//var audio_received_sample_rate = 48000; //var audio_received_sample_rate = 48000;
var audio_input_buffer_size; var audio_input_buffer_size;
// Optimalise these if audio lags or is choppy: // Optimalise these if audio lags or is choppy:
var audio_buffer_size = 8192;//2048 was choppy var audio_buffer_size = 4096;//2048 was choppy
var audio_buffer_maximal_length_sec=1.7; //actual number of samples are calculated from sample rate var audio_buffer_maximal_length_sec=3; //actual number of samples are calculated from sample rate
var audio_flush_interval_ms=250; //the interval in which audio_flush() is called var audio_buffer_decrease_to_on_overrun_sec=2.2;
var audio_flush_interval_ms=500; //the interval in which audio_flush() is called
var audio_prepared_buffers = Array(); var audio_prepared_buffers = Array();
var audio_rebuffer = new sdrjs.Rebuffer(audio_buffer_size,sdrjs.REBUFFER_FIXED);
var audio_last_output_buffer = new Float32Array(audio_buffer_size); var audio_last_output_buffer = new Float32Array(audio_buffer_size);
var audio_last_output_offset = 0; var audio_last_output_offset = 0;
var audio_buffering = false; var audio_buffering = false;
var audio_buffering_fill_to=10; //on audio underrun we wait until this n*audio_buffer_size samples are present //var audio_buffering_fill_to=4; //on audio underrun we wait until this n*audio_buffer_size samples are present
//tnx to the hint from HA3FLT, now we have about half the response time! (original value: 10)
function gain_ff(gain_value,data) //great! solved clicking! will have to move to sdr.js
{
for(var i=0;i<data.length;i++)
data[i]*=gain_value;
return data;
}
function audio_prepare(data) function audio_prepare(data)
{
//audio_rebuffer.push(sdrjs.ConvertI16_F(data));//no resampling
//audio_rebuffer.push(audio_resampler.process(sdrjs.ConvertI16_F(data)));//resampling without ADPCM
if(audio_compression=="none")
audio_rebuffer.push(audio_resampler.process(gain_ff(0.9,sdrjs.ConvertI16_F(data))));//resampling without ADPCM
else if(audio_compression=="adpcm")
audio_rebuffer.push(audio_resampler.process(gain_ff(0.9,sdrjs.ConvertI16_F(audio_codec.decode(data))))); //resampling & ADPCM
else return;
//console.log("prepare",data.length,audio_rebuffer.remaining());
while(audio_rebuffer.remaining())
{
audio_prepared_buffers.push(audio_rebuffer.take());
audio_buffer_current_count_debug++;
}
if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) audio_buffering=false;
}
function audio_prepare_without_resampler(data)
{
audio_rebuffer.push(sdrjs.ConvertI16_F(data));
console.log("prepare",data.length,audio_rebuffer.remaining());
while(audio_rebuffer.remaining())
{
audio_prepared_buffers.push(audio_rebuffer.take());
audio_buffer_current_count_debug++;
}
if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) audio_buffering=false;
}
function audio_prepare_old(data)
{ {
//console.log("audio_prepare :: "+data.length.toString()); //console.log("audio_prepare :: "+data.length.toString());
//console.log("data.len = "+data.length.toString()); //console.log("data.len = "+data.length.toString());
var dopush=function() var dopush=function()
{ {
console.log(audio_last_output_buffer);
audio_prepared_buffers.push(audio_last_output_buffer); audio_prepared_buffers.push(audio_last_output_buffer);
audio_last_output_offset=0; audio_last_output_offset=0;
audio_last_output_buffer=new Float32Array(audio_buffer_size); audio_last_output_buffer=new Float32Array(audio_buffer_size);
audio_buffer_current_count_debug++; audio_buffer_current_count_debug++;
}; };
var original_data_length=data.length;
var f32data=new Float32Array(data.length);
for(var i=0;i<data.length;i++) f32data[i]=data[i]/32768; //convert_i16_f
data=audio_resampler.process(f32data);
console.log(data,data.length,original_data_length);
if(data.length==0) return; if(data.length==0) return;
if(audio_last_output_offset+data.length<=audio_buffer_size) if(audio_last_output_offset+data.length<=audio_buffer_size)
{ //array fits into output buffer { //array fits into output buffer
for(var i=0;i<data.length;i++) audio_last_output_buffer[i+audio_last_output_offset]=data[i]/32768; for(var i=0;i<data.length;i++) audio_last_output_buffer[i+audio_last_output_offset]=data[i];
audio_last_output_offset+=data.length; audio_last_output_offset+=data.length;
//console.log("fits into; offset="+audio_last_output_offset.toString()); console.log("fits into; offset="+audio_last_output_offset.toString());
if(audio_last_output_offset==audio_buffer_size) dopush(); if(audio_last_output_offset==audio_buffer_size) dopush();
} }
else else
{ //array is larger than the remaining space in the output buffer { //array is larger than the remaining space in the output buffer
var copied=audio_buffer_size-audio_last_output_offset; var copied=audio_buffer_size-audio_last_output_offset;
var remain=data.length-copied; var remain=data.length-copied;
for(var i=0;i<audio_buffer_size-audio_last_output_offset;i++) //fill the remaining space in the output buffer for(var i=0;i<audio_buffer_size-audio_last_output_offset;i++) //fill the remaining space in the output buffer
audio_last_output_buffer[i+audio_last_output_offset]=data[i]/32768; audio_last_output_buffer[i+audio_last_output_offset]=data[i];///32768;
dopush();//push the output buffer and create a new one dopush();//push the output buffer and create a new one
//console.log("larger than; copied half: "+copied.toString()+", now at: "+audio_last_output_offset.toString()); console.log("larger than; copied half: "+copied.toString()+", now at: "+audio_last_output_offset.toString());
for(var i=0;i<remain;i++) //copy the remaining input samples to the new output buffer for(var i=0;i<remain;i++) //copy the remaining input samples to the new output buffer
audio_last_output_buffer[i]=data[i+copied]/32768; audio_last_output_buffer[i]=data[i+copied];///32768;
audio_last_output_offset+=remain; audio_last_output_offset+=remain;
//console.log("larger than; remained: "+remain.toString()+", now at: "+audio_last_output_offset.toString()); console.log("larger than; remained: "+remain.toString()+", now at: "+audio_last_output_offset.toString());
} }
if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) audio_buffering=false; if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) audio_buffering=false;
} }
@ -1127,24 +1235,49 @@ if (!AudioBuffer.prototype.copyToChannel)
} }
function audio_onprocess(e) function audio_onprocess(e)
{ {
//console.log("audio onprocess");
if(audio_buffering) return; if(audio_buffering) return;
if(audio_prepared_buffers.length==0) { add_problem("audio underrun"); audio_buffering=true; } if(audio_prepared_buffers.length==0) { audio_buffer_progressbar_update(); /*add_problem("audio underrun");*/ audio_buffering=true; }
else e.outputBuffer.copyToChannel(audio_prepared_buffers.shift(),0); else { e.outputBuffer.copyToChannel(audio_prepared_buffers.shift(),0); }
} }
var audio_buffer_progressbar_update_disabled=false;
var audio_buffer_total_average_level=0;
var audio_buffer_total_average_level_length=0;
function audio_buffer_progressbar_update()
{
if(audio_buffer_progressbar_update_disabled) return;
var audio_buffer_value=(audio_prepared_buffers.length*audio_buffer_size)/44100;
audio_buffer_total_average_level_length++; audio_buffer_total_average_level=(audio_buffer_total_average_level*((audio_buffer_total_average_level_length-1)/audio_buffer_total_average_level_length))+(audio_buffer_value/audio_buffer_total_average_level_length);
var overrun=audio_buffer_value>audio_buffer_maximal_length_sec;
var underrun=audio_prepared_buffers.length==0;
var text="buffer";
if(overrun) text="overrun";
if(underrun) text="underrun";
if(overrun||underrun)
{
audio_buffer_progressbar_update_disabled=true;
window.setTimeout(function(){audio_buffer_progressbar_update_disabled=false; audio_buffer_progressbar_update();},1000);
}
progressbar_set(e("openwebrx-bar-audio-buffer"),(underrun)?1:audio_buffer_value/1.5,"Audio "+text+" ["+(audio_buffer_value).toFixed(1)+" s]",overrun||underrun||audio_buffer_value<0.25);
}
function audio_flush() function audio_flush()
{ {
flushed=false; flushed=false;
while(audio_buffer_maximal_length_sec*audio_context.sampleRate<audio_prepared_buffers.length*audio_buffer_size) we_have_more_than=function(sec){ return sec*audio_context.sampleRate<audio_prepared_buffers.length*audio_buffer_size; }
if(we_have_more_than(audio_buffer_maximal_length_sec)) while(we_have_more_than(audio_buffer_decrease_to_on_overrun_sec))
{ {
if(!flushed) audio_buffer_progressbar_update();
flushed=true; flushed=true;
audio_prepared_buffers.shift(); audio_prepared_buffers.shift();
} }
if(flushed) add_problem("audio overrun"); //if(flushed) add_problem("audio overrun");
} }
@ -1238,7 +1371,8 @@ function audio_init()
//https://github.com/grantgalitz/XAudioJS/blob/master/XAudioServer.js //https://github.com/grantgalitz/XAudioJS/blob/master/XAudioServer.js
//audio_resampler = new Resampler(audio_received_sample_rate, audio_context.sampleRate, 1, audio_buffer_size, true); //audio_resampler = new Resampler(audio_received_sample_rate, audio_context.sampleRate, 1, audio_buffer_size, true);
//audio_input_buffer_size = audio_buffer_size*(audio_received_sample_rate/audio_context.sampleRate); //audio_input_buffer_size = audio_buffer_size*(audio_received_sample_rate/audio_context.sampleRate);
webrx_set_param("audio_rate",audio_context.sampleRate); //Don't try to resample webrx_set_param("audio_rate",audio_context.sampleRate); //Don't try to resample //TODO remove this
window.setInterval(audio_flush,audio_flush_interval_ms); window.setInterval(audio_flush,audio_flush_interval_ms);
divlog('Web Audio API succesfully initialized, sample rate: '+audio_context.sampleRate.toString()+ " sps"); divlog('Web Audio API succesfully initialized, sample rate: '+audio_context.sampleRate.toString()+ " sps");
/*audio_source=audio_context.createBufferSource(); /*audio_source=audio_context.createBufferSource();
@ -1246,6 +1380,14 @@ function audio_init()
audio_source.buffer = buffer; audio_source.buffer = buffer;
audio_source.noteOn(0);*/ audio_source.noteOn(0);*/
demodulator_analog_replace('nfm'); //needs audio_context.sampleRate to exist demodulator_analog_replace('nfm'); //needs audio_context.sampleRate to exist
//hide log panel in a second (if user has not hidden it yet)
window.setTimeout(function(){
if(typeof e("openwebrx-panel-log").openwebrxHidden == "undefined" && !was_error)
{
animate(e("openwebrx-panel-log"),"opacity","",1,0,0.9,1000,60);
window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200)
}
},1000);
} }
function on_ws_closed() function on_ws_closed()
@ -1267,11 +1409,11 @@ String.prototype.startswith=function(str){ return this.indexOf(str) == 0; }; //h
function open_websocket() function open_websocket()
{ {
if(ws_url.startswith("ws://localhost:")&&window.location.hostname!="127.0.0.1"&&window.location.hostname!="localhost") //if(ws_url.startswith("ws://localhost:")&&window.location.hostname!="127.0.0.1"&&window.location.hostname!="localhost")
{ //{
divlog("Server administrator should set <em>server_hostname</em> correctly, because it is left as <em>\"localhost\"</em>. Now guessing hostname from page URL.",1); //divlog("Server administrator should set <em>server_hostname</em> correctly, because it is left as <em>\"localhost\"</em>. Now guessing hostname from page URL.",1);
ws_url="ws://"+(window.location.origin.split("://")[1])+"/ws/"; //guess automatically ws_url="ws://"+(window.location.origin.split("://")[1])+"/ws/"; //guess automatically -> now default behaviour
} //}
if (!("WebSocket" in window)) if (!("WebSocket" in window))
divlog("Your browser does not support WebSocket, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser."); divlog("Your browser does not support WebSocket, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser.");
ws = new WebSocket(ws_url+client_id); ws = new WebSocket(ws_url+client_id);
@ -1293,14 +1435,16 @@ function open_websocket()
//var color_scale=[ 0x000000FF, 0xff5656ff, 0xffffffff]; //var color_scale=[ 0x000000FF, 0xff5656ff, 0xffffffff];
//2014-04-22 //2014-04-22
var color_scale=[0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]; var color_scale=[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]
//2015-04-10
//var color_scale=[0x112634ff,0x4991c6ff,0x18364cff,0x9dc4e0ff,0xfff775ff,0xff9f60,0xff4d4dff,0x8d0000ff];
function waterfall_mkcolor(db_value) function waterfall_mkcolor(db_value)
{ {
min_value=-100; //in dB min_value=-115; //in dB
max_value=10 max_value=0;
if(db_value<min_value) db_value=min_value if(db_value<min_value) db_value=min_value;
if(db_value>max_value) db_value=max_value if(db_value>max_value) db_value=max_value;
full_scale=max_value-min_value; full_scale=max_value-min_value;
relative_value=db_value-min_value; relative_value=db_value-min_value;
value_percent=relative_value/full_scale; value_percent=relative_value/full_scale;
@ -1503,11 +1647,12 @@ 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"); //divlog("Drawn FFT");
} }
@ -1525,10 +1670,17 @@ function waterfall_shift()
function check_top_bar_congestion() function check_top_bar_congestion()
{ {
var wt=e("webrx-rx-title"); var rmf=function(x){ return x.offsetLeft+x.offsetWidth; };
var tl=e("webrx-ha5kfu-top-logo"); var wet=e("webrx-rx-title");
if(wt.offsetLeft+wt.offsetWidth>tl.offsetLeft-20) tl.style.display="none"; var wed=e("webrx-rx-desc");
else tl.style.display="block"; var rightmost=Math.max(rmf(wet),rmf(wed));
var tl=e("openwebrx-main-buttons");
[wet, wed].map(function(what) {
if(rmf(what)>tl.offsetLeft-20) what.style.opacity=what.style.opacity="0";
else wet.style.opacity=wed.style.opacity="1";
});
} }
function openwebrx_resize() function openwebrx_resize()
@ -1546,6 +1698,7 @@ function openwebrx_init()
place_panels(); place_panels();
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);
check_top_bar_congestion();
} }
/* /*
@ -1577,18 +1730,33 @@ function debug_audio()
audio_debug_time_since_last_call=(time_now-audio_debug_time_last_start)/1000; audio_debug_time_since_last_call=(time_now-audio_debug_time_last_start)/1000;
audio_debug_time_last_start=time_now; //now audio_debug_time_last_start=time_now; //now
audio_debug_time_taken=(time_now-audio_debug_time_start)/1000; audio_debug_time_taken=(time_now-audio_debug_time_start)/1000;
e("openwebrx-audio-sps").innerHTML= kbps_mult=(audio_compression=="adpcm")?8:16;
"audio recv. at "+(audio_buffer_current_size_debug/audio_debug_time_since_last_call).toFixed(0)+" sps ("+ //e("openwebrx-audio-sps").innerHTML=
(audio_buffer_all_size_debug/audio_debug_time_taken).toFixed(1)+" sps avg.), feed at "+ // ((audio_compression=="adpcm")?"ADPCM compressed":"uncompressed")+" audio downlink:<br/> "+(audio_buffer_current_size_debug*kbps_mult/audio_debug_time_since_last_call).toFixed(0)+" kbps ("+
((audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken).toFixed(1)+" sps output"; // (audio_buffer_all_size_debug*kbps_mult/audio_debug_time_taken).toFixed(1)+" kbps avg.), feed at "+
// ((audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken).toFixed(1)+" sps output";
var audio_speed_value=audio_buffer_current_size_debug*kbps_mult/audio_debug_time_since_last_call;
progressbar_set(e("openwebrx-bar-audio-speed"),audio_speed_value/500000,"Audio stream ["+(audio_speed_value/1000).toFixed(0)+" kbps]",false);
var audio_output_value=(audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken;
progressbar_set(e("openwebrx-bar-audio-output"),audio_output_value/55000,"Audio output ["+(audio_output_value/1000).toFixed(1)+" ksps]",audio_output_value>55000||audio_output_value<10000);
audio_buffer_progressbar_update();
var network_speed_value=debug_ws_data_received/audio_debug_time_taken;
progressbar_set(e("openwebrx-bar-network-speed"),network_speed_value*8/2000,"Network usage ["+(network_speed_value*8).toFixed(1)+" kbps]",false);
audio_buffer_current_size_debug=0; audio_buffer_current_size_debug=0;
if(waterfall_measure_minmax) waterfall_measure_minmax_print();
} }
// ======================================================== // ========================================================
// ======================= PANELS ======================= // ======================= PANELS =======================
// ======================================================== // ========================================================
panel_margin=10; panel_margin=5.9;
function pop_bottommost_panel(from) function pop_bottommost_panel(from)
{ {
@ -1608,8 +1776,19 @@ function pop_bottommost_panel(from)
return to_return; return to_return;
} }
function toggle_panel(what)
{
var item=e(what);
if(item.openwebrxDisableClick) return;
item.openwebrxHidden=!item.openwebrxHidden;
place_panels();
item.openwebrxDisableClick=true;
window.setTimeout(function(){item.openwebrxDisableClick=false;},100);
}
function place_panels() function place_panels()
{ {
var hoffset=0; //added this because the first panel should not have such great gap below
var left_col=[]; var left_col=[];
var right_col=[]; var right_col=[];
var plist=e("openwebrx-panels-container").children; var plist=e("openwebrx-panels-container").children;
@ -1618,33 +1797,61 @@ function place_panels()
c=plist[i]; c=plist[i];
if(c.className=="openwebrx-panel") if(c.className=="openwebrx-panel")
{ {
if(c.openwebrxHidden)
{
c.style.display="none";
continue;
}
c.style.display="block";
c.openwebrxPanelTransparent=(!!c.dataset.panelTransparent);
newSize=c.dataset.panelSize.split(","); newSize=c.dataset.panelSize.split(",");
if (c.dataset.panelPos=="left") { left_col.push(c); } if (c.dataset.panelPos=="left") { left_col.push(c); }
else if(c.dataset.panelPos=="right") { right_col.push(c); } else if(c.dataset.panelPos=="right") { right_col.push(c); }
c.style.width=newSize[0]+"px"; c.style.width=newSize[0]+"px";
c.style.height=newSize[1]+"px"; c.style.height=newSize[1]+"px";
c.style.margin=panel_margin.toString()+"px"; if(!c.openwebrxPanelTransparent) c.style.margin=panel_margin.toString()+"px";
else c.style.marginLeft=panel_margin.toString()+"px";
c.openwebrxPanelWidth=parseInt(newSize[0]); c.openwebrxPanelWidth=parseInt(newSize[0]);
c.openwebrxPanelHeight=parseInt(newSize[1]); c.openwebrxPanelHeight=parseInt(newSize[1]);
} }
} }
y=0; y=hoffset; //was y=0 before hoffset
while(left_col.length>0) while(left_col.length>0)
{ {
p=pop_bottommost_panel(left_col); p=pop_bottommost_panel(left_col);
p.style.left="0px"; p.style.left="0px";
p.style.bottom=y.toString()+"px"; p.style.bottom=y.toString()+"px";
p.style.visibility="visible"; p.style.visibility="visible";
y+=p.openwebrxPanelHeight+3*panel_margin; y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
} }
y=0; y=hoffset;
while(right_col.length>0) while(right_col.length>0)
{ {
p=pop_bottommost_panel(right_col); p=pop_bottommost_panel(right_col);
p.style.right="10px"; 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+3*panel_margin; y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
} }
} }
function progressbar_set(obj,val,text,over)
{
if (val<0.05) val=0;
if (val>1) val=1;
var innerBar=null;
var innerText=null;
for(var i=0;i<obj.children.length;i++)
{
if(obj.children[i].className=="openwebrx-progressbar-text") innerText=obj.children[i];
else if(obj.children[i].className=="openwebrx-progressbar-bar") innerBar=obj.children[i];
}
if(innerBar==null) return;
//.h: function animate(object,style_name,unit,from,to,accel,time_ms,fps,to_exec)
animate(innerBar,"width","px",innerBar.clientWidth,val*obj.clientWidth,0.7,700,60);
//innerBar.style.width=(val*100).toFixed(0)+"%";
innerBar.style.backgroundColor=(over)?"#ff6262":"#00aba6";
if(innerText==null) return;
innerText.innerHTML=text;
}

94
htdocs/retry.html Normal file
View File

@ -0,0 +1,94 @@
<html>
<!--
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This 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/>.
-->
<head><title>OpenWebRX</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
html, body
{
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
width: 100%;
text-align: center;
margin: 0;
padding: 0;
}
img.logo
{
margin-top: 120px;
}
div.frame
{
text-align: left;
margin:0px auto;
width: 800px;
}
div.panel
{
text-align: center;
background-color:#777777;
border-radius: 15px;
padding: 12px;
font-weight: bold;
color: White;
font-size: 13pt;
/*text-shadow: 1px 1px 4px #444;*/
font-family: sans;
}
div.alt
{
font-size: 10pt;
padding-top: 10px;
}
body div a
{
color: #5ca8ff;
text-shadow: none;
}
span.browser
{
}
</style>
<script>
var irt = function (s,n) {return s.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c>="a"?97:65)<=(c=c.charCodeAt(0)-n)?c:c+26);});}
var sendmail2 = function (s) { window.location.href="mailto:"+irt(s.replace("=",String.fromCharCode(0100)).replace("$","."),8); }
window.addEventListener("load",function(){rs=document.getElementById("reconnect-secs"); rt=document.getElementById("reconnect-text"); cnt=29;window.setInterval(function(){if(cnt<=-1) window.location.href=window.location.href.split("retry.")[0]; else if(cnt==0) {rt.innerHTML="Reconnecting..."; cnt--;} else rs.innerHTML=(cnt--).toString();},1000);},false);
</script>
</head>
<body>
<div class="frame">
<img class="logo" src="gfx/openwebrx-logo-big.png" style="height: 60px;"/>
<div class="panel">
There are no client slots left on this server.
<div class="alt">
Please wait until a client disconnects.<br /><span id="reconnect-text">We will try to reconnect in <span id="reconnect-secs">30</span> seconds...</span>
</div>
</div>
</div>
</body>
</html>

11683
htdocs/sdr.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,21 +1,23 @@
<html> <html>
<!-- <!--
OpenWebRX (c) Copyright 2013 Andras Retzler <ha7ilm@sdr.hu>
This file is part of OpenWebRX. This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
OpenWebRX 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 General Public License as published by it under the terms of the GNU Affero General Public License as
the Free Software Foundation, either version 3 of the License, or published by the Free Software Foundation, either version 3 of the
(at your option) any later version. License, or (at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. 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/>.
You should have received a copy of the GNU General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<head><title>OpenWebRX</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <head><title>OpenWebRX</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style> <style>

View File

@ -1,31 +1,26 @@
#!/usr/bin/python2 #!/usr/bin/python2
print "" # python2.7 is required to run OpenWebRX instead of python3. Please run me by: python2 openwebrx.py print "" # python2.7 is required to run OpenWebRX instead of python3. Please run me by: python2 openwebrx.py
""" """
OpenWebRX: open-source web based SDR for everyone!
This file is part of OpenWebRX. This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
OpenWebRX 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 General Public License as published by it under the terms of the GNU Affero General Public License as
the Free Software Foundation, either version 3 of the License, or published by the Free Software Foundation, either version 3 of the
(at your option) any later version. License, or (at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
Authors:
Andras Retzler, HA7ILM <randras@sdr.hu>
""" """
sw_version="v0.12+"
# http://www.codeproject.com/Articles/462525/Simple-HTTP-Server-and-Client-in-Python
# some ideas are used from the artice above
import os import os
import code import code
@ -54,6 +49,11 @@ import rxws
import uuid import uuid
import config_webrx as cfg import config_webrx as cfg
import signal import signal
import socket
try: import sdrhu
except: sdrhu=False
avatar_ctime=""
#pypy compatibility #pypy compatibility
try: import dl try: import dl
@ -62,7 +62,6 @@ try: import __pypy__
except: pass except: pass
pypy="__pypy__" in globals() pypy="__pypy__" in globals()
def import_all_plugins(directory): def import_all_plugins(directory):
for subdir in os.listdir(directory): for subdir in os.listdir(directory):
if os.path.isdir(directory+subdir) and not subdir[0]=="_": if os.path.isdir(directory+subdir) and not subdir[0]=="_":
@ -80,9 +79,9 @@ def handle_signal(signal, frame):
os._exit(1) #not too graceful exit os._exit(1) #not too graceful exit
def main(): def main():
global clients, clients_mutex, pypy global clients, clients_mutex, pypy, lock_try_time, avatar_ctime
print print
print "OpenWebRX - Open Source Web Based SDR for Everyone | for license see LICENSE file in the package" print "OpenWebRX - Open Source SDR Web App for Everyone! | for license see LICENSE file in the package"
print "_________________________________________________________________________________________________" print "_________________________________________________________________________________________________"
print print
print "Author contact info: Andras Retzler, HA7ILM <randras@sdr.hu>" print "Author contact info: Andras Retzler, HA7ILM <randras@sdr.hu>"
@ -123,13 +122,33 @@ def main():
#Initialize clients #Initialize clients
clients=[] clients=[]
clients_mutex=threading.Lock() clients_mutex=threading.Lock()
lock_try_time=0
#Start watchdog thread
print "[openwebrx-main] Starting watchdog threads."
mutex_test_thread=threading.Thread(target = mutex_test_thread_function, args = ())
mutex_test_thread.start()
mutex_watchdog_thread=threading.Thread(target = mutex_watchdog_thread_function, args = ())
mutex_watchdog_thread.start()
#Start spectrum thread #Start spectrum thread
print "[openwebrx-main] Starting spectrum thread." print "[openwebrx-main] Starting spectrum thread."
spectrum_thread=threading.Thread(target = spectrum_thread_function, args = ()) spectrum_thread=threading.Thread(target = spectrum_thread_function, args = ())
spectrum_thread.start() spectrum_thread.start()
get_cpu_usage()
bcastmsg_thread=threading.Thread(target = bcastmsg_thread_function, args = ())
bcastmsg_thread.start()
#threading.Thread(target = measure_thread_function, args = ()).start() #threading.Thread(target = measure_thread_function, args = ()).start()
#Start sdr.hu update thread
if sdrhu and cfg.sdrhu_key and cfg.sdrhu_public_listing:
print "[openwebrx-main] Starting sdr.hu update thread..."
sdrhu_thread=threading.Thread(target = sdrhu.run, args = ())
sdrhu_thread.start()
avatar_ctime=str(os.path.getctime("htdocs/gfx/openwebrx-avatar.png"))
#Start HTTP thread #Start HTTP thread
httpd = MultiThreadHTTPServer(('', cfg.web_port), WebRXHandler) httpd = MultiThreadHTTPServer(('', cfg.web_port), WebRXHandler)
@ -146,23 +165,67 @@ def measure_thread_function():
measure_value=0 measure_value=0
time.sleep(1) time.sleep(1)
def bcastmsg_thread_function():
global clients
while True:
time.sleep(3)
try: cpu_usage=get_cpu_usage()
except: cpu_usage=0
cma("bcastmsg_thread")
for i in range(0,len(clients)):
clients[i].bcastmsg="MSG cpu_usage={0} clients={1}".format(int(cpu_usage*100),len(clients))
cmr()
def spectrum_thread_function(): def mutex_test_thread_function():
global clients_mutex, lock_try_time
while True:
time.sleep(0.5)
lock_try_time=time.time()
clients_mutex.acquire()
clients_mutex.release()
lock_try_time=0
def cma(what): #clients_mutex acquire
global clients_mutex global clients_mutex
global clients_mutex_locker
if not clients_mutex.locked(): clients_mutex_locker = what
clients_mutex.acquire()
def cmr():
global clients_mutex
global clients_mutex_locker
clients_mutex_locker = None
clients_mutex.release()
def mutex_watchdog_thread_function():
global lock_try_time
global clients_mutex_locker
global clients_mutex
while True:
if lock_try_time != 0 and time.time()-lock_try_time > 3.0:
#if 3 seconds pass without unlock
print "[openwebrx-watchdog] Mutex unlock timeout. Locker: \""+str(clients_mutex_locker)+"\" Now unlocking..."
clients_mutex.release()
time.sleep(0.5)
def spectrum_thread_function():
global clients global clients
dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin()
dsp.set_demodulator("fft") dsp.set_demodulator("fft")
dsp.set_samp_rate(cfg.samp_rate) dsp.set_samp_rate(cfg.samp_rate)
dsp.set_fft_size(cfg.fft_size) dsp.set_fft_size(cfg.fft_size)
dsp.set_fft_fps(cfg.fft_fps) dsp.set_fft_fps(cfg.fft_fps)
dsp.set_fft_compression(cfg.fft_compression)
dsp.set_format_conversion(cfg.format_conversion)
sleep_sec=0.87/cfg.fft_fps sleep_sec=0.87/cfg.fft_fps
print "[openwebrx-spectrum] Spectrum thread initialized successfully." print "[openwebrx-spectrum] Spectrum thread initialized successfully."
dsp.start() dsp.start()
print "[openwebrx-spectrum] Spectrum thread started." print "[openwebrx-spectrum] Spectrum thread started."
bytes_to_read=int(dsp.get_fft_bytes_to_read())
while True: while True:
data=dsp.read(cfg.fft_size*4) data=dsp.read(bytes_to_read)
#print "gotcha",len(data),"bytes of spectrum data via spectrum_thread_function()" #print "gotcha",len(data),"bytes of spectrum data via spectrum_thread_function()"
clients_mutex.acquire() cma("spectrum_thread")
correction=0 correction=0
for i in range(0,len(clients)): for i in range(0,len(clients)):
i-=correction i-=correction
@ -173,18 +236,17 @@ def spectrum_thread_function():
correction+=1 correction+=1
else: else:
clients[i].spectrum_queue.put([data]) # add new string by "reference" to all clients clients[i].spectrum_queue.put([data]) # add new string by "reference" to all clients
clients_mutex.release() cmr()
def get_client_by_id(client_id, use_mutex=True): def get_client_by_id(client_id, use_mutex=True):
global clients_mutex
global clients global clients
output=-1 output=-1
if use_mutex: clients_mutex.acquire() if use_mutex: cma("get_client_by_id")
for i in range(0,len(clients)): for i in range(0,len(clients)):
if(clients[i].id==client_id): if(clients[i].id==client_id):
output=i output=i
break break
if use_mutex: clients_mutex.release() if use_mutex: cmr()
if output==-1: if output==-1:
raise ClientNotFoundException raise ClientNotFoundException
else: else:
@ -195,53 +257,69 @@ def log_client(client, what):
def cleanup_clients(): def cleanup_clients():
# if client doesn't open websocket for too long time, we drop it # if client doesn't open websocket for too long time, we drop it
global clients_mutex
global clients global clients
clients_mutex.acquire() cma("cleanup_clients")
correction=0 correction=0
for i in range(0,len(clients)): for i in range(0,len(clients)):
i-=correction i-=correction
#print "cleanup_clients:: len(clients)=", len(clients), "i=", i #print "cleanup_clients:: len(clients)=", len(clients), "i=", i
if (not clients[i].ws_started) and (time.time()-clients[i].gen_time)>180: if (not clients[i].ws_started) and (time.time()-clients[i].gen_time)>45:
print "[openwebrx] cleanup_clients :: client timeout to open WebSocket" print "[openwebrx] cleanup_clients :: client timeout to open WebSocket"
close_client(i, False) close_client(i, False)
correction+=1 correction+=1
clients_mutex.release() cmr()
def generate_client_id(ip): def generate_client_id(ip):
#add a client #add a client
global clients global clients
global clients_mutex new_client=namedtuple("ClientStruct", "id gen_time ws_started sprectum_queue ip closed bcastmsg dsp")
new_client=namedtuple("ClientStruct", "id gen_time ws_started sprectum_queue ip closed")
new_client.id=md5.md5(str(random.random())).hexdigest() new_client.id=md5.md5(str(random.random())).hexdigest()
new_client.gen_time=time.time() new_client.gen_time=time.time()
new_client.ws_started=False # to check whether client has ever tried to open the websocket new_client.ws_started=False # to check whether client has ever tried to open the websocket
new_client.spectrum_queue=Queue.Queue(1000) new_client.spectrum_queue=Queue.Queue(1000)
new_client.ip=ip new_client.ip=ip
new_client.bcastmsg=""
new_client.closed=[False] #byref, not exactly sure if required new_client.closed=[False] #byref, not exactly sure if required
clients_mutex.acquire() new_client.dsp=None
cma("generate_client_id")
clients.append(new_client) clients.append(new_client)
log_client(new_client,"client added. Clients now: {0}".format(len(clients))) log_client(new_client,"client added. Clients now: {0}".format(len(clients)))
clients_mutex.release() cmr()
cleanup_clients() cleanup_clients()
return new_client.id return new_client.id
def close_client(i, use_mutex=True): def close_client(i, use_mutex=True):
global clients_mutex
global clients global clients
log_client(clients[i],"client being closed.") log_client(clients[i],"client being closed.")
if use_mutex: clients_mutex.acquire() if use_mutex: cma("close_client")
try:
clients[i].dsp.stop()
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
print "[openwebrx] close_client dsp.stop() :: error -",exc_type,exc_value
traceback.print_tb(exc_traceback)
clients[i].closed[0]=True clients[i].closed[0]=True
del clients[i] del clients[i]
if use_mutex: clients_mutex.release() if use_mutex: cmr()
# http://www.codeproject.com/Articles/462525/Simple-HTTP-Server-and-Client-in-Python
# some ideas are used from the artice above
class WebRXHandler(BaseHTTPRequestHandler): class WebRXHandler(BaseHTTPRequestHandler):
def proc_read_thread(): def proc_read_thread():
pass pass
def send_302(self,what):
self.send_response(302)
self.send_header('Content-type','text/html')
self.send_header("Location", "http://{0}:{1}/{2}".format(cfg.server_hostname,cfg.web_port,what))
self.end_headers()
self.wfile.write("<html><body><h1>Object moved</h1>Please <a href=\"/{0}\">click here</a> to continue.</body></html>".format(what))
def do_GET(self): def do_GET(self):
global dsp_plugin self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
global clients_mutex global dsp_plugin, clients_mutex, clients, avatar_ctime, sw_version
rootdir = 'htdocs' rootdir = 'htdocs'
self.path=self.path.replace("..","") self.path=self.path.replace("..","")
path_temp_parts=self.path.split("?") path_temp_parts=self.path.split("?")
@ -251,19 +329,20 @@ class WebRXHandler(BaseHTTPRequestHandler):
if self.path=="/": if self.path=="/":
self.path="/index.wrx" self.path="/index.wrx"
# there's even another cool tip at http://stackoverflow.com/questions/4419650/how-to-implement-timeout-in-basehttpserver-basehttprequesthandler-python # there's even another cool tip at http://stackoverflow.com/questions/4419650/how-to-implement-timeout-in-basehttpserver-basehttprequesthandler-python
#if self.path[:5]=="/lock": cma("do_GET /lock/") # to test mutex_watchdog_thread. Do not uncomment in production environment!
if self.path[:4]=="/ws/": if self.path[:4]=="/ws/":
try: try:
# ========= WebSocket handshake ========= # ========= WebSocket handshake =========
ws_success=True ws_success=True
try: try:
rxws.handshake(self) rxws.handshake(self)
clients_mutex.acquire() cma("do_GET /ws/")
client_i=get_client_by_id(self.path[4:], False) client_i=get_client_by_id(self.path[4:], False)
myclient=clients[client_i] myclient=clients[client_i]
except rxws.WebSocketException: ws_success=False except rxws.WebSocketException: ws_success=False
except ClientNotFoundException: ws_success=False except ClientNotFoundException: ws_success=False
finally: finally:
if clients_mutex.locked(): clients_mutex.release() if clients_mutex.locked(): cmr()
if not ws_success: if not ws_success:
self.send_error(400, 'Bad request.') self.send_error(400, 'Bad request.')
return return
@ -280,7 +359,7 @@ class WebRXHandler(BaseHTTPRequestHandler):
return return
myclient.ws_started=True myclient.ws_started=True
#send default parameters #send default parameters
rxws.send(self, "MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} setup".format(str(cfg.center_freq),str(cfg.samp_rate),cfg.fft_size,cfg.fft_fps)) rxws.send(self, "MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} audio_compression={4} fft_compression={5} max_clients={6} setup".format(str(cfg.shown_center_freq),str(cfg.samp_rate),cfg.fft_size,cfg.fft_fps,cfg.audio_compression,cfg.fft_compression,cfg.max_clients))
# ========= Initialize DSP ========= # ========= Initialize DSP =========
dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin()
@ -288,7 +367,10 @@ class WebRXHandler(BaseHTTPRequestHandler):
dsp.set_demodulator("nfm") dsp.set_demodulator("nfm")
dsp.set_offset_freq(0) dsp.set_offset_freq(0)
dsp.set_bpf(-4000,4000) dsp.set_bpf(-4000,4000)
dsp.set_audio_compression(cfg.audio_compression)
dsp.set_format_conversion(cfg.format_conversion)
dsp.start() dsp.start()
myclient.dsp=dsp
while True: while True:
if myclient.closed[0]: if myclient.closed[0]:
@ -296,15 +378,21 @@ class WebRXHandler(BaseHTTPRequestHandler):
break break
# ========= send audio ========= # ========= send audio =========
temp_audio_data=dsp.read(1024*8) temp_audio_data=dsp.read(256)
rxws.send(self, temp_audio_data, "AUD ") rxws.send(self, temp_audio_data, "AUD ")
# ========= send spectrum ========= # ========= send spectrum =========
while not myclient.spectrum_queue.empty(): while not myclient.spectrum_queue.empty():
spectrum_data=myclient.spectrum_queue.get() spectrum_data=myclient.spectrum_queue.get()
spectrum_data_mid=len(spectrum_data[0])/2 #spectrum_data_mid=len(spectrum_data[0])/2
rxws.send(self, spectrum_data[0][spectrum_data_mid:]+spectrum_data[0][:spectrum_data_mid], "FFT ") #rxws.send(self, spectrum_data[0][spectrum_data_mid:]+spectrum_data[0][:spectrum_data_mid], "FFT ")
# (it seems GNU Radio exchanges the first and second part of the FFT output, we correct it) # (it seems GNU Radio exchanges the first and second part of the FFT output, we correct it)
rxws.send(self, spectrum_data[0],"FFT ")
# ========= send bcastmsg =========
if myclient.bcastmsg!="":
rxws.send(self,myclient.bcastmsg)
myclient.bcastmsg=""
# ========= process commands ========= # ========= process commands =========
while True: while True:
@ -328,9 +416,10 @@ class WebRXHandler(BaseHTTPRequestHandler):
elif param_name == "offset_freq" and -cfg.samp_rate/2 <= float(param_value) <= cfg.samp_rate/2: elif param_name == "offset_freq" and -cfg.samp_rate/2 <= float(param_value) <= cfg.samp_rate/2:
dsp.set_offset_freq(int(param_value)) dsp.set_offset_freq(int(param_value))
elif param_name=="mod": elif param_name=="mod":
dsp.stop() if (dsp.get_demodulator()!=param_value):
dsp.set_demodulator(param_value) dsp.stop()
dsp.start() dsp.set_demodulator(param_value)
dsp.start()
else: else:
print "[openwebrx-httpd:ws] invalid parameter" print "[openwebrx-httpd:ws] invalid parameter"
if bpf_set: if bpf_set:
@ -355,7 +444,7 @@ class WebRXHandler(BaseHTTPRequestHandler):
#delete disconnected client #delete disconnected client
try: try:
clients_mutex.acquire() cma("do_GET /ws/ delete disconnected")
id_to_close=get_client_by_id(myclient.id,False) id_to_close=get_client_by_id(myclient.id,False)
close_client(id_to_close,False) close_client(id_to_close,False)
except: except:
@ -363,19 +452,23 @@ class WebRXHandler(BaseHTTPRequestHandler):
print "[openwebrx-httpd] client cannot be closed: ",exc_type,exc_value print "[openwebrx-httpd] client cannot be closed: ",exc_type,exc_value
traceback.print_tb(exc_traceback) traceback.print_tb(exc_traceback)
finally: finally:
clients_mutex.release() cmr()
return return
elif self.path in ("/status", "/status/"):
#self.send_header('Content-type','text/plain')
getbands=lambda: str(int(cfg.shown_center_freq-cfg.samp_rate/2))+"-"+str(int(cfg.shown_center_freq+cfg.samp_rate/2))
self.wfile.write("status=active\nname="+cfg.receiver_name+"\nsdr_hw="+cfg.receiver_device+"\nop_email="+cfg.receiver_admin+"\nbands="+getbands()+"\nusers="+str(len(clients))+"\navatar_ctime="+avatar_ctime+"\ngps="+str(cfg.receiver_gps)+"\nasl="+str(cfg.receiver_asl)+"\nloc="+cfg.receiver_location+"\nsw_version="+sw_version+"\nantenna="+cfg.receiver_ant+"\n")
print "[openwebrx-httpd] GET /status/ from",self.client_address[0]
else: else:
f=open(rootdir+self.path) f=open(rootdir+self.path)
data=f.read() data=f.read()
extension=self.path[(len(self.path)-4):len(self.path)] extension=self.path[(len(self.path)-4):len(self.path)]
extension=extension[2:] if extension[1]=='.' else extension[1:] extension=extension[2:] if extension[1]=='.' else extension[1:]
if extension == "wrx" and ((self.headers['user-agent'].count("Chrome")==0 and self.headers['user-agent'].count("Firefox")==0) if 'user-agent' in self.headers.keys() else True) and (not request_param.count("unsupported")): if extension == "wrx" and ((self.headers['user-agent'].count("Chrome")==0 and self.headers['user-agent'].count("Firefox")==0 and (not "Googlebot" in self.headers['user-agent'])) if 'user-agent' in self.headers.keys() else True) and (not request_param.count("unsupported")):
self.send_response(302) self.send_302("upgrade.html")
self.send_header('Content-type','text/html') return
self.send_header("Location", "http://{0}:{1}/upgrade.html".format(cfg.server_hostname,cfg.web_port)) if extension == "wrx" and cfg.max_clients<=len(clients):
self.end_headers() self.send_302("retry.html")
self.wfile.write("<html><body><h1>Object moved</h1>Please <a href=\"/upgrade.html\">click here</a> to continue.</body></html>")
return return
self.send_response(200) self.send_response(200)
if(("wrx","html","htm").count(extension)): if(("wrx","html","htm").count(extension)):
@ -384,11 +477,11 @@ class WebRXHandler(BaseHTTPRequestHandler):
self.send_header('Content-type','text/javascript') self.send_header('Content-type','text/javascript')
elif(extension=="css"): elif(extension=="css"):
self.send_header('Content-type','text/css') self.send_header('Content-type','text/css')
self.end_headers() self.end_headers()
if extension == "wrx": if extension == "wrx":
replace_dictionary=( replace_dictionary=(
("%[RX_PHOTO_DESC]",cfg.photo_desc), ("%[RX_PHOTO_DESC]",cfg.photo_desc),
("%[CLIENT_ID]",generate_client_id(self.client_address[0])), ("%[CLIENT_ID]", generate_client_id(self.client_address[0])) if "%[CLIENT_ID]" in data else "",
("%[WS_URL]","ws://"+cfg.server_hostname+":"+str(cfg.web_port)+"/ws/"), ("%[WS_URL]","ws://"+cfg.server_hostname+":"+str(cfg.web_port)+"/ws/"),
("%[RX_TITLE]",cfg.receiver_name), ("%[RX_TITLE]",cfg.receiver_name),
("%[RX_LOC]",cfg.receiver_location), ("%[RX_LOC]",cfg.receiver_location),
@ -398,7 +491,8 @@ class WebRXHandler(BaseHTTPRequestHandler):
("%[RX_PHOTO_HEIGHT]",str(cfg.photo_height)),("%[RX_PHOTO_TITLE]",cfg.photo_title), ("%[RX_PHOTO_HEIGHT]",str(cfg.photo_height)),("%[RX_PHOTO_TITLE]",cfg.photo_title),
("%[RX_ADMIN]",cfg.receiver_admin), ("%[RX_ADMIN]",cfg.receiver_admin),
("%[RX_ANT]",cfg.receiver_ant), ("%[RX_ANT]",cfg.receiver_ant),
("%[RX_DEVICE]",cfg.receiver_device) ("%[RX_DEVICE]",cfg.receiver_device),
("%[AUDIO_BUFSIZE]",str(cfg.client_audio_buffer_size))
) )
for rule in replace_dictionary: for rule in replace_dictionary:
while data.find(rule[0])!=-1: while data.find(rule[0])!=-1:
@ -417,6 +511,27 @@ class WebRXHandler(BaseHTTPRequestHandler):
class ClientNotFoundException(Exception): class ClientNotFoundException(Exception):
pass pass
last_worktime=0
last_idletime=0
def get_cpu_usage():
global last_worktime, last_idletime
f=open("/proc/stat","r")
line=""
while not "cpu " in line: line=f.readline()
f.close()
spl=line.split(" ")
worktime=int(spl[2])+int(spl[3])+int(spl[4])
idletime=int(spl[5])
dworktime=(worktime-last_worktime)
didletime=(idletime-last_idletime)
rate=float(dworktime)/(didletime+dworktime)
last_worktime=worktime
last_idletime=idletime
if(last_worktime==0): return 0
return rate
if __name__=="__main__": if __name__=="__main__":
main() main()

0
plugins/__init__.py Executable file → Normal file
View File

0
plugins/dsp/__init__.py Executable file → Normal file
View File

0
plugins/dsp/csdr/__init__.py Executable file → Normal file
View File

View File

@ -1,13 +1,36 @@
"""
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 subprocess
import time import time
import os import os
import code import code
import signal
class dsp_plugin: class dsp_plugin:
def __init__(self): def __init__(self):
self.samp_rate = 250000 self.samp_rate = 250000
self.output_rate = 44100 #this is default, and cannot be set at the moment self.output_rate = 11025 #this is default, and cannot be set at the moment
self.fft_size = 1024 self.fft_size = 1024
self.fft_fps = 5 self.fft_fps = 5
self.offset_freq = 0 self.offset_freq = 0
@ -16,20 +39,42 @@ class dsp_plugin:
self.bpf_transition_bw = 320 #Hz, and this is a constant self.bpf_transition_bw = 320 #Hz, and this is a constant
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
self.running = False self.running = False
chain_begin="nc localhost 4951 | csdr convert_u8_f | 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 | " self.audio_compression = "none"
self.chains = { self.fft_compression = "none"
"nfm" : chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 44100 | csdr fastagc_ff | csdr convert_f_i16",
"am" : chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_i16",
"ssb" : chain_begin + "csdr realpart_cf | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_i16",
"fft" : "nc -vv localhost 4951 | csdr convert_u8_f | csdr fft_cc {fft_size} {fft_block_size} | csdr logpower_cf -70"
}
self.demodulator = "nfm" self.demodulator = "nfm"
self.name = "csdr" self.name = "csdr"
self.format_conversion = "csdr convert_u8_f"
try: try:
subprocess.Popen("nc",stdout=subprocess.PIPE,stderr=subprocess.PIPE) subprocess.Popen("nc",stdout=subprocess.PIPE,stderr=subprocess.PIPE).kill()
except: except:
print "[openwebrx-plugin:csdr] error: netcat not found, please install netcat!" print "[openwebrx-plugin:csdr] error: netcat not found, please install netcat!"
def chain(self,which):
any_chain_base="nc -v localhost 4951 | "+self.format_conversion+(" | " if self.format_conversion!="" else "")+"csdr flowcontrol {flowcontrol} 10 | "
if which == "fft":
fft_chain_base = "sleep 1; "+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 | "
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 fastagc_ff | csdr convert_f_i16"+chain_end
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_i16"+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_i16"+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): def set_samp_rate(self,samp_rate):
#to change this, restart is required #to change this, restart is required
self.samp_rate=samp_rate self.samp_rate=samp_rate
@ -47,11 +92,13 @@ class dsp_plugin:
def get_output_rate(self): def get_output_rate(self):
return self.output_rate return self.output_rate
def set_demodulator(self,demodulator): def set_demodulator(self,demodulator):
#to change this, restart is required #to change this, restart is required
self.demodulator=demodulator self.demodulator=demodulator
def get_demodulator(self):
return self.demodulator
def set_fft_size(self,fft_size): def set_fft_size(self,fft_size):
#to change this, restart is required #to change this, restart is required
self.fft_size=fft_size self.fft_size=fft_size
@ -63,6 +110,9 @@ class dsp_plugin:
def fft_block_size(self): def fft_block_size(self):
return self.samp_rate/self.fft_fps 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): def set_offset_freq(self,offset_freq):
self.offset_freq=offset_freq self.offset_freq=offset_freq
if self.running: if self.running:
@ -90,7 +140,7 @@ class dsp_plugin:
return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate)) return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate))
def start(self): def start(self):
command_base=self.chains[self.demodulator] command_base=self.chain(self.demodulator)
#create control pipes for csdr #create control pipes for csdr
pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self)) pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
@ -103,10 +153,10 @@ class dsp_plugin:
self.mkfifo(self.shift_pipe) self.mkfifo(self.shift_pipe)
#run the command #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()) 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*4*2*1.5))
print "[openwebrx-dsp-plugin:csdr] Command =",command print "[openwebrx-dsp-plugin:csdr] Command =",command
#code.interact(local=locals()) #code.interact(local=locals())
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp)
self.running = True self.running = True
#open control pipes for csdr and send initialization data #open control pipes for csdr and send initialization data
@ -121,12 +171,22 @@ class dsp_plugin:
return self.process.stdout.read(size) return self.process.stdout.read(size)
def stop(self): def stop(self):
if(self.process!=None):return # returns None while subprocess is running os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
while(self.process.poll()==None): #if(self.process.poll()!=None):return # returns None while subprocess is running
self.process.kill() #while(self.process.poll()==None):
time.sleep(0.1) # #self.process.kill()
os.unlink(self.bpf_pipe) # print "killproc",os.getpgid(self.process.pid),self.process.pid
os.unlink(self.shift_pipe) # os.killpg(self.process.pid, signal.SIGTERM)
#
# time.sleep(0.1)
try:
os.unlink(self.bpf_pipe)
except:
print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
try:
os.unlink(self.shift_pipe)
except:
print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
self.running = False self.running = False
def restart(self): def restart(self):

View File

@ -1,20 +1,20 @@
''' '''
This file is part of RTL Multi-User Server, This file is part of RTL Multi-User Server,
that makes multi-user access to your DVB-T dongle used as an SDR. that makes multi-user access to your DVB-T dongle used as an SDR.
Copyright (c) 2013-2014 by Andras Retzler, HA7ILM <randras@sdr.hu> Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
RTL Multi-User Server 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 General Public License as published by it under the terms of the GNU Affero General Public License as
the Free Software Foundation, either version 3 of the License, or published by the Free Software Foundation, either version 3 of the
(at your option) any later version. License, or (at your option) any later version.
RTL Multi-User Server is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with RTL Multi-User Server. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
----- -----
@ -87,7 +87,7 @@ def add_data_to_clients(new_data):
elif cfg.cache_full_behaviour == 1: elif cfg.cache_full_behaviour == 1:
#rather closing client: #rather closing client:
log.error("client cache full, dropping client: "+str(client[0].ident)+"@"+client[0].socket[1][0]) log.error("client cache full, dropping client: "+str(client[0].ident)+"@"+client[0].socket[1][0])
client[0].close() client[0].close(False)
elif cfg.cache_full_behaviour == 2: elif cfg.cache_full_behaviour == 2:
pass #client cache full, just not taking care pass #client cache full, just not taking care
else: log.error("invalid value for cfg.cache_full_behaviour") else: log.error("invalid value for cfg.cache_full_behaviour")
@ -131,6 +131,7 @@ class client_handler(asyncore.dispatcher):
self.sent_dongle_id=False self.sent_dongle_id=False
self.last_waiting_buffer="" self.last_waiting_buffer=""
asyncore.dispatcher.__init__(self, self.client[0].socket[0]) asyncore.dispatcher.__init__(self, self.client[0].socket[0])
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
def handle_read(self): def handle_read(self):
global commands global commands
@ -174,6 +175,7 @@ class server_asyncore(asyncore.dispatcher):
self.set_reuse_addr() self.set_reuse_addr()
self.bind((cfg.my_ip, cfg.my_listening_port)) self.bind((cfg.my_ip, cfg.my_listening_port))
self.listen(5) self.listen(5)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
log.info("Server listening on port: "+str(cfg.my_listening_port)) log.info("Server listening on port: "+str(cfg.my_listening_port))
def handle_accept(self): def handle_accept(self):
@ -188,7 +190,7 @@ class server_asyncore(asyncore.dispatcher):
my_client[0].ident=max_client_id my_client[0].ident=max_client_id
max_client_id+=1 max_client_id+=1
my_client[0].start_time=time.time() my_client[0].start_time=time.time()
my_client[0].waiting_data=multiprocessing.Queue(250) my_client[0].waiting_data=multiprocessing.Queue(500)
clients_mutex.acquire() clients_mutex.acquire()
clients.append(my_client) clients.append(my_client)
clients_mutex.release() clients_mutex.release()
@ -223,6 +225,7 @@ class rtl_tcp_asyncore(asyncore.dispatcher):
def __init__(self): def __init__(self):
global server_missing_logged global server_missing_logged
asyncore.dispatcher.__init__(self) asyncore.dispatcher.__init__(self)
self.password_sent = False
self.ok=True self.ok=True
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
@ -255,6 +258,7 @@ class rtl_tcp_asyncore(asyncore.dispatcher):
global server_missing_logged global server_missing_logged
global rtl_tcp_connected global rtl_tcp_connected
self.socket.settimeout(0.1) self.socket.settimeout(0.1)
self.password_sent = False
rtl_tcp_connected=True rtl_tcp_connected=True
if self.ok: if self.ok:
log.info("rtl_tcp host connection estabilished") log.info("rtl_tcp host connection estabilished")
@ -278,9 +282,9 @@ class rtl_tcp_asyncore(asyncore.dispatcher):
if(len(rtl_dongle_identifier)==0): if(len(rtl_dongle_identifier)==0):
rtl_dongle_identifier=self.recv(12) rtl_dongle_identifier=self.recv(12)
return return
new_data_buffer=self.recv(16348) new_data_buffer=self.recv(1024*16)
if cfg.watchdog_interval: if cfg.watchdog_interval:
watchdog_data_count+=16348 watchdog_data_count+=1024*16
if cfg.use_dsp_command: if cfg.use_dsp_command:
dsp_input_queue.put(new_data_buffer) dsp_input_queue.put(new_data_buffer)
#print "did put anyway" #print "did put anyway"
@ -288,11 +292,16 @@ class rtl_tcp_asyncore(asyncore.dispatcher):
add_data_to_clients(new_data_buffer) add_data_to_clients(new_data_buffer)
def writable(self): def writable(self):
#check if any new commands to write #check if any new commands to write
global commands global commands
return not commands.empty() return (not self.password_sent and cfg.rtl_tcp_password != None) or not commands.empty()
def handle_write(self): def handle_write(self):
if(not self.password_sent and cfg.rtl_tcp_password != None):
log.info("Sending rtl_tcp_password...")
self.send(cfg.rtl_tcp_password)
self.password_sent = True
global commands global commands
while not commands.empty(): while not commands.empty():
mcmd=commands.get() mcmd=commands.get()
@ -418,11 +427,13 @@ class client:
socket=None socket=None
asyncore=None asyncore=None
def close(self): def close(self, use_mutex=True):
global clients_mutex global clients_mutex
global clients global clients
clients_mutex.acquire() if use_mutex: clients_mutex.acquire()
correction=0
for i in range(0,len(clients)): for i in range(0,len(clients)):
i-=correction
if clients[i][0].ident==self.ident: if clients[i][0].ident==self.ident:
try: try:
self.socket.close() self.socket.close()
@ -433,8 +444,9 @@ class client:
del self.asyncore del self.asyncore
except: except:
pass pass
break del clients[i]
clients_mutex.release() correction+=1
if use_mutex: clients_mutex.release()
def main(): def main():

23
rxws.py
View File

@ -1,23 +1,22 @@
""" """
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.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
OpenWebRX 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 General Public License as published by it under the terms of the GNU Affero General Public License as
the Free Software Foundation, either version 3 of the License, or published by the Free Software Foundation, either version 3 of the
(at your option) any later version. License, or (at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
Authors:
Andras Retzler, HA7ILM <retzlerandras@gmail.com>
""" """

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

51
sdrhu.py Executable file
View File

@ -0,0 +1,51 @@
#!/usr/bin/python2
"""
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This 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 config_webrx as cfg, time, subprocess
def run(continuously=True):
if not cfg.sdrhu_key: return
firsttime="(Your receiver is soon getting listed on sdr.hu!)"
while True:
cmd = "wget --timeout=15 -qO- http://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1"
#print "[openwebrx-sdrhu]", cmd
returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
returned=returned[0]
#print returned
if "UPDATE:" in returned:
retrytime_mins = 20
value=returned.split("UPDATE:")[1].split("\n",1)[0]
if value.startswith("SUCCESS"):
print "[openwebrx-sdrhu] Update succeeded! "+firsttime
firsttime=""
else:
print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value
else:
retrytime_mins = 2
print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!"
if not continuously: break
time.sleep(60*retrytime_mins)
if __name__=="__main__":
run(False)