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),
- 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
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.
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
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.
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.
RTL Multi-User Server 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 General Public License for more details.
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 General Public License
along with RTL Multi-User Server. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
In addition, as a special exception, the copyright holders
state that config_rtl.py and config_webrx.py are not part of the
Corresponding Source defined in GNU AGPL version 3 section 1.
(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
@ -70,11 +80,11 @@ Example DSP commands:
* Decompress FLAC-coded I/Q data:
flac --force-raw-format --decode --endian=little --sign=unsigned - -
'''
watchdog_interval=1.5
watchdog_interval=0
reconnect_interval=10
'''
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.
'''
@ -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)
'''
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
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
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,
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 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
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
In addition, as a special exception, the copyright holders
state that config_rtl.py and config_webrx.py are not part of the
Corresponding Source defined in GNU AGPL version 3 section 1.
(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
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_location="Budapest, Hungary"
receiver_qra="JN97ML"
receiver_asl=182
receiver_asl=200
receiver_ant="Longwire"
receiver_device="RTL-SDR"
receiver_admin="localhost@localhost"
receiver_admin="example@example.com"
receiver_gps=(47.000000,19.000000)
photo_height=350
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>
"""
#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"
fft_fps=9
fft_size=4096
samp_rate = 250000
center_freq = 145525000
rf_gain = 5
ppm = 0
start_rtl_thread=True #rtl_sdr is more stable than rtl_tcp...
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)
#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).
audio_compression="adpcm" #valid values: "adpcm", "none"
fft_compression="adpcm" #valid values: "adpcm", "none"
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>
<!--
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
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.
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 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
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>
<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">
//Local variables
client_id="%[CLIENT_ID]";
ws_url="%[WS_URL]";
rx_photo_height=%[RX_PHOTO_HEIGHT];
var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
</script>
<script src="sdr.js"></script>
<script src="openwebrx.js"></script>
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
<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-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
</div>
<section id="openwebrx-main-buttons">
<ul>
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
</ul>
</section>
</div>
</div>
<div id="webrx-main-container">
@ -61,7 +72,7 @@ This file is part of OpenWebRX.
<!-- add canvas here by javascript -->
</div>
<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-mouse-freq">---.--- MHz</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('cw');">CW</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-inner">
<div id="openwebrx-client-log-title">openwebrx.js (beta) client log </strong><span id="openwebrx-problems"></span></div>
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,142">
<div class="openwebrx-panel-inner" id="openwebrx-log-scroll">
<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/>
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/>-->
Your client ID is: <em>%[CLIENT_ID]</em><br />
<div id="openwebrx-debugdiv"></div>
</div>
</div>
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network 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;">
<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.

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
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.
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 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
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
@ -52,9 +54,14 @@ html, body
{
margin: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
{
position: absolute;
@ -65,7 +72,7 @@ html, body
#webrx-ha5kfu-top-logo
{
position: absolute;
top: 19px;
top: 15px;
right: 15px;
}
@ -177,8 +184,10 @@ html, body
#webrx-rx-photo-desc a
{
/*color: #007df1;*/
color: #5ca8ff;
text-shadow: none;
/*text-shadow: 0px 0px 7px #fff;*/
}
#webrx-rx-title
@ -269,6 +278,8 @@ html, body
{
position: absolute;
border-style: none;
image-rendering: crisp-edges;
image-rendering: -webkit-optimize-contrast;
}
#openwebrx-phantom-canvas
@ -410,6 +421,12 @@ html, body
cursor: pointer;
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% );
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
@ -431,3 +448,94 @@ html, body
margin-bottom: 5px;
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
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,
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 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
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
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/>.
"""
*/
@ -43,6 +45,9 @@ var audio_buffer_current_count_debug=0;
var audio_buffer_current_size=0;
var fft_size;
var fft_fps;
var fft_compression="none";
var fft_codec=new sdrjs.ImaAdpcm();
var audio_compression="none";
var waterfall_setup_done=0;
var waterfall_queue = [];
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.
// 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
function demodulator_default_analog(offset_frequency,subtype)
@ -703,27 +708,21 @@ function mkscale()
var text_measured=scale_ctx.measureText(text_to_draw);
scale_ctx.textAlign = "center";
//advanced text drawing begins
if(zoom_level==0&&range.start+spacing.smallbw*spacing.ratio>marker_hz)
{ //if this is the first overall marker when zoomed out
if(x<text_measured.width/2)
{ //and if it would be clipped off the screen
if(scale_px_from_freq(marker_hz+spacing.smallbw*spacing.ratio,range)-text_measured.width>=scale_min_space_bw_texts)
{ //and if we have enough space to draw it correctly without clipping
scale_ctx.textAlign = "left";
scale_ctx.fillText(text_to_draw, 0, text_h_pos);
}
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... and if it would be clipped off the screen...
if(scale_px_from_freq(marker_hz+spacing.smallbw*spacing.ratio,range)-text_measured.width>=scale_min_space_bw_texts)
{ //and if we have enough space to draw it correctly without clipping
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)
{ //if this is the last overall marker when zoomed out
if(x>window.innerWidth-text_measured.width/2)
{ //and if it would be clipped off the screen
if(window.innerWidth-text_measured.width-scale_px_from_freq(marker_hz-spacing.smallbw*spacing.ratio,range)>=scale_min_space_bw_texts)
{ //and if we have enough space to draw it correctly without clipping
scale_ctx.textAlign = "right";
scale_ctx.fillText(text_to_draw, window.innerWidth, text_h_pos);
}
}
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... and if it would be clipped off the screen...
if(window.innerWidth-text_measured.width-scale_px_from_freq(marker_hz-spacing.smallbw*spacing.ratio,range)>=scale_min_space_bw_texts)
{ //and if we have enough space to draw it correctly without clipping
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
}
@ -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";
}
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)
{
if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; }
//
debug_ws_data_received+=evt.data.byteLength/1000;
firstChars=getFirstChars(evt.data,3);
if(firstChars=="CLI")
{
@ -973,7 +977,9 @@ function on_ws_recv(evt)
}
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_buffer_current_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")
{
//alert("Yupee! Doing FFT");
var floatArray = new Float32Array(evt.data,4);
waterfall_add_queue(floatArray);
if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4));
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")
{
/*try
@ -999,18 +1012,36 @@ function on_ws_recv(evt)
waterfall_init();
break;
case "bandwidth":
bandwidth=parseInt(param[1])
bandwidth=parseInt(param[1]);
break;
case "center_freq":
center_freq=parseInt(param[1])
center_freq=parseInt(param[1]); //there was no ; and it was no problem... why?
break;
case "fft_size":
fft_size=parseInt(param[1])
fft_size=parseInt(param[1]);
break;
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;
}
}
/*}
@ -1032,17 +1063,33 @@ function add_problem(what)
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)
{
if(waterfall_measure_minmax) waterfall_measure_minmax_do(what);
waterfall_queue.push(what);
}
function waterfall_dequeue()
{
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");
while(waterfall_queue.length) waterfall_add(waterfall_queue.shift());
}
@ -1054,10 +1101,20 @@ function on_ws_opened()
divlog("WebSocket opened to "+ws_url);
}
var was_error=0;
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 />";
var wls=e("openwebrx-log-scroll");
wls.scrollTop=wls.scrollHeight; //scroll to bottom
}
var audio_context;
@ -1065,54 +1122,105 @@ var audio_initialized=0;
var audio_received = Array();
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_received_sample_rate = 48000;
var audio_input_buffer_size;
// Optimalise these if audio lags or is choppy:
var audio_buffer_size = 8192;//2048 was choppy
var audio_buffer_maximal_length_sec=1.7; //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_size = 4096;//2048 was choppy
var audio_buffer_maximal_length_sec=3; //actual number of samples are calculated from sample rate
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_rebuffer = new sdrjs.Rebuffer(audio_buffer_size,sdrjs.REBUFFER_FIXED);
var audio_last_output_buffer = new Float32Array(audio_buffer_size);
var audio_last_output_offset = 0;
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)
{
//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("data.len = "+data.length.toString());
var dopush=function()
{
console.log(audio_last_output_buffer);
audio_prepared_buffers.push(audio_last_output_buffer);
audio_last_output_offset=0;
audio_last_output_buffer=new Float32Array(audio_buffer_size);
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(audio_last_output_offset+data.length<=audio_buffer_size)
{ //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;
//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();
}
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 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
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
//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
audio_last_output_buffer[i]=data[i+copied]/32768;
audio_last_output_buffer[i]=data[i+copied];///32768;
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;
}
@ -1127,24 +1235,49 @@ if (!AudioBuffer.prototype.copyToChannel)
}
function audio_onprocess(e)
{
{
//console.log("audio onprocess");
if(audio_buffering) return;
if(audio_prepared_buffers.length==0) { add_problem("audio underrun"); audio_buffering=true; }
else e.outputBuffer.copyToChannel(audio_prepared_buffers.shift(),0);
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); }
}
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()
{
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;
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
//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);
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);
divlog('Web Audio API succesfully initialized, sample rate: '+audio_context.sampleRate.toString()+ " sps");
/*audio_source=audio_context.createBufferSource();
@ -1246,6 +1380,14 @@ function audio_init()
audio_source.buffer = buffer;
audio_source.noteOn(0);*/
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()
@ -1267,11 +1409,11 @@ String.prototype.startswith=function(str){ return this.indexOf(str) == 0; }; //h
function open_websocket()
{
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);
ws_url="ws://"+(window.location.origin.split("://")[1])+"/ws/"; //guess automatically
}
//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);
ws_url="ws://"+(window.location.origin.split("://")[1])+"/ws/"; //guess automatically -> now default behaviour
//}
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.");
ws = new WebSocket(ws_url+client_id);
@ -1293,14 +1435,16 @@ function open_websocket()
//var color_scale=[ 0x000000FF, 0xff5656ff, 0xffffffff];
//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)
{
min_value=-100; //in dB
max_value=10
if(db_value<min_value) db_value=min_value
if(db_value>max_value) db_value=max_value
min_value=-115; //in dB
max_value=0;
if(db_value<min_value) db_value=min_value;
if(db_value>max_value) db_value=max_value;
full_scale=max_value-min_value;
relative_value=db_value-min_value;
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;
}
//Draw image
canvas_context.putImageData(oneline_image, 0, canvas_actual_line--);
shift_canvases();
if(canvas_actual_line<0) add_canvas();
//divlog("Drawn FFT");
}
@ -1525,10 +1670,17 @@ function waterfall_shift()
function check_top_bar_congestion()
{
var wt=e("webrx-rx-title");
var tl=e("webrx-ha5kfu-top-logo");
if(wt.offsetLeft+wt.offsetWidth>tl.offsetLeft-20) tl.style.display="none";
else tl.style.display="block";
var rmf=function(x){ return x.offsetLeft+x.offsetWidth; };
var wet=e("webrx-rx-title");
var wed=e("webrx-rx-desc");
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()
@ -1546,6 +1698,7 @@ function openwebrx_init()
place_panels();
window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000);
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_last_start=time_now; //now
audio_debug_time_taken=(time_now-audio_debug_time_start)/1000;
e("openwebrx-audio-sps").innerHTML=
"audio recv. at "+(audio_buffer_current_size_debug/audio_debug_time_since_last_call).toFixed(0)+" sps ("+
(audio_buffer_all_size_debug/audio_debug_time_taken).toFixed(1)+" sps avg.), feed at "+
((audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken).toFixed(1)+" sps output";
kbps_mult=(audio_compression=="adpcm")?8:16;
//e("openwebrx-audio-sps").innerHTML=
// ((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_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;
if(waterfall_measure_minmax) waterfall_measure_minmax_print();
}
// ========================================================
// ======================= PANELS =======================
// ========================================================
panel_margin=10;
panel_margin=5.9;
function pop_bottommost_panel(from)
{
@ -1608,8 +1776,19 @@ function pop_bottommost_panel(from)
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()
{
var hoffset=0; //added this because the first panel should not have such great gap below
var left_col=[];
var right_col=[];
var plist=e("openwebrx-panels-container").children;
@ -1618,33 +1797,61 @@ function place_panels()
c=plist[i];
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(",");
if (c.dataset.panelPos=="left") { left_col.push(c); }
else if(c.dataset.panelPos=="right") { right_col.push(c); }
c.style.width=newSize[0]+"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.openwebrxPanelHeight=parseInt(newSize[1]);
}
}
y=0;
y=hoffset; //was y=0 before hoffset
while(left_col.length>0)
{
p=pop_bottommost_panel(left_col);
p.style.left="0px";
p.style.bottom=y.toString()+"px";
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)
{
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.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>
<!--
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
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.
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 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
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">
<style>

View File

@ -1,31 +1,26 @@
#!/usr/bin/python2
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
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.
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 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
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
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
Authors:
Andras Retzler, HA7ILM <randras@sdr.hu>
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/>.
"""
# http://www.codeproject.com/Articles/462525/Simple-HTTP-Server-and-Client-in-Python
# some ideas are used from the artice above
sw_version="v0.12+"
import os
import code
@ -54,6 +49,11 @@ import rxws
import uuid
import config_webrx as cfg
import signal
import socket
try: import sdrhu
except: sdrhu=False
avatar_ctime=""
#pypy compatibility
try: import dl
@ -62,7 +62,6 @@ try: import __pypy__
except: pass
pypy="__pypy__" in globals()
def import_all_plugins(directory):
for subdir in os.listdir(directory):
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
def main():
global clients, clients_mutex, pypy
global clients, clients_mutex, pypy, lock_try_time, avatar_ctime
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 "Author contact info: Andras Retzler, HA7ILM <randras@sdr.hu>"
@ -123,13 +122,33 @@ def main():
#Initialize clients
clients=[]
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
print "[openwebrx-main] Starting spectrum thread."
spectrum_thread=threading.Thread(target = spectrum_thread_function, args = ())
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()
#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
httpd = MultiThreadHTTPServer(('', cfg.web_port), WebRXHandler)
@ -146,23 +165,67 @@ def measure_thread_function():
measure_value=0
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_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
dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin()
dsp.set_demodulator("fft")
dsp.set_samp_rate(cfg.samp_rate)
dsp.set_fft_size(cfg.fft_size)
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
print "[openwebrx-spectrum] Spectrum thread initialized successfully."
dsp.start()
print "[openwebrx-spectrum] Spectrum thread started."
bytes_to_read=int(dsp.get_fft_bytes_to_read())
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()"
clients_mutex.acquire()
cma("spectrum_thread")
correction=0
for i in range(0,len(clients)):
i-=correction
@ -173,18 +236,17 @@ def spectrum_thread_function():
correction+=1
else:
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):
global clients_mutex
global clients
output=-1
if use_mutex: clients_mutex.acquire()
if use_mutex: cma("get_client_by_id")
for i in range(0,len(clients)):
if(clients[i].id==client_id):
output=i
break
if use_mutex: clients_mutex.release()
if use_mutex: cmr()
if output==-1:
raise ClientNotFoundException
else:
@ -195,53 +257,69 @@ def log_client(client, what):
def cleanup_clients():
# if client doesn't open websocket for too long time, we drop it
global clients_mutex
global clients
clients_mutex.acquire()
cma("cleanup_clients")
correction=0
for i in range(0,len(clients)):
i-=correction
#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"
close_client(i, False)
correction+=1
clients_mutex.release()
cmr()
def generate_client_id(ip):
#add a client
global clients
global clients_mutex
new_client=namedtuple("ClientStruct", "id gen_time ws_started sprectum_queue ip closed")
new_client=namedtuple("ClientStruct", "id gen_time ws_started sprectum_queue ip closed bcastmsg dsp")
new_client.id=md5.md5(str(random.random())).hexdigest()
new_client.gen_time=time.time()
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.ip=ip
new_client.bcastmsg=""
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)
log_client(new_client,"client added. Clients now: {0}".format(len(clients)))
clients_mutex.release()
cmr()
cleanup_clients()
return new_client.id
def close_client(i, use_mutex=True):
global clients_mutex
global clients
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
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):
def proc_read_thread():
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):
global dsp_plugin
global clients_mutex
self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
global dsp_plugin, clients_mutex, clients, avatar_ctime, sw_version
rootdir = 'htdocs'
self.path=self.path.replace("..","")
path_temp_parts=self.path.split("?")
@ -251,19 +329,20 @@ class WebRXHandler(BaseHTTPRequestHandler):
if self.path=="/":
self.path="/index.wrx"
# 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/":
try:
# ========= WebSocket handshake =========
ws_success=True
try:
rxws.handshake(self)
clients_mutex.acquire()
cma("do_GET /ws/")
client_i=get_client_by_id(self.path[4:], False)
myclient=clients[client_i]
except rxws.WebSocketException: ws_success=False
except ClientNotFoundException: ws_success=False
finally:
if clients_mutex.locked(): clients_mutex.release()
if clients_mutex.locked(): cmr()
if not ws_success:
self.send_error(400, 'Bad request.')
return
@ -280,7 +359,7 @@ class WebRXHandler(BaseHTTPRequestHandler):
return
myclient.ws_started=True
#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 =========
dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin()
@ -288,7 +367,10 @@ class WebRXHandler(BaseHTTPRequestHandler):
dsp.set_demodulator("nfm")
dsp.set_offset_freq(0)
dsp.set_bpf(-4000,4000)
dsp.set_audio_compression(cfg.audio_compression)
dsp.set_format_conversion(cfg.format_conversion)
dsp.start()
myclient.dsp=dsp
while True:
if myclient.closed[0]:
@ -296,15 +378,21 @@ class WebRXHandler(BaseHTTPRequestHandler):
break
# ========= send audio =========
temp_audio_data=dsp.read(1024*8)
temp_audio_data=dsp.read(256)
rxws.send(self, temp_audio_data, "AUD ")
# ========= send spectrum =========
while not myclient.spectrum_queue.empty():
spectrum_data=myclient.spectrum_queue.get()
spectrum_data_mid=len(spectrum_data[0])/2
rxws.send(self, spectrum_data[0][spectrum_data_mid:]+spectrum_data[0][:spectrum_data_mid], "FFT ")
#spectrum_data_mid=len(spectrum_data[0])/2
#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)
rxws.send(self, spectrum_data[0],"FFT ")
# ========= send bcastmsg =========
if myclient.bcastmsg!="":
rxws.send(self,myclient.bcastmsg)
myclient.bcastmsg=""
# ========= process commands =========
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:
dsp.set_offset_freq(int(param_value))
elif param_name=="mod":
dsp.stop()
dsp.set_demodulator(param_value)
dsp.start()
if (dsp.get_demodulator()!=param_value):
dsp.stop()
dsp.set_demodulator(param_value)
dsp.start()
else:
print "[openwebrx-httpd:ws] invalid parameter"
if bpf_set:
@ -355,7 +444,7 @@ class WebRXHandler(BaseHTTPRequestHandler):
#delete disconnected client
try:
clients_mutex.acquire()
cma("do_GET /ws/ delete disconnected")
id_to_close=get_client_by_id(myclient.id,False)
close_client(id_to_close,False)
except:
@ -363,19 +452,23 @@ class WebRXHandler(BaseHTTPRequestHandler):
print "[openwebrx-httpd] client cannot be closed: ",exc_type,exc_value
traceback.print_tb(exc_traceback)
finally:
clients_mutex.release()
cmr()
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:
f=open(rootdir+self.path)
data=f.read()
extension=self.path[(len(self.path)-4):len(self.path)]
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")):
self.send_response(302)
self.send_header('Content-type','text/html')
self.send_header("Location", "http://{0}:{1}/upgrade.html".format(cfg.server_hostname,cfg.web_port))
self.end_headers()
self.wfile.write("<html><body><h1>Object moved</h1>Please <a href=\"/upgrade.html\">click here</a> to continue.</body></html>")
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_302("upgrade.html")
return
if extension == "wrx" and cfg.max_clients<=len(clients):
self.send_302("retry.html")
return
self.send_response(200)
if(("wrx","html","htm").count(extension)):
@ -384,11 +477,11 @@ class WebRXHandler(BaseHTTPRequestHandler):
self.send_header('Content-type','text/javascript')
elif(extension=="css"):
self.send_header('Content-type','text/css')
self.end_headers()
self.end_headers()
if extension == "wrx":
replace_dictionary=(
("%[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/"),
("%[RX_TITLE]",cfg.receiver_name),
("%[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_ADMIN]",cfg.receiver_admin),
("%[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:
while data.find(rule[0])!=-1:
@ -417,6 +511,27 @@ class WebRXHandler(BaseHTTPRequestHandler):
class ClientNotFoundException(Exception):
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__":
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 time
import os
import code
import signal
class dsp_plugin:
def __init__(self):
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_fps = 5
self.offset_freq = 0
@ -16,20 +39,42 @@ class dsp_plugin:
self.bpf_transition_bw = 320 #Hz, and this is a constant
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
self.running = False
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.chains = {
"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.audio_compression = "none"
self.fft_compression = "none"
self.demodulator = "nfm"
self.name = "csdr"
self.format_conversion = "csdr convert_u8_f"
try:
subprocess.Popen("nc",stdout=subprocess.PIPE,stderr=subprocess.PIPE)
subprocess.Popen("nc",stdout=subprocess.PIPE,stderr=subprocess.PIPE).kill()
except:
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):
#to change this, restart is required
self.samp_rate=samp_rate
@ -47,11 +92,13 @@ class dsp_plugin:
def get_output_rate(self):
return self.output_rate
def set_demodulator(self,demodulator):
#to change this, restart is required
self.demodulator=demodulator
def get_demodulator(self):
return self.demodulator
def set_fft_size(self,fft_size):
#to change this, restart is required
self.fft_size=fft_size
@ -63,6 +110,9 @@ class dsp_plugin:
def fft_block_size(self):
return self.samp_rate/self.fft_fps
def set_format_conversion(self,format_conversion):
self.format_conversion=format_conversion
def set_offset_freq(self,offset_freq):
self.offset_freq=offset_freq
if self.running:
@ -90,7 +140,7 @@ class dsp_plugin:
return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate))
def start(self):
command_base=self.chains[self.demodulator]
command_base=self.chain(self.demodulator)
#create control pipes for csdr
pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
@ -103,10 +153,10 @@ class dsp_plugin:
self.mkfifo(self.shift_pipe)
#run the command
command=command_base.format(bpf_pipe=self.bpf_pipe,shift_pipe=self.shift_pipe,decimation=self.decimation,last_decimation=self.last_decimation,fft_size=self.fft_size,fft_block_size=self.fft_block_size(),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
#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
#open control pipes for csdr and send initialization data
@ -121,12 +171,22 @@ class dsp_plugin:
return self.process.stdout.read(size)
def stop(self):
if(self.process!=None):return # returns None while subprocess is running
while(self.process.poll()==None):
self.process.kill()
time.sleep(0.1)
os.unlink(self.bpf_pipe)
os.unlink(self.shift_pipe)
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
#if(self.process.poll()!=None):return # returns None while subprocess is running
#while(self.process.poll()==None):
# #self.process.kill()
# print "killproc",os.getpgid(self.process.pid),self.process.pid
# os.killpg(self.process.pid, signal.SIGTERM)
#
# time.sleep(0.1)
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
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.
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
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.
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.
RTL Multi-User Server 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 General Public License for more details.
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 General Public License
along with RTL Multi-User Server. If not, see <http://www.gnu.org/licenses/>.
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/>.
-----
@ -87,7 +87,7 @@ def add_data_to_clients(new_data):
elif cfg.cache_full_behaviour == 1:
#rather closing client:
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:
pass #client cache full, just not taking care
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.last_waiting_buffer=""
asyncore.dispatcher.__init__(self, self.client[0].socket[0])
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
def handle_read(self):
global commands
@ -174,6 +175,7 @@ class server_asyncore(asyncore.dispatcher):
self.set_reuse_addr()
self.bind((cfg.my_ip, cfg.my_listening_port))
self.listen(5)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
log.info("Server listening on port: "+str(cfg.my_listening_port))
def handle_accept(self):
@ -188,7 +190,7 @@ class server_asyncore(asyncore.dispatcher):
my_client[0].ident=max_client_id
max_client_id+=1
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.append(my_client)
clients_mutex.release()
@ -223,6 +225,7 @@ class rtl_tcp_asyncore(asyncore.dispatcher):
def __init__(self):
global server_missing_logged
asyncore.dispatcher.__init__(self)
self.password_sent = False
self.ok=True
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
try:
@ -255,6 +258,7 @@ class rtl_tcp_asyncore(asyncore.dispatcher):
global server_missing_logged
global rtl_tcp_connected
self.socket.settimeout(0.1)
self.password_sent = False
rtl_tcp_connected=True
if self.ok:
log.info("rtl_tcp host connection estabilished")
@ -278,9 +282,9 @@ class rtl_tcp_asyncore(asyncore.dispatcher):
if(len(rtl_dongle_identifier)==0):
rtl_dongle_identifier=self.recv(12)
return
new_data_buffer=self.recv(16348)
new_data_buffer=self.recv(1024*16)
if cfg.watchdog_interval:
watchdog_data_count+=16348
watchdog_data_count+=1024*16
if cfg.use_dsp_command:
dsp_input_queue.put(new_data_buffer)
#print "did put anyway"
@ -288,11 +292,16 @@ class rtl_tcp_asyncore(asyncore.dispatcher):
add_data_to_clients(new_data_buffer)
def writable(self):
#check if any new commands to write
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):
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
while not commands.empty():
mcmd=commands.get()
@ -418,11 +427,13 @@ class client:
socket=None
asyncore=None
def close(self):
def close(self, use_mutex=True):
global clients_mutex
global clients
clients_mutex.acquire()
if use_mutex: clients_mutex.acquire()
correction=0
for i in range(0,len(clients)):
i-=correction
if clients[i][0].ident==self.ident:
try:
self.socket.close()
@ -433,8 +444,9 @@ class client:
del self.asyncore
except:
pass
break
clients_mutex.release()
del clients[i]
correction+=1
if use_mutex: clients_mutex.release()
def main():

23
rxws.py
View File

@ -1,23 +1,22 @@
"""
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
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.
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 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
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
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
Authors:
Andras Retzler, HA7ILM <retzlerandras@gmail.com>
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/>.
"""

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)