Merged dev2 into master.

This commit is contained in:
ha7ilm 2016-02-14 19:28:06 +01:00
commit bf1d3805ea
12 changed files with 359 additions and 716 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.pyc

5
CONTRIBUTORS Normal file
View File

@ -0,0 +1,5 @@
This is a list of the great people who contributed code to the OpenWebRX repository. (Names are sorted alphabetically.)
Gnoxter <gnoxter@linuxlounge.net>
John Seamons, ZL/KF6VO <jks@jks.com>

View File

@ -14,14 +14,28 @@ 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 supports RTL-SDR and HackRF; other SDR hardware may be easily added. - currently supports RTL-SDR and HackRF; other SDR hardware may be easily added.
**News:** **News (2015-08-18)**
- My BSc. thesis written on OpenWebRX is <a href="http://openwebrx.org/bsc-thesis.pdf">available here.</a> - My BSc. thesis written on OpenWebRX is <a href="http://openwebrx.org/bsc-thesis.pdf">available here.</a>
- Several bugs were fixed to improve reliability and stability. - Several bugs were fixed to improve reliability and stability.
- OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.) - OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.)
- OpenWebRX now uses <a href="https://github.com/simonyiszk/csdr#sdrjs">sdr.js</a> (*libcsdr* compiled to JavaScript) for some client-side DSP tasks. - OpenWebRX now uses <a href="https://github.com/simonyiszk/csdr#sdrjs">sdr.js</a> (*libcsdr* compiled to JavaScript) for some client-side DSP tasks.
- Receivers can now be listed on <a href="http://sdr.hu/">sdr.hu</a>. - Receivers can now be listed on <a href="http://sdr.hu/">SDR.hu</a>.
- License for OpenWebRX is now Affero GPL v3. - License for OpenWebRX is now Affero GPL v3.
**News (2015-02-14)**
- The DDC in *csdr* has been [manually optimized](https://github.com/simonyiszk/csdr/blob/2b54054a9f5de9a908ee075b488a5ee74f41ba18/libcsdr.c#L300) for ARM NEON, so it runs around 3 times faster on the Raspberry Pi 2 than before.
- Also we use *ncat* instead of *rtl_mus*, and it is 3 times faster.
- OpenWebRX now supports URLs like: `http://localhost:8073/#freq=145555000,mod=usb`
- UI improvements were made, thanks to John Seamons and Gnoxter.
> When upgrading OpenWebRX, please make sure that you upgrade *csdr*, and install the new (optional) dependency *ncat*!
## OpenWebRX servers on SDR.hu
[SDR.hu](http://sdr.hu) is a site which lists the active, public OpenWebRX servers. Your receiver [can also be part of it](http://sdr.hu/openwebrx), if you want.
![sdr.hu](/screenshot-sdrhu.png?raw=true)
## Setup ## Setup
OpenWebRX currently requires Linux and python 2.7 to run. OpenWebRX currently requires Linux and python 2.7 to run.
@ -30,6 +44,11 @@ First you will need to install the dependencies:
- <a href="https://github.com/simonyiszk/csdr">libcsdr</a> - <a href="https://github.com/simonyiszk/csdr">libcsdr</a>
- <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</a> - <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</a>
- ncat (On Debian/Ubuntu, it is in the *nmap* package).
> By the way, *nmap* is a tool commonly used for auditing network security, and it is not used by OpenWebRX in any way. We need to install it, because the *ncat* command is packaged with it.
>
> *ncat* is a better *netcat* alternative, which is used by OpenWebRX for internally distributing the I/Q data stream. It also solves the problem of having different versions of *netcat* on different Linux distributions, which are not compatible by their command-line arguments.
After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server: After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server:
@ -39,7 +58,6 @@ You can now open the GUI at <a href="http://localhost:8073">http://localhost:807
Please note that the server is also listening on the following ports (on localhost only): Please note that the server is also listening on the following ports (on localhost only):
- port 8888 for the I/Q source,
- port 4951 for the multi-user I/Q server. - port 4951 for the multi-user I/Q server.
Now the next step is to customize the parameters of your server in `config_webrx.py`. Now the next step is to customize the parameters of your server in `config_webrx.py`.
@ -61,7 +79,7 @@ If you have any problems installing OpenWebRX, you should check out the <a href=
Sometimes the actual error message is not at the end of the terminal output, you may have to look at the whole output to find it. Sometimes the actual error message is not at the end of the terminal output, you may have to look at the whole output to find it.
If you want to run OpenWebRX on a remote server instead of localhost, do not forget to set *server_hostname* in `config_webrx.py`. If you want to run OpenWebRX on a remote server instead of *localhost*, do not forget to set *server_hostname* in `config_webrx.py`.
## Licensing ## Licensing

View File

@ -1,103 +0,0 @@
'''
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-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/>.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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_listening_port = 4951
rtl_tcp_host,rtl_tcp_port='localhost',8888
send_first=""
#send_first=chr(9)+chr(0)+chr(0)+chr(0)+chr(1) # set direct sampling
setuid_on_start = 0 # we normally start with root privileges and setuid() to another user
uid = 999 # determine by issuing: $ id -u username
ignore_clients_without_commands = 1 # we won't serve data to telnet sessions and things like that
# we'll start to serve data after getting the first valid command
freq_allowed_ranges = [[0,2200000000]]
client_cant_set_until=0
first_client_can_set=True # openwebrx - spectrum thread will set things on start # no good, clients set parameters and things
buffer_size=25000000 # per client
log_file_path = "/dev/null" # Might be set to /dev/null to turn off logging
'''
Allow any host to connect:
use_ip_access_control=0
Allow from specific ranges:
use_ip_access_control=1
order_allow_deny=0 # deny and then allow
denied_ip_ranges=() # deny from all
allowed_ip_ranges=('192.168.','44.','127.0.0.1') # allow only from ...
Deny from specific ranges:
use_ip_access_control=1
order_allow_deny=0 # allow and then deny
allowed_ip_ranges=() # allow from all
denied_ip_ranges=('192.168.') # deny any hosts from ...
'''
use_ip_access_control=1 #You may want to open up the I/Q server to the public, then set this to zero.
order_allow_deny=0
denied_ip_ranges=() # deny from all
allowed_ip_ranges=('127.0.0.1') # allow only local connections (from openwebrx).
allow_gain_set=1
use_dsp_command=False # you can process raw I/Q data with a custom command that starts a process that we can pipe the data into, and also pipe out of.
debug_dsp_command=False # show sample rate before and after the dsp command
dsp_command=""
'''
Example DSP commands:
* Compress I/Q data with FLAC:
flac --force-raw-format --channels 2 --sample-rate=250000 --sign=unsigned --bps=8 --endian=little -o - -
* Decompress FLAC-coded I/Q data:
flac --force-raw-format --decode --endian=little --sign=unsigned - -
'''
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_thread.
If watchdog_interval is 0, then watchdog thread is not started.
'''
cache_full_behaviour=2
'''
0 = drop samples
1 = close client
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.
'''

View File

@ -81,7 +81,7 @@ start_rtl_thread=True
# >> RTL-SDR via rtl_sdr # >> 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) start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
format_conversion="csdr convert_u8_f" format_conversion="csdr convert_u8_f"
#start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l16 -a0 -r hackrf_pipe & cat hackrf_pipe | nc -vvl 127.0.0.1 8888".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) #start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l16 -a0 -r hackrf_pipe & cat hackrf_pipe | nc -vvl 127.0.0.1 8888".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
@ -94,23 +94,31 @@ format_conversion="csdr convert_u8_f"
# >> Sound card SDR (needs ALSA) # >> Sound card SDR (needs ALSA)
#I did not have the chance to properly test it. #I did not have the chance to properly test it.
#samp_rate = 96000 #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) #start_rtl_command="arecord -f S16_LE -r {samp_rate} -c2 -".format(samp_rate=samp_rate)
#format_conversion="csdr convert_i16_f | csdr gain_ff 30" #format_conversion="csdr convert_s16_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 # >> /dev/urandom test signal source
#samp_rate = 2400000 #samp_rate = 2400000
#start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1) | nc -vvl 127.0.0.1 8888".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate) #start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
#format_conversion="csdr convert_u8_f" #format_conversion="csdr convert_u8_f"
#You can use other SDR hardware as well, by giving your own command that outputs the I/Q samples... #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 shown_center_freq = center_freq #you can change this if you use an upconverter
client_audio_buffer_size = 4 client_audio_buffer_size = 5
#increasing client_audio_buffer_size will: #increasing client_audio_buffer_size will:
# - also increase the latency # - also increase the latency
# - decrease the chance of audio underruns # - decrease the chance of audio underruns
start_freq = center_freq
start_mod = "nfm" #nfm, am, lsb, usb, cw
iq_server_port = 4951 #TCP port for ncat to listen on. It will send I/Q data over its connections, for internal use in OpenWebRX. It is only accessible from the localhost by default.
#access_log = "~/openwebrx_access.log"
#Warning! The settings below are very experimental.
csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr.
csdr_print_bufsizes = False # This prints the buffer sizes used for csdr processes.
csdr_through = False # Setting this True will print out how much data is going into the DSP chains.

85
htdocs/inactive.html Normal file
View File

@ -0,0 +1,85 @@
<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>
</head>
<body>
<div class="frame">
<img class="logo" src="gfx/openwebrx-logo-big.png" style="height: 60px;"/>
<div class="panel">
Sorry, the receiver is inactive due to internal error.
</div>
</div>
</body>
</html>

View File

@ -23,11 +23,13 @@
<head> <head>
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title> <title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
<script type="text/javascript"> <script type="text/javascript">
//Local variables //Global variables
client_id="%[CLIENT_ID]"; var client_id="%[CLIENT_ID]";
ws_url="%[WS_URL]"; var ws_url="%[WS_URL]";
rx_photo_height=%[RX_PHOTO_HEIGHT]; var rx_photo_height=%[RX_PHOTO_HEIGHT];
var audio_buffering_fill_to=%[AUDIO_BUFSIZE]; var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
var starting_mod = "%[START_MOD]";
var starting_offset_frequency = %[START_OFFSET_FREQ];
</script> </script>
<script src="sdr.js"></script> <script src="sdr.js"></script>
<script src="openwebrx.js"></script> <script src="openwebrx.js"></script>

View File

@ -988,6 +988,32 @@ 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";
} }
audio_server_output_rate=11025;
audio_client_resampling_factor=4;
function audio_calculate_resampling(targetRate)
{ //both at the server and the client
output_range_max = 12000;
output_range_min = 8000;
i = 1;
while(true)
{
audio_server_output_rate = Math.floor(targetRate / i);
if(audio_server_output_rate < output_range_min)
{
audio_client_resampling_factor = audio_server_output_rate = 0;
divlog("Your audio card sampling rate ("+targetRate.toString()+") is not supported.<br />Please change your operating system default settings in order to fix this.",1);
}
if(audio_server_output_rate >= output_range_min && audio_server_output_rate <= output_range_max) break; //okay, we're done
i++;
}
audio_client_resampling_factor=i;
console.log("audio_calculate_resampling() :: "+audio_client_resampling_factor.toString()+", "+audio_server_output_rate.toString());
}
debug_ws_data_received=0; debug_ws_data_received=0;
max_clients_num=0; max_clients_num=0;
@ -1003,6 +1029,7 @@ function on_ws_recv(evt)
{ {
var stringData=arrayBufferToString(evt.data); var stringData=arrayBufferToString(evt.data);
if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Acknowledged WebSocket connection: "+stringData); if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Acknowledged WebSocket connection: "+stringData);
} }
if(firstChars=="AUD") if(firstChars=="AUD")
{ {
@ -1021,6 +1048,7 @@ function on_ws_recv(evt)
else if(fft_compression="adpcm") else if(fft_compression="adpcm")
{ {
fft_codec.reset(); fft_codec.reset();
var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4)); var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4));
var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N); var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N);
for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100; for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100;
@ -1039,6 +1067,7 @@ function on_ws_recv(evt)
{ {
case "setup": case "setup":
waterfall_init(); waterfall_init();
audio_preinit();
break; break;
case "bandwidth": case "bandwidth":
bandwidth=parseInt(param[1]); bandwidth=parseInt(param[1]);
@ -1154,7 +1183,7 @@ var mute = false;
var audio_received = Array(); var audio_received = Array();
var audio_buffer_index = 0; var audio_buffer_index = 0;
var audio_resampler=new sdrjs.RationalResamplerFF(4,1); var audio_resampler;
var audio_codec=new sdrjs.ImaAdpcm(); var audio_codec=new sdrjs.ImaAdpcm();
var audio_compression="unknown"; var audio_compression="unknown";
var audio_node; var audio_node;
@ -1199,7 +1228,7 @@ function audio_prepare(data)
audio_prepared_buffers.push(audio_rebuffer.take()); audio_prepared_buffers.push(audio_rebuffer.take());
audio_buffer_current_count_debug++; audio_buffer_current_count_debug++;
} }
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) { console.log("buffers now: "+audio_prepared_buffers.length.toString()); audio_buffering=false; }
} }
@ -1278,6 +1307,8 @@ var audio_buffer_progressbar_update_disabled=false;
var audio_buffer_total_average_level=0; var audio_buffer_total_average_level=0;
var audio_buffer_total_average_level_length=0; var audio_buffer_total_average_level_length=0;
var audio_overrun_cnt = 0;
var audio_underrun_cnt = 0;
function audio_buffer_progressbar_update() function audio_buffer_progressbar_update()
{ {
@ -1287,8 +1318,8 @@ function audio_buffer_progressbar_update()
var overrun=audio_buffer_value>audio_buffer_maximal_length_sec; var overrun=audio_buffer_value>audio_buffer_maximal_length_sec;
var underrun=audio_prepared_buffers.length==0; var underrun=audio_prepared_buffers.length==0;
var text="buffer"; var text="buffer";
if(overrun) text="overrun"; if(overrun) { text="overrun"; console.log("audio overrun, "+(++audio_overrun_cnt).toString()); }
if(underrun) text="underrun"; if(underrun) { text="underrun"; console.log("audio underrun, "+(++audio_underrun_cnt).toString()); }
if(overrun||underrun) if(overrun||underrun)
{ {
audio_buffer_progressbar_update_disabled=true; audio_buffer_progressbar_update_disabled=true;
@ -1377,13 +1408,26 @@ function webrx_set_param(what, value)
ws.send("SET "+what+"="+value.toString()); ws.send("SET "+what+"="+value.toString());
} }
function audio_init() function parsehash()
{ {
audio_debug_time_start=(new Date()).getTime(); if(h=window.location.hash)
audio_debug_time_last_start=audio_debug_time_start; {
h.substring(1).split(",").forEach(function(x){
harr=x.split("=");
console.log(harr);
if(harr[0]=="mod") starting_mod = harr[1];
if(harr[0]=="freq") {
console.log(parseInt(harr[1]));
console.log(center_freq);
starting_offset_frequency = parseInt(harr[1])-center_freq;
}
});
}
}
//https://github.com/0xfe/experiments/blob/master/www/tone/js/sinewave.js function audio_preinit()
audio_initialized=1; // only tell on_ws_recv() not to call it again {
try try
{ {
window.AudioContext = window.AudioContext||window.webkitAudioContext; window.AudioContext = window.AudioContext||window.webkitAudioContext;
@ -1394,6 +1438,28 @@ function audio_init()
divlog('Your browser does not support Web Audio API, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser.', 1); divlog('Your browser does not support Web Audio API, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser.', 1);
} }
//we send our setup packet
parsehash();
//needs audio_context.sampleRate to exist
audio_calculate_resampling(audio_context.sampleRate);
audio_resampler = new sdrjs.RationalResamplerFF(audio_client_resampling_factor,1);
ws.send("SET output_rate="+audio_server_output_rate.toString()+" action=start"); //now we'll get AUD packets as well
}
function audio_init()
{
if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor...
audio_debug_time_start=(new Date()).getTime();
audio_debug_time_last_start=audio_debug_time_start;
//https://github.com/0xfe/experiments/blob/master/www/tone/js/sinewave.js
audio_initialized=1; // only tell on_ws_recv() not to call it again
//on Chrome v36, createJavaScriptNode has been replaced by createScriptProcessor //on Chrome v36, createJavaScriptNode has been replaced by createScriptProcessor
createjsnode_function = (audio_context.createJavaScriptNode == undefined)?audio_context.createScriptProcessor.bind(audio_context):audio_context.createJavaScriptNode.bind(audio_context); createjsnode_function = (audio_context.createJavaScriptNode == undefined)?audio_context.createScriptProcessor.bind(audio_context):audio_context.createJavaScriptNode.bind(audio_context);
audio_node = createjsnode_function(audio_buffer_size, 0, 1); audio_node = createjsnode_function(audio_buffer_size, 0, 1);
@ -1411,7 +1477,14 @@ function audio_init()
audio_buffer = audio_context.createBuffer(xhr.response, false); audio_buffer = audio_context.createBuffer(xhr.response, false);
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(starting_mod);
if(starting_offset_frequency)
{
demodulators[0].offset_frequency = starting_offset_frequency;
demodulators[0].set();
mkscale();
}
//hide log panel in a second (if user has not hidden it yet) //hide log panel in a second (if user has not hidden it yet)
window.setTimeout(function(){ window.setTimeout(function(){
if(typeof e("openwebrx-panel-log").openwebrxHidden == "undefined" && !was_error) if(typeof e("openwebrx-panel-log").openwebrxHidden == "undefined" && !was_error)

View File

@ -29,6 +29,7 @@ import plugins
import plugins.dsp import plugins.dsp
import thread import thread
import time import time
import datetime
import subprocess import subprocess
import os import os
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
@ -47,7 +48,6 @@ import ctypes
#import rtl_mus #import rtl_mus
import rxws import rxws
import uuid import uuid
import config_webrx as cfg
import signal import signal
import socket import socket
@ -75,11 +75,22 @@ class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer):
pass pass
def handle_signal(signal, frame): def handle_signal(signal, frame):
global spectrum_dsp
print "[openwebrx] Ctrl+C: aborting." print "[openwebrx] Ctrl+C: aborting."
cleanup_clients(True)
spectrum_dsp.stop()
os._exit(1) #not too graceful exit os._exit(1) #not too graceful exit
def access_log(data):
global logs
logs.access_log.write("["+datetime.datetime.now().isoformat()+"] "+data+"\n")
logs.access_log.flush()
receiver_failed=spectrum_thread_watchdog_last_tick=rtl_thread=spectrum_dsp=server_fail=None
def main(): def main():
global clients, clients_mutex, pypy, lock_try_time, avatar_ctime global clients, clients_mutex, pypy, lock_try_time, avatar_ctime, cfg, logs
global serverfail, rtl_thread
print print
print "OpenWebRX - Open Source SDR Web App 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 "_________________________________________________________________________________________________"
@ -87,6 +98,15 @@ def main():
print "Author contact info: Andras Retzler, HA7ILM <randras@sdr.hu>" print "Author contact info: Andras Retzler, HA7ILM <randras@sdr.hu>"
print print
no_arguments=len(sys.argv)==1
if no_arguments: print "[openwebrx-main] Configuration script not specified. I will use: \"config_webrx.py\""
cfg=__import__("config_webrx" if no_arguments else sys.argv[1])
for option in ("access_log","csdr_dynamic_bufsize","csdr_print_bufsizes","csdr_through"):
if not option in dir(cfg): setattr(cfg, option, False) #initialize optional config parameters
#Open log files
logs = type("logs_class", (object,), {"access_log":open(cfg.access_log if cfg.access_log else "/dev/null","a"), "error_log":""})()
#Set signal handler #Set signal handler
signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python
@ -107,16 +127,14 @@ def main():
pass pass
#Start rtl thread #Start rtl thread
if os.system("ncat --version > /dev/null") == 32512: #check for ncat
print "[openwebrx-main] Error: ncat not detected, please install it! The ncat tool is a netcat alternative, used for distributing the I/Q data stream. It is usually available in the nmap package (sudo apt-get install nmap). For more explanation, look into the README.md"
return
if cfg.start_rtl_thread: if cfg.start_rtl_thread:
rtl_thread=threading.Thread(target = lambda:subprocess.Popen(cfg.start_rtl_command, shell=True), args=()) cfg.start_rtl_command += "| ncat -4l %d -k --send-only --allow 127.0.0.1" % cfg.iq_server_port
rtl_thread=threading.Thread(target = lambda:subprocess.Popen(cfg.start_rtl_command, shell=True), args=())
rtl_thread.start() rtl_thread.start()
print "[openwebrx-main] Started rtl thread: "+cfg.start_rtl_command print "[openwebrx-main] Started rtl_thread: "+cfg.start_rtl_command
#Run rtl_mus.py in a different OS thread
python_command="pypy" if pypy else "python2"
rtl_mus_thread=threading.Thread(target = lambda:subprocess.Popen(python_command+" rtl_mus.py config_rtl", shell=True), args=())
rtl_mus_thread.start() # The new feature in GNU Radio 3.7: top_block() locks up ALL python threads until it gets the TCP connection.
print "[openwebrx-main] Started rtl_mus."
time.sleep(1) #wait until it really starts time.sleep(1) #wait until it really starts
#Initialize clients #Initialize clients
@ -136,6 +154,8 @@ def main():
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()
spectrum_watchdog_thread=threading.Thread(target = spectrum_watchdog_thread_function, args = ())
spectrum_watchdog_thread.start()
get_cpu_usage() get_cpu_usage()
bcastmsg_thread=threading.Thread(target = bcastmsg_thread_function, args = ()) bcastmsg_thread=threading.Thread(target = bcastmsg_thread_function, args = ())
@ -146,13 +166,14 @@ def main():
#Start sdr.hu update thread #Start sdr.hu update thread
if sdrhu and cfg.sdrhu_key and cfg.sdrhu_public_listing: if sdrhu and cfg.sdrhu_key and cfg.sdrhu_public_listing:
print "[openwebrx-main] Starting sdr.hu update thread..." print "[openwebrx-main] Starting sdr.hu update thread..."
avatar_ctime=str(os.path.getctime("htdocs/gfx/openwebrx-avatar.png"))
sdrhu_thread=threading.Thread(target = sdrhu.run, args = ()) sdrhu_thread=threading.Thread(target = sdrhu.run, args = ())
sdrhu_thread.start() 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)
print('[openwebrx-main] Starting HTTP server.') print('[openwebrx-main] Starting HTTP server.')
access_log("Starting OpenWebRX...")
httpd.serve_forever() httpd.serve_forever()
@ -204,27 +225,61 @@ def mutex_watchdog_thread_function():
while True: while True:
if lock_try_time != 0 and time.time()-lock_try_time > 3.0: if lock_try_time != 0 and time.time()-lock_try_time > 3.0:
#if 3 seconds pass without unlock #if 3 seconds pass without unlock
print "[openwebrx-watchdog] Mutex unlock timeout. Locker: \""+str(clients_mutex_locker)+"\" Now unlocking..." print "[openwebrx-mutex-watchdog] Mutex unlock timeout. Locker: \""+str(clients_mutex_locker)+"\" Now unlocking..."
clients_mutex.release() clients_mutex.release()
time.sleep(0.5) time.sleep(0.5)
def spectrum_watchdog_thread_function():
global spectrum_thread_watchdog_last_tick, receiver_failed
while True:
time.sleep(60)
if spectrum_thread_watchdog_last_tick and time.time()-spectrum_thread_watchdog_last_tick > 60.0:
print "[openwebrx-spectrum-watchdog] Spectrum timeout. Seems like no I/Q data is coming from the receiver.\nIf you're using RTL-SDR, the receiver hardware may randomly fail under some circumstances:\n1) high temperature,\n2) insufficient current available from the USB port."
print "[openwebrx-spectrum-watchdog] Deactivating receiver."
receiver_failed="spectrum"
return
def check_server():
global spectrum_dsp, server_fail, rtl_thread
if server_fail: return server_fail
#print spectrum_dsp.process.poll()
if spectrum_dsp and spectrum_dsp.process.poll()!=None: server_fail = "spectrum_thread dsp subprocess failed"
#if rtl_thread and not rtl_thread.is_alive(): server_fail = "rtl_thread failed"
if server_fail: print "[openwebrx-check_server] >>>>>>> ERROR:", server_fail
return server_fail
def apply_csdr_cfg_to_dsp(dsp):
dsp.csdr_dynamic_bufsize = cfg.csdr_dynamic_bufsize
dsp.csdr_print_bufsizes = cfg.csdr_print_bufsizes
dsp.csdr_through = cfg.csdr_through
def spectrum_thread_function(): def spectrum_thread_function():
global clients global clients, spectrum_dsp, spectrum_thread_watchdog_last_tick
dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() spectrum_dsp=dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin()
dsp.nc_port=cfg.iq_server_port
dsp.set_demodulator("fft") dsp.set_demodulator("fft")
dsp.set_samp_rate(cfg.samp_rate) dsp.set_samp_rate(cfg.samp_rate)
dsp.set_fft_size(cfg.fft_size) dsp.set_fft_size(cfg.fft_size)
dsp.set_fft_fps(cfg.fft_fps) dsp.set_fft_fps(cfg.fft_fps)
dsp.set_fft_compression(cfg.fft_compression) dsp.set_fft_compression(cfg.fft_compression)
dsp.set_format_conversion(cfg.format_conversion) dsp.set_format_conversion(cfg.format_conversion)
apply_csdr_cfg_to_dsp(dsp)
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()
if cfg.csdr_dynamic_bufsize:
dsp.read(8) #dummy read to skip bufsize & preamble
print "[openwebrx-spectrum] Note: CSDR_DYNAMIC_BUFSIZE_ON = 1"
print "[openwebrx-spectrum] Spectrum thread started." print "[openwebrx-spectrum] Spectrum thread started."
bytes_to_read=int(dsp.get_fft_bytes_to_read()) bytes_to_read=int(dsp.get_fft_bytes_to_read())
spectrum_thread_counter=0
while True: while True:
data=dsp.read(bytes_to_read) 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()"
if spectrum_thread_counter >= cfg.fft_fps:
spectrum_thread_counter=0
spectrum_thread_watchdog_last_tick = time.time() #once every second
else: spectrum_thread_counter+=1
cma("spectrum_thread") cma("spectrum_thread")
correction=0 correction=0
for i in range(0,len(clients)): for i in range(0,len(clients)):
@ -255,16 +310,17 @@ def get_client_by_id(client_id, use_mutex=True):
def log_client(client, what): def log_client(client, what):
print "[openwebrx-httpd] client {0}#{1} :: {2}".format(client.ip,client.id,what) print "[openwebrx-httpd] client {0}#{1} :: {2}".format(client.ip,client.id,what)
def cleanup_clients(): def cleanup_clients(end_all=False):
# if client doesn't open websocket for too long time, we drop it # - if a client doesn't open websocket for too long time, we drop it
# - or if end_all is true, we drop all clients
global clients global clients
cma("cleanup_clients") 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)>45: if end_all or ((not clients[i].ws_started) and (time.time()-clients[i].gen_time)>45):
print "[openwebrx] cleanup_clients :: client timeout to open WebSocket" if not end_all: print "[openwebrx] cleanup_clients :: client timeout to open WebSocket"
close_client(i, False) close_client(i, False)
correction+=1 correction+=1
cmr() cmr()
@ -299,6 +355,7 @@ def close_client(i, use_mutex=True):
print "[openwebrx] close_client dsp.stop() :: error -",exc_type,exc_value print "[openwebrx] close_client dsp.stop() :: error -",exc_type,exc_value
traceback.print_tb(exc_traceback) traceback.print_tb(exc_traceback)
clients[i].closed[0]=True clients[i].closed[0]=True
access_log("Stopped streaming to client: "+clients[i].ip+"#"+str(clients[i].id)+" (users now: "+str(len(clients)-1)+")")
del clients[i] del clients[i]
if use_mutex: cmr() if use_mutex: cmr()
@ -319,18 +376,20 @@ class WebRXHandler(BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
global dsp_plugin, clients_mutex, clients, avatar_ctime, sw_version global dsp_plugin, clients_mutex, clients, avatar_ctime, sw_version, receiver_failed
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("?")
self.path=path_temp_parts[0] self.path=path_temp_parts[0]
request_param=path_temp_parts[1] if(len(path_temp_parts)>1) else "" request_param=path_temp_parts[1] if(len(path_temp_parts)>1) else ""
access_log("GET "+self.path+" from "+self.client_address[0])
try: try:
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[: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/":
if receiver_failed: self.send_error(500,"Internal server error")
try: try:
# ========= WebSocket handshake ========= # ========= WebSocket handshake =========
ws_success=True ws_success=True
@ -363,23 +422,26 @@ class WebRXHandler(BaseHTTPRequestHandler):
# ========= Initialize DSP ========= # ========= Initialize DSP =========
dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin()
dsp.set_samp_rate(cfg.samp_rate) dsp_initialized=False
dsp.set_demodulator("nfm")
dsp.set_offset_freq(0)
dsp.set_bpf(-4000,4000)
dsp.set_audio_compression(cfg.audio_compression) dsp.set_audio_compression(cfg.audio_compression)
dsp.set_format_conversion(cfg.format_conversion) dsp.set_format_conversion(cfg.format_conversion)
dsp.start() dsp.set_offset_freq(0)
dsp.set_bpf(-4000,4000)
dsp.nc_port=cfg.iq_server_port
apply_csdr_cfg_to_dsp(dsp)
myclient.dsp=dsp myclient.dsp=dsp
access_log("Started streaming to client: "+self.client_address[0]+"#"+myclient.id+" (users now: "+str(len(clients))+")")
while True: while True:
if myclient.closed[0]: if myclient.closed[0]:
print "[openwebrx-httpd:ws] client closed by other thread" print "[openwebrx-httpd:ws] client closed by other thread"
break break
# ========= send audio ========= # ========= send audio =========
temp_audio_data=dsp.read(256) if dsp_initialized:
rxws.send(self, temp_audio_data, "AUD ") temp_audio_data=dsp.read(256)
rxws.send(self, temp_audio_data, "AUD ")
# ========= send spectrum ========= # ========= send spectrum =========
while not myclient.spectrum_queue.empty(): while not myclient.spectrum_queue.empty():
@ -417,9 +479,17 @@ class WebRXHandler(BaseHTTPRequestHandler):
dsp.set_offset_freq(int(param_value)) dsp.set_offset_freq(int(param_value))
elif param_name=="mod": elif param_name=="mod":
if (dsp.get_demodulator()!=param_value): if (dsp.get_demodulator()!=param_value):
dsp.stop() if dsp_initialized: dsp.stop()
dsp.set_demodulator(param_value) dsp.set_demodulator(param_value)
if dsp_initialized: dsp.start()
elif param_name == "output_rate":
if not dsp_initialized:
dsp.set_output_rate(int(param_value))
dsp.set_samp_rate(cfg.samp_rate)
elif param_name=="action" and param_value=="start":
if not dsp_initialized:
dsp.start() dsp.start()
dsp_initialized=True
else: else:
print "[openwebrx-httpd:ws] invalid parameter" print "[openwebrx-httpd:ws] invalid parameter"
if bpf_set: if bpf_set:
@ -457,13 +527,17 @@ class WebRXHandler(BaseHTTPRequestHandler):
elif self.path in ("/status", "/status/"): elif self.path in ("/status", "/status/"):
#self.send_header('Content-type','text/plain') #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)) 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") self.wfile.write("status="+("inactive" if receiver_failed else "active")+"\nname="+cfg.receiver_name+"\nsdr_hw="+cfg.receiver_device+"\nop_email="+cfg.receiver_admin+"\nbands="+getbands()+"\nusers="+str(len(clients))+"\nusers_max="+str(cfg.max_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] 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:]
checkresult=check_server()
if extension == "wrx" and (checkresult or receiver_failed):
self.send_302("inactive.html")
return
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")): 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") self.send_302("upgrade.html")
return return
@ -492,7 +566,9 @@ class WebRXHandler(BaseHTTPRequestHandler):
("%[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)) ("%[AUDIO_BUFSIZE]",str(cfg.client_audio_buffer_size)),
("%[START_OFFSET_FREQ]",str(cfg.start_freq-cfg.center_freq)),
("%[START_MOD]",cfg.start_mod)
) )
for rule in replace_dictionary: for rule in replace_dictionary:
while data.find(rule[0])!=-1: while data.find(rule[0])!=-1:

View File

@ -44,13 +44,17 @@ class dsp_plugin:
self.demodulator = "nfm" self.demodulator = "nfm"
self.name = "csdr" self.name = "csdr"
self.format_conversion = "csdr convert_u8_f" self.format_conversion = "csdr convert_u8_f"
try: self.base_bufsize = 512
subprocess.Popen("nc",stdout=subprocess.PIPE,stderr=subprocess.PIPE).kill() self.nc_port = 4951
except: self.csdr_dynamic_bufsize = False
print "[openwebrx-plugin:csdr] error: netcat not found, please install netcat!" self.csdr_print_bufsizes = False
self.csdr_through = False
def chain(self,which): def chain(self,which):
any_chain_base="nc -v localhost 4951 | "+self.format_conversion+(" | " if self.format_conversion!="" else "")+"csdr flowcontrol {flowcontrol} 10 | " any_chain_base="ncat -v 127.0.0.1 {nc_port} | "
if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
if self.csdr_through: any_chain_base+="csdr through | "
any_chain_base+=self.format_conversion+(" | " if self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
if which == "fft": 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}" 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": if self.fft_compression=="adpcm":
@ -61,9 +65,9 @@ class dsp_plugin:
chain_end = "" chain_end = ""
if self.audio_compression=="adpcm": if self.audio_compression=="adpcm":
chain_end = " | csdr encode_ima_adpcm_i16_u8" 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 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 1024 | 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 == "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 elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr clipdetect_ff | csdr limit_ff | csdr convert_f_i16"+chain_end
def set_audio_compression(self,what): def set_audio_compression(self,what):
self.audio_compression = what self.audio_compression = what
@ -92,6 +96,10 @@ class dsp_plugin:
def get_output_rate(self): def get_output_rate(self):
return self.output_rate return self.output_rate
def set_output_rate(self,output_rate):
self.output_rate=output_rate
self.set_samp_rate(self.samp_rate) #as it depends on output_rate
def set_demodulator(self,demodulator): def set_demodulator(self,demodulator):
#to change this, restart is required #to change this, restart is required
self.demodulator=demodulator self.demodulator=demodulator
@ -153,10 +161,17 @@ 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(),flowcontrol=int(self.samp_rate*4*2*1.5)) command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \
last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), \
bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(), ddc_transition_bw=self.ddc_transition_bw(), \
flowcontrol=int(self.samp_rate*2), start_bufsize=self.base_bufsize*self.decimation, nc_port=self.nc_port)
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, preexec_fn=os.setpgrp) my_env=os.environ.copy()
if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
self.running = True self.running = True
#open control pipes for csdr and send initialization data #open control pipes for csdr and send initialization data
@ -179,14 +194,16 @@ class dsp_plugin:
# os.killpg(self.process.pid, signal.SIGTERM) # os.killpg(self.process.pid, signal.SIGTERM)
# #
# time.sleep(0.1) # time.sleep(0.1)
try: if self.bpf_pipe:
os.unlink(self.bpf_pipe) try:
except: os.unlink(self.bpf_pipe)
print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe except:
try: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
os.unlink(self.shift_pipe) if self.shift_pipe:
except: try:
print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe 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,539 +0,0 @@
'''
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-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/>.
-----
2013-11? Asyncore version
2014-03 Fill with null on no data
'''
import socket
import sys
import array
import time
import logging
import os
import time
import subprocess
import fcntl
import thread
import pdb
import asyncore
import multiprocessing
import signal
#pypy compatiblity
try: import dl
except: pass
import code
import traceback
def handle_signal(signal, frame):
log.info("Ctrl+C: aborting.")
os._exit(1) #not too graceful exit
def ip_match(this,ip_ranges,for_allow):
if not len(ip_ranges):
return 1 #empty list matches all ip addresses
for ip_range in ip_ranges:
#print this[0:len(ip_range)], ip_range
if this[0:len(ip_range)]==ip_range:
return 1
return 0
def ip_access_control(ip):
if(not cfg.use_ip_access_control): return 1
allowed=0
if(cfg.order_allow_deny):
if ip_match(ip,cfg.allowed_ip_ranges,1): allowed=1
if ip_match(ip,cfg.denied_ip_ranges,0): allowed=0
else:
if ip_match(ip,cfg.denied_ip_ranges,0):
allowed=0
if ip_match(ip,cfg.allowed_ip_ranges,1):
allowed=1
return allowed
def add_data_to_clients(new_data):
# might be called from:
# -> dsp_read
# -> rtl_tcp_asyncore.handle_read
global clients
global clients_mutex
clients_mutex.acquire()
for client in clients:
#print "client %d size: %d"%(client[0].ident,client[0].waiting_data.qsize())
if(client[0].waiting_data.full()):
if cfg.cache_full_behaviour == 0:
log.error("client cache full, dropping samples: "+str(client[0].ident)+"@"+client[0].socket[1][0])
while not client[0].waiting_data.empty(): # clear queue
client[0].waiting_data.get(False, None)
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(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")
else:
client[0].waiting_data.put(new_data)
clients_mutex.release()
def dsp_read_thread():
global proc
global dsp_data_count
while True:
try:
my_buffer=proc.stdout.read(1024)
except IOError:
log.error("DSP subprocess is not ready for reading.")
time.sleep(1)
continue
add_data_to_clients(my_buffer)
if cfg.debug_dsp_command:
dsp_data_count+=len(my_buffer)
def dsp_write_thread():
global proc
global dsp_input_queue
global original_data_count
while True:
try:
my_buffer=dsp_input_queue.get(timeout=0.3)
except:
continue
proc.stdin.write(my_buffer)
proc.stdin.flush()
if cfg.debug_dsp_command:
original_data_count+=len(my_buffer)
class client_handler(asyncore.dispatcher):
def __init__(self,client_param):
self.client=client_param
self.client[0].asyncore=self
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
new_command = self.recv(5)
if len(new_command)>=5:
if handle_command(new_command, self.client):
commands.put(new_command)
def handle_error(self):
exc_type, exc_value, exc_traceback = sys.exc_info()
log.info("client error: "+str(self.client[0].ident)+"@"+self.client[0].socket[1][0])
traceback.print_tb(exc_traceback)
self.close()
def handle_close(self):
self.client[0].close()
log.info("client disconnected: "+str(self.client[0].ident)+"@"+self.client[0].socket[1][0])
def writable(self):
#print "queryWritable",not self.client[0].waiting_data.empty()
return not self.client[0].waiting_data.empty()
def handle_write(self):
global last_waiting
global rtl_dongle_identifier
global sample_rate
if not self.sent_dongle_id:
self.send(rtl_dongle_identifier)
self.sent_dongle_id=True
return
#print "write2client",self.client[0].waiting_data.qsize()
next=self.last_waiting_buffer+self.client[0].waiting_data.get()
sent=asyncore.dispatcher.send(self, next)
self.last_waiting_buffer=next[sent:]
class server_asyncore(asyncore.dispatcher):
def __init__(self):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
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):
global max_client_id
global clients_mutex
global clients
my_client=[client()]
my_client[0].socket=self.accept()
if (my_client[0].socket is None): # not sure if required
return
if (ip_access_control(my_client[0].socket[1][0])):
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(500)
clients_mutex.acquire()
clients.append(my_client)
clients_mutex.release()
handler = client_handler(my_client)
log.info("client accepted: "+str(len(clients)-1)+"@"+my_client[0].socket[1][0]+":"+str(my_client[0].socket[1][1])+" users now: "+str(len(clients)))
else:
log.info("client denied: "+str(len(clients)-1)+"@"+my_client[0].socket[1][0]+":"+str(my_client[0].socket[1][1])+" blocked by ip")
my_client.socket.close()
rtl_tcp_resetting=False #put me away
def rtl_tcp_asyncore_reset(timeout):
global rtl_tcp_core
global rtl_tcp_resetting
if rtl_tcp_resetting: return
#print "rtl_tcp_asyncore_reset"
rtl_tcp_resetting=True
time.sleep(timeout)
try:
rtl_tcp_core.close()
except:
pass
try:
del rtl_tcp_core
except:
pass
rtl_tcp_core=rtl_tcp_asyncore()
#print asyncore.socket_map
rtl_tcp_resetting=False
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:
self.connect((cfg.rtl_tcp_host, cfg.rtl_tcp_port))
self.socket.settimeout(0.1)
except:
log.error("rtl_tcp connection refused. Retrying.")
thread.start_new_thread(rtl_tcp_asyncore_reset, (1,))
self.close()
return
def handle_error(self):
global server_missing_logged
global rtl_tcp_connected
rtl_tcp_connected=False
exc_type, exc_value, exc_traceback = sys.exc_info()
self.ok=False
server_is_missing=hasattr(exc_value,"errno") and exc_value.errno==111
if (not server_is_missing) or (not server_missing_logged):
log.error("with rtl_tcp host connection: "+str(exc_value))
#traceback.print_tb(exc_traceback)
server_missing_logged|=server_is_missing
try:
self.close()
except:
pass
thread.start_new_thread(rtl_tcp_asyncore_reset, (2,))
def handle_connect(self):
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")
server_missing_logged=False
def handle_close(self):
global rtl_tcp_connected
global rtl_tcp_core
rtl_tcp_connected=False
log.error("rtl_tcp host connection has closed, now trying to reopen")
try:
self.close()
except:
pass
thread.start_new_thread(rtl_tcp_asyncore_reset, (2,))
def handle_read(self):
global rtl_dongle_identifier
global dsp_input_queue
global watchdog_data_count
if(len(rtl_dongle_identifier)==0):
rtl_dongle_identifier=self.recv(12)
return
new_data_buffer=self.recv(1024*16)
if cfg.watchdog_interval:
watchdog_data_count+=1024*16
if cfg.use_dsp_command:
dsp_input_queue.put(new_data_buffer)
#print "did put anyway"
else:
add_data_to_clients(new_data_buffer)
def writable(self):
#check if any new commands to write
global commands
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()
self.send(mcmd)
def xxd(data):
#diagnostic purposes only
output=""
for d in data:
output+=hex(ord(d))[2:].zfill(2)+" "
return output
def handle_command(command, client_param):
global sample_rate
client=client_param[0]
param=array.array("I", command[1:5])[0]
param=socket.ntohl(param)
command_id=ord(command[0])
client_info=str(client.ident)+"@"+client.socket[1][0]+":"+str(client.socket[1][1])
if(time.time()-client.start_time<cfg.client_cant_set_until and not (cfg.first_client_can_set and client.ident==0) ):
log.info("deny: "+client_info+" -> client can't set anything until "+str(cfg.client_cant_set_until)+" seconds")
return 0
if command_id == 1:
if max(map((lambda r: param>=r[0] and param<=r[1]),cfg.freq_allowed_ranges)):
log.debug("allow: "+client_info+" -> set freq "+str(param))
return 1
else:
log.debug("deny: "+client_info+" -> set freq - out of range: "+str(param))
elif command_id == 2:
log.debug("deny: "+client_info+" -> set sample rate: "+str(param))
sample_rate=param
return 0 # ordinary clients are not allowed to do this
elif command_id == 3:
log.debug("deny/allow: "+client_info+" -> set gain mode: "+str(param))
return cfg.allow_gain_set
elif command_id == 4:
log.debug("deny/allow: "+client_info+" -> set gain: "+str(param))
return cfg.allow_gain_set
elif command_id == 5:
log.debug("deny: "+client_info+" -> set freq correction: "+str(param))
return 0
elif command_id == 6:
log.debug("deny/allow: set if stage gain")
return cfg.allow_gain_set
elif command_id == 7:
log.debug("deny: set test mode")
return 0
elif command_id == 8:
log.debug("deny/allow: set agc mode")
return cfg.allow_gain_set
elif command_id == 9:
log.debug("deny: set direct sampling")
return 0
elif command_id == 10:
log.debug("deny: set offset tuning")
return 0
elif command_id == 11:
log.debug("deny: set rtl xtal")
return 0
elif command_id == 12:
log.debug("deny: set tuner xtal")
return 0
elif command_id == 13:
log.debug("deny/allow: set tuner gain by index")
return cfg.allow_gain_set
else:
log.debug("deny: "+client_info+" sent an ivalid command: "+str(param))
return 0
def watchdog_thread():
global rtl_tcp_connected
global rtl_tcp_core
global watchdog_data_count
global sample_rate
zero_buffer_size=16348
second_frac=10
zero_buffer='\x7f'*zero_buffer_size
watchdog_data_count=0
rtl_tcp_connected=False
null_fill=False
time.sleep(4) # wait before activating this thread
log.info("watchdog started")
first_start=True
n=0
while True:
wait_altogether=cfg.watchdog_interval if rtl_tcp_connected or first_start else cfg.reconnect_interval
first_start=False
if null_fill:
log.error("watchdog: filling buffer with zeros.")
while wait_altogether>0:
wait_altogether-=1.0/second_frac
for i in range(0,((2*sample_rate)/second_frac)/zero_buffer_size):
add_data_to_clients(zero_buffer)
n+=len(zero_buffer)
time.sleep(0) #yield
if watchdog_data_count: break
if watchdog_data_count: break
time.sleep(1.0/second_frac)
#print "sent altogether",n
else:
time.sleep(wait_altogether)
null_fill=not watchdog_data_count
if not watchdog_data_count:
log.error("watchdog: restarting rtl_tcp_asyncore() now.")
rtl_tcp_asyncore_reset(0)
watchdog_data_count=0
def dsp_debug_thread():
global dsp_data_count
global original_data_count
while 1:
time.sleep(1)
print "[rtl-mus] DSP | Original data: "+str(int(original_data_count/1000))+"kB/sec | Processed data: "+str(int(dsp_data_count/1000))+"kB/sec"
dsp_data_count = original_data_count=0
class client:
ident=None #id
to_close=False
waiting_data=None
start_time=None
socket=None
asyncore=None
def close(self, use_mutex=True):
global clients_mutex
global clients
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()
except:
pass
try:
self.asyncore.close()
del self.asyncore
except:
pass
del clients[i]
correction+=1
if use_mutex: clients_mutex.release()
def main():
global server_missing_logged
global rtl_dongle_identifier
global log
global clients
global clients_mutex
global original_data_count
global dsp_input_queue
global dsp_data_count
global proc
global commands
global max_client_id
global rtl_tcp_core
global sample_rate
#Set signal handler
signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python
# set up logging
log = logging.getLogger("rtl_mus")
log.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(formatter)
log.addHandler(stream_handler)
file_handler = logging.FileHandler(cfg.log_file_path)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
log.addHandler(file_handler)
log.info("Server is UP")
server_missing_logged=0 # Not to flood the screen with messages related to rtl_tcp disconnect
rtl_dongle_identifier='' # rtl_tcp sends some identifier on dongle type and gain values in the first few bytes right after connection
clients=[]
dsp_data_count=original_data_count=0
commands=multiprocessing.Queue()
dsp_input_queue=multiprocessing.Queue()
clients_mutex=multiprocessing.Lock()
max_client_id=0
sample_rate=250000 # so far only watchdog thread uses it to fill buffer up with zeros on missing input
# start dsp threads
if cfg.use_dsp_command:
print "[rtl_mus] Opening DSP process..."
proc = subprocess.Popen (cfg.dsp_command.split(" "), stdin = subprocess.PIPE, stdout = subprocess.PIPE) #!! should fix the split :-S
dsp_read_thread_v=thread.start_new_thread(dsp_read_thread, ())
dsp_write_thread_v=thread.start_new_thread(dsp_write_thread, ())
if cfg.debug_dsp_command:
dsp_debug_thread_v=thread.start_new_thread(dsp_debug_thread,())
# start watchdog thread
if cfg.watchdog_interval != 0:
watchdog_thread_v=thread.start_new_thread(watchdog_thread,())
# start asyncores
rtl_tcp_core = rtl_tcp_asyncore()
server_core = server_asyncore()
asyncore.loop(0.1)
if __name__=="__main__":
print
print "rtl_mus: Multi-User I/Q Data Server for RTL-SDR v0.22, made at HA5KFU Amateur Radio Club (http://ha5kfu.hu)"
print " code by Andras Retzler, HA7ILM <randras@sdr.hu>"
print " distributed under GNU GPL v3"
print
try:
for libcpath in ["/lib/i386-linux-gnu/libc.so.6","/lib/libc.so.6"]:
if os.path.exists(libcpath):
libc = dl.open(libcpath)
libc.call("prctl", 15, "rtl_mus", 0, 0, 0)
break
except:
pass
# === Load configuration script ===
if len(sys.argv)==1:
print "[rtl_mus] Warning! Configuration script not specified. I will use: \"config_rtl.py\""
config_script="config_rtl"
else:
config_script=sys.argv[1]
cfg=__import__(config_script)
if cfg.setuid_on_start:
os.setuid(cfg.uid)
main()

BIN
screenshot-sdrhu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB