initial commit
							
								
								
									
										44
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,4 +1,44 @@ | ||||
| openwebrx | ||||
| OpenWebRX | ||||
| ========= | ||||
|  | ||||
| Open source web-based SDR receiver software | ||||
| OpenWebRX is a multi-user SDR receiver software with a web interface. | ||||
|  | ||||
|  | ||||
|  | ||||
| It has the following features: | ||||
|  | ||||
| - <a href="https://github.com/simonyiszk/csdr">libcsdr</a> based demodulators (AM/FM/SSB), | ||||
| - filter bandwith, BFO, PBS can be set from GUI, | ||||
| - waterfall display can be shifted back in time, | ||||
| - it extensively uses HTML5 features like WebSocket, Web Audio API, and >canvas<. | ||||
| - it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28), | ||||
| - currently only supports RTL-SDR, but other SDR hardware may be easily added. | ||||
|  | ||||
| ## Setup | ||||
|  | ||||
| OpenWebRX currently requires a Linux machine to run.  | ||||
|  | ||||
| First you will need to install the dependencies: | ||||
| - <a href="https://github.com/simonyiszk/csdr">libcsdr</a> | ||||
| - <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</a> | ||||
|  | ||||
| After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server: | ||||
|  | ||||
| 	python openwebrx.py | ||||
|  | ||||
| You can now open the GUI at <a href="http://localhost:8073">http://localhost:8073</a>. | ||||
|  | ||||
| Please note that it 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. | ||||
|  | ||||
| Now the next step is to customize the parameters of your server in `config_webrx.py`. | ||||
|  | ||||
| Actually, if you do something cool with OpenWebRX (or just have a problem), please drop me a mail: Andras Retzler, HA7ILM >randras@sdr.hu<. | ||||
| I would like to maintain a list of online amateur radio receivers on <a href="http://sdr.hu/">sdr.hu</a>. | ||||
|  | ||||
| ## Usage tips | ||||
|  | ||||
| The filter envelope can be dragged at its ends and moved. | ||||
|  | ||||
| However, if you hold the shift key, you can drag the center line (BFO) or the passband (PBS). | ||||
|   | ||||
							
								
								
									
										87
									
								
								config_rtl.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,87 @@ | ||||
| ''' | ||||
| 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> | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 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/>. | ||||
| ''' | ||||
|  | ||||
| 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=1.5 | ||||
| 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.  | ||||
| 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) | ||||
| ''' | ||||
|  | ||||
							
								
								
									
										59
									
								
								config_webrx.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,59 @@ | ||||
| # -*- coding: utf-8 -*-  | ||||
|  | ||||
| """ | ||||
| config_webrx: configuration options for OpenWebRX | ||||
|  | ||||
| OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu> | ||||
|  | ||||
| This file is part of OpenWebRX. | ||||
|  | ||||
|     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, | ||||
|     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. | ||||
|  | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with OpenWebRX.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| """ | ||||
| #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) | ||||
|  | ||||
| #Web GUI configuration | ||||
| receiver_name="[Callsign]" | ||||
| receiver_location="Budapest, Hungary" | ||||
| receiver_qra="JN97ML" | ||||
| receiver_asl=182 | ||||
| receiver_ant="Longwire" | ||||
| receiver_device="RTL-SDR" | ||||
| receiver_admin="localhost@localhost" | ||||
| receiver_gps=(47.000000,19.000000) | ||||
| photo_height=350 | ||||
| photo_title="Panorama of Budapest from Schönherz Zoltán Dormitory" | ||||
| photo_desc=""" | ||||
| You can add your own background photo and receiver information.<br /> | ||||
| Receiver is operated by: <a href="mailto:%[RX_ADMIN]">%[RX_ADMIN]</a><br/> | ||||
| Device: %[RX_DEVICE]<br /> | ||||
| Antenna: %[RX_ANT]<br /> | ||||
| Website: <a href="http://localhost" target="_blank">http://localhost</a> | ||||
| """ | ||||
|  | ||||
| #DSP/RX settings | ||||
| dsp_plugin="csdr" | ||||
| fft_fps=9 | ||||
| fft_size=4096 | ||||
| samp_rate = 250000 | ||||
| center_freq = 145525000 | ||||
| rf_gain = 5 | ||||
|  | ||||
| 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 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). | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								htdocs/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 318 B | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/font-expletus-sans/ExpletusSans-Medium.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										93
									
								
								htdocs/gfx/font-expletus-sans/OFL.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,93 @@ | ||||
| Copyright (c) 2011, Jasper de Waard (jasper@designtown.nl), | ||||
| with Reserved Font Name "Expletus Sans". | ||||
| This Font Software is licensed under the SIL Open Font License, Version 1.1. | ||||
| This license is copied below, and is also available with a FAQ at: | ||||
| http://scripts.sil.org/OFL | ||||
|  | ||||
|  | ||||
| ----------------------------------------------------------- | ||||
| SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | ||||
| ----------------------------------------------------------- | ||||
|  | ||||
| PREAMBLE | ||||
| The goals of the Open Font License (OFL) are to stimulate worldwide | ||||
| development of collaborative font projects, to support the font creation | ||||
| efforts of academic and linguistic communities, and to provide a free and | ||||
| open framework in which fonts may be shared and improved in partnership | ||||
| with others. | ||||
|  | ||||
| The OFL allows the licensed fonts to be used, studied, modified and | ||||
| redistributed freely as long as they are not sold by themselves. The | ||||
| fonts, including any derivative works, can be bundled, embedded,  | ||||
| redistributed and/or sold with any software provided that any reserved | ||||
| names are not used by derivative works. The fonts and derivatives, | ||||
| however, cannot be released under any other type of license. The | ||||
| requirement for fonts to remain under this license does not apply | ||||
| to any document created using the fonts or their derivatives. | ||||
|  | ||||
| DEFINITIONS | ||||
| "Font Software" refers to the set of files released by the Copyright | ||||
| Holder(s) under this license and clearly marked as such. This may | ||||
| include source files, build scripts and documentation. | ||||
|  | ||||
| "Reserved Font Name" refers to any names specified as such after the | ||||
| copyright statement(s). | ||||
|  | ||||
| "Original Version" refers to the collection of Font Software components as | ||||
| distributed by the Copyright Holder(s). | ||||
|  | ||||
| "Modified Version" refers to any derivative made by adding to, deleting, | ||||
| or substituting -- in part or in whole -- any of the components of the | ||||
| Original Version, by changing formats or by porting the Font Software to a | ||||
| new environment. | ||||
|  | ||||
| "Author" refers to any designer, engineer, programmer, technical | ||||
| writer or other person who contributed to the Font Software. | ||||
|  | ||||
| PERMISSION & CONDITIONS | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of the Font Software, to use, study, copy, merge, embed, modify, | ||||
| redistribute, and sell modified and unmodified copies of the Font | ||||
| Software, subject to the following conditions: | ||||
|  | ||||
| 1) Neither the Font Software nor any of its individual components, | ||||
| in Original or Modified Versions, may be sold by itself. | ||||
|  | ||||
| 2) Original or Modified Versions of the Font Software may be bundled, | ||||
| redistributed and/or sold with any software, provided that each copy | ||||
| contains the above copyright notice and this license. These can be | ||||
| included either as stand-alone text files, human-readable headers or | ||||
| in the appropriate machine-readable metadata fields within text or | ||||
| binary files as long as those fields can be easily viewed by the user. | ||||
|  | ||||
| 3) No Modified Version of the Font Software may use the Reserved Font | ||||
| Name(s) unless explicit written permission is granted by the corresponding | ||||
| Copyright Holder. This restriction only applies to the primary font name as | ||||
| presented to the users. | ||||
|  | ||||
| 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | ||||
| Software shall not be used to promote, endorse or advertise any | ||||
| Modified Version, except to acknowledge the contribution(s) of the | ||||
| Copyright Holder(s) and the Author(s) or with their explicit written | ||||
| permission. | ||||
|  | ||||
| 5) The Font Software, modified or unmodified, in part or in whole, | ||||
| must be distributed entirely under this license, and must not be | ||||
| distributed under any other license. The requirement for fonts to | ||||
| remain under this license does not apply to any document created | ||||
| using the Font Software. | ||||
|  | ||||
| TERMINATION | ||||
| This license becomes null and void if any of the above conditions are | ||||
| not met. | ||||
|  | ||||
| DISCLAIMER | ||||
| THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | ||||
| OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | ||||
| COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||||
| INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | ||||
| DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | ||||
| OTHER DEALINGS IN THE FONT SOFTWARE. | ||||
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-avatar-background.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 459 B | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-avatar.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 742 B | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-background-cool-blue.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 90 KiB | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-background-lingrad.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 679 B | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-logo-big.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 44 KiB | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-rx-details-arrow-up.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 518 B | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-rx-details-arrow.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 505 B | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-scale-background.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 64 KiB | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-top-logo.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-top-photo.jpg
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 125 KiB | 
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/webrx-ha5kfu-top-logo.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										93
									
								
								htdocs/index.wrx
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,93 @@ | ||||
| <!DOCTYPE HTML> | ||||
| <!-- | ||||
| OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu> | ||||
|  | ||||
| This file is part of OpenWebRX. | ||||
|  | ||||
|     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, | ||||
|     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. | ||||
|  | ||||
|     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> | ||||
| 		<script type="text/javascript"> | ||||
| 			//Local variables | ||||
| 			client_id="%[CLIENT_ID]"; | ||||
| 			ws_url="%[WS_URL]"; | ||||
| 			rx_photo_height=%[RX_PHOTO_HEIGHT]; | ||||
| 		</script> | ||||
| 		<script src="openwebrx.js"></script> | ||||
| 		<link rel="stylesheet" type="text/css" href="openwebrx.css" /> | ||||
| 		<meta charset="utf-8"> | ||||
| 	</head> | ||||
| 	<body onload="openwebrx_init();"> | ||||
| <div id="webrx-page-container"> | ||||
| 	<div id="webrx-top-container"> | ||||
| 		<div id="webrx-top-photo-clip">  | ||||
| 			<img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>  | ||||
| 			<div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div> | ||||
| 			<div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div> | ||||
| 		</div> | ||||
| 		<div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div> | ||||
| 		<div id="webrx-top-bar" class="webrx-top-bar-parts"> | ||||
| 			<a href="http://openwebrx.org/" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a> | ||||
| 			<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a> | ||||
| 			<img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/> | ||||
| 			<img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/> | ||||
| 			<div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div> | ||||
| 			<div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div> | ||||
| 			<div id="openwebrx-rx-details-arrow"> | ||||
| 				<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> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div id="webrx-main-container"> | ||||
| 			<div id="openwebrx-scale-container"> | ||||
| 				<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas> | ||||
| 			</div> | ||||
| 			<div id="webrx-canvas-container"> | ||||
| 				<div id="openwebrx-phantom-canvas"></div> | ||||
| 				<!-- 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 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>--> | ||||
| 					<div class="openwebrx-button" onclick="demodulator_analog_replace('nfm');">FM</div> | ||||
| 					<div class="openwebrx-button" onclick="demodulator_analog_replace('am');">AM</div>					 | ||||
| 					<div class="openwebrx-button" onclick="demodulator_analog_replace('lsb');">LSB</div> | ||||
| 					<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> | ||||
| 						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" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="background-color: Red;"> | ||||
| 					<span style="font-size: 15pt; font-weight: bold;">Under construction</span> | ||||
| 					<br />We're working on the code right now, so the application might fail. | ||||
| 				</div> | ||||
| 			</div> | ||||
| 	</div> | ||||
| </div> | ||||
| 	</body> | ||||
| </html> | ||||
							
								
								
									
										433
									
								
								htdocs/openwebrx.css
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,433 @@ | ||||
| /* | ||||
| OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu> | ||||
|  | ||||
| This file is part of OpenWebRX. | ||||
|  | ||||
|     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, | ||||
|     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. | ||||
|  | ||||
|     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 | ||||
| { | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
|    height: 100%; | ||||
| 	font-family: "DejaVu Sans", Verdana, Geneva, sans-serif; | ||||
| 	overflow: hidden; | ||||
| } | ||||
|  | ||||
| #webrx-top-container | ||||
| { | ||||
| 	position: relative; | ||||
| 	z-index:1000; | ||||
| } | ||||
|  | ||||
| .webrx-top-bar-parts | ||||
| { | ||||
| 	position: absolute; | ||||
| 	top: 0px; | ||||
| 	left: 0px; | ||||
| 	width:100%; | ||||
| 	height:67px; | ||||
| } | ||||
|  | ||||
| #webrx-top-bar-background | ||||
| { | ||||
| 	background-color: #808080; | ||||
| 	opacity: 0.15; | ||||
| 	filter:alpha(opacity=15); | ||||
| } | ||||
|  | ||||
| #webrx-top-bar | ||||
| { | ||||
| 	margin:0; | ||||
| 	padding:0; | ||||
| } | ||||
|  | ||||
|  | ||||
| #webrx-top-logo | ||||
| { | ||||
| 	position: absolute; | ||||
| 	top: 12px; | ||||
| 	left: 15px; | ||||
| } | ||||
|  | ||||
| #webrx-ha5kfu-top-logo | ||||
| { | ||||
| 	position: absolute; | ||||
| 	top: 19px; | ||||
| 	right: 15px; | ||||
| } | ||||
|  | ||||
| #webrx-top-photo | ||||
| { | ||||
| 	width: 100%; | ||||
| 	display: block; | ||||
| } | ||||
|  | ||||
| #webrx-rx-avatar-background | ||||
| { | ||||
| 	cursor:pointer; | ||||
| 	position: absolute; | ||||
| 	left: 285px; | ||||
| 	top: 6px; | ||||
| } | ||||
|  | ||||
| #webrx-rx-avatar | ||||
| { | ||||
| 	cursor:pointer; | ||||
| 	position: absolute; | ||||
| 	left: 289px; | ||||
| 	top: 10px; | ||||
| 	width: 46px; | ||||
| 	height: 46px; | ||||
| } | ||||
|  | ||||
| #webrx-top-photo-clip | ||||
| { | ||||
| 	max-height: 350px; | ||||
| 	overflow: hidden; | ||||
| 	position: relative; | ||||
| } | ||||
|  | ||||
| /*#webrx-bottom-bar | ||||
| { | ||||
| 	position: absolute; | ||||
| 	bottom: 0px; | ||||
| 	width: 100%; | ||||
| 	height: 117px; | ||||
| 	background-image:url(gfx/webrx-bottom-bar.png); | ||||
| }*/ | ||||
|  | ||||
| #webrx-page-container | ||||
| { | ||||
|    min-height:100%; | ||||
|    position:relative; | ||||
| } | ||||
|  | ||||
| /*#webrx-photo-gradient-left | ||||
| { | ||||
| 	position: absolute; | ||||
| 	bottom: 0px; | ||||
| 	left: 0px; | ||||
| 	background-image:url(gfx/webrx-photo-gradient-corner.png);	 | ||||
| 	width: 59px; | ||||
| 	height: 92px; | ||||
|  | ||||
| } | ||||
|  | ||||
| #webrx-photo-gradient-middle | ||||
| { | ||||
| 	position: absolute; | ||||
| 	bottom: 0px; | ||||
| 	left: 59px; | ||||
| 	right: 59px; | ||||
| 	height: 92px; | ||||
| 	background-image:url(gfx/webrx-photo-gradient-middle.png);	 | ||||
| } | ||||
|  | ||||
| #webrx-photo-gradient-right | ||||
| { | ||||
| 	position: absolute; | ||||
| 	bottom: 0px; | ||||
| 	right: 0px; | ||||
| 	background-image:url(gfx/webrx-photo-gradient-corner.png);	 | ||||
| 	width: 59px; | ||||
| 	height: 92px; | ||||
|    -webkit-transform:scaleX(-1); | ||||
|    -moz-transform:scaleX(-1); | ||||
|    -ms-transform:scaleX(-1); | ||||
|    -o-transform:scaleX(-1); | ||||
|    transform:scaleX(-1); | ||||
| }*/ | ||||
|  | ||||
| #webrx-rx-photo-title | ||||
| { | ||||
| 	position: absolute; | ||||
| 	left: 15px; | ||||
| 	top: 78px; | ||||
| 	color: White; | ||||
| 	font-size: 16pt; | ||||
| 	text-shadow: 1px 1px 4px #444; | ||||
| 	opacity: 1; | ||||
| } | ||||
|  | ||||
| #webrx-rx-photo-desc | ||||
| { | ||||
| 	position: absolute; | ||||
| 	left: 15px; | ||||
| 	top: 109px; | ||||
| 	color: White; | ||||
| 	font-size: 10pt; | ||||
| 	font-weight: bold; | ||||
| 	text-shadow: 0px 0px 6px #444; | ||||
| 	opacity: 1; | ||||
| 	line-height: 1.5em; | ||||
| } | ||||
|  | ||||
| #webrx-rx-photo-desc a | ||||
| { | ||||
| 	color: #5ca8ff; | ||||
| 	text-shadow: none; | ||||
| } | ||||
|  | ||||
| #webrx-rx-title | ||||
| { | ||||
| 	white-space:nowrap; | ||||
| 	overflow: hidden; | ||||
| 	cursor:pointer; | ||||
| 	position: absolute; | ||||
| 	left: 350px; | ||||
| 	top: 13px; | ||||
| 	font-family: "DejaVu Sans", Verdana, Geneva, sans-serif; | ||||
| 	color: #909090; | ||||
| 	font-size: 11pt; | ||||
| 	font-weight: bold; | ||||
| } | ||||
|  | ||||
| #webrx-rx-desc | ||||
| { | ||||
| 	white-space:nowrap; | ||||
| 	overflow: hidden; | ||||
| 	cursor:pointer; | ||||
| 	font-size: 10pt; | ||||
| 	color: #909090; | ||||
| 	position: absolute; | ||||
| 	left: 350px; | ||||
| 	top: 34px; | ||||
| } | ||||
|  | ||||
| #webrx-rx-desc a | ||||
| { | ||||
| 	color: #909090; | ||||
| 	/*text-decoration: none;*/ | ||||
| } | ||||
|  | ||||
| #openwebrx-rx-details-arrow | ||||
| { | ||||
| 	cursor:pointer; | ||||
| 	position: absolute; | ||||
| 	left: 470px; | ||||
| 	top: 51px; | ||||
| } | ||||
|  | ||||
| #openwebrx-rx-details-arrow a | ||||
| { | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| } | ||||
|  | ||||
| #openwebrx-rx-details-arrow-down | ||||
| { | ||||
| 	display:none; | ||||
| } | ||||
|  | ||||
| /*canvas#waterfall-canvas | ||||
| { | ||||
| 	border-style: none; | ||||
| 	border-width: 1px; | ||||
| 	height: 150px; | ||||
| 	width: 100%; | ||||
| }*/ | ||||
|  | ||||
| #openwebrx-scale-container | ||||
| { | ||||
| 	height: 47px; | ||||
| 	background-image: url("gfx/openwebrx-scale-background.png"); | ||||
| 	background-repeat: repeat-x; | ||||
| 	overflow: hidden; | ||||
| 	z-index:1000; | ||||
| 	position: relative; | ||||
| } | ||||
|  | ||||
| #webrx-canvas-container | ||||
| { | ||||
| 	/*background-image:url('gfx/openwebrx-blank-background-1.jpg');*/ | ||||
| 	position: relative; | ||||
| 	height: 2000px; | ||||
| 	overflow-y: scroll; | ||||
| 	overflow-x: hidden; | ||||
| 	/*background-color: #646464;*/ | ||||
| 	/*background-image: -webkit-linear-gradient(top, rgba(247,247,247,1) 0%, rgba(0,0,0,1) 100%);*/ | ||||
| 	background-image: url('gfx/openwebrx-background-cool-blue.png'); | ||||
| 	background-repeat: no-repeat; | ||||
| 	background-color: #1e5f7f; | ||||
| 	cursor: crosshair; | ||||
| } | ||||
|  | ||||
| #webrx-canvas-container canvas | ||||
| { | ||||
| 	position: absolute; | ||||
| 	border-style: none; | ||||
| } | ||||
|  | ||||
| #openwebrx-phantom-canvas | ||||
| { | ||||
| 	position: absolute; | ||||
| 	width: 0px; | ||||
| 	height: 0px; | ||||
| } | ||||
|  | ||||
| /*#openwebrx-canvas-gradient-background | ||||
| { | ||||
| 	overflow: hidden; | ||||
| 	width: 100%; | ||||
| 	height: 396px; | ||||
| }*/ | ||||
|  | ||||
| /*#webrx-debugdiv | ||||
| { | ||||
| 	font-size: 10pt; | ||||
| 	/*overflow-y:scroll;*/ | ||||
| /*}*/ | ||||
|  | ||||
| #webrx-main-container | ||||
| { | ||||
| 	position: relative; | ||||
| 	width: 100%; | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| } | ||||
|  | ||||
| .webrx-error | ||||
| { | ||||
| 	font-weight: bold; | ||||
| 	color: #ff6262; | ||||
| } | ||||
|  | ||||
| #openwebrx-problems span | ||||
| { | ||||
| 	background: #ff6262; | ||||
| 	padding: 3px; | ||||
| 	font-size: 8pt; | ||||
| 	color: white; | ||||
| 	font-weight: bold; | ||||
| 	border-radius: 4px; | ||||
| 	-moz-border-radius: 4px; | ||||
| 	margin: 0px 2px 0px 2px; | ||||
| } | ||||
|  | ||||
| /*#webrx-freq-show | ||||
| { | ||||
| 	visibility: hidden; | ||||
| 	position: absolute; | ||||
| 	top: 0px; | ||||
| 	left: 0px; | ||||
| 	padding: 5px; | ||||
| 	font-weight: bold; | ||||
| 	border-radius: 10px; | ||||
| 	-moz-border-radius: 10px; | ||||
| 	background-color: #999999; | ||||
| 	color: White; | ||||
| 	z-index:9999; /*should be higher? | ||||
| 	 | ||||
| }*/ | ||||
|  | ||||
| /* removed non-free fonts like that: */ | ||||
| /*@font-face { | ||||
|     font-family: 'unibody_8_pro_regregular'; | ||||
|     src: url('gfx/unibody8pro-regular-webfont.eot'); | ||||
|     src: url('gfx/unibody8pro-regular-webfont.ttf'); | ||||
|     font-weight: normal; | ||||
|     font-style: normal; | ||||
| }*/ | ||||
|  | ||||
| @font-face { | ||||
|     font-family: 'expletus-sans-medium'; | ||||
|     src: url('gfx/font-expletus-sans/ExpletusSans-Medium.ttf'); | ||||
|     font-weight: normal; | ||||
|     font-style: normal; | ||||
| } | ||||
|  | ||||
| #webrx-actual-freq | ||||
| { | ||||
| 	width: 100%; | ||||
| 	text-align: left; | ||||
| 	font-size: 16pt; | ||||
| 	font-family: 'expletus-sans-medium'; | ||||
| 	padding: 0; | ||||
| 	margin: 0; | ||||
| 	line-height:22px; | ||||
| 	 | ||||
| } | ||||
|  | ||||
| #webrx-mouse-freq | ||||
| { | ||||
| 	width: 100%; | ||||
| 	text-align: left; | ||||
| 	font-size: 10pt; | ||||
| 	color: #AAA; | ||||
| 	font-family: 'expletus-sans-medium'; | ||||
| 	margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| .openwebrx-panel | ||||
| { | ||||
| 	visibility: hidden; | ||||
| 	background-color: #575757; | ||||
| 	padding: 10px; | ||||
| 	color: white; | ||||
| 	position: fixed; | ||||
| 	font-size: 10pt; | ||||
| 	border-radius: 15px; | ||||
| 	-moz-border-radius: 15px; | ||||
| } | ||||
|  | ||||
| .openwebrx-panel a | ||||
| { | ||||
| 	color: #5ca8ff; | ||||
| 	text-shadow: none; | ||||
| } | ||||
|  | ||||
| .openwebrx-panel-inner | ||||
| { | ||||
| 	overflow-y: auto; | ||||
| 	overflow-x: hidden; | ||||
| 	height: 100%; | ||||
| } | ||||
|  | ||||
| .openwebrx-button | ||||
| { | ||||
| 	background-color: #373737; | ||||
| 	padding: 5px; | ||||
| 	border-radius: 5px; | ||||
| 	-moz-border-radius: 5px; | ||||
| 	color: White; | ||||
| 	font-weight: bold; | ||||
| 	width: auto; | ||||
| 	float: left; | ||||
| 	margin-right: 5px; | ||||
| 	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% ); | ||||
| } | ||||
|  | ||||
| .openwebrx-button:hover | ||||
| { | ||||
| 	/*background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0	, #3F3F3F), color-stop(1, #777777) ); | ||||
| 	background:-moz-linear-gradient( center top, #373737 5%, #4F4F4F 100% );*/ | ||||
| 	background: #474747; | ||||
| 	color: #FFFF50; | ||||
| } | ||||
|  | ||||
| .openwebrx-button:active  | ||||
| { | ||||
| 	background: #777777; | ||||
| 	color: #FFFF50;	 | ||||
| } | ||||
|  | ||||
| #openwebrx-client-log-title  | ||||
| { | ||||
| 	margin-bottom: 5px; | ||||
| 	font-weight: bold; | ||||
| } | ||||
							
								
								
									
										1634
									
								
								htdocs/openwebrx.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										1538
									
								
								htdocs/openwebrx.js~
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										93
									
								
								htdocs/upgrade.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,93 @@ | ||||
| <html> | ||||
| <!-- | ||||
| OpenWebRX (c) Copyright 2013 Andras Retzler <ha7ilm@sdr.hu> | ||||
|  | ||||
| This file is part of OpenWebRX. | ||||
|  | ||||
|     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, | ||||
|     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. | ||||
|  | ||||
|     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> | ||||
| 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); } | ||||
| </script> | ||||
|  | ||||
| </head> | ||||
| <body> | ||||
|  | ||||
| <div class="frame"> | ||||
| 	<img class="logo" src="gfx/openwebrx-logo-big.png" style="height: 60px;"/> | ||||
| 	<div class="panel"> | ||||
| 		Only the latest <span class="browser">Google Chrome</span> browser is supported at the moment.<br/> | ||||
| 		Please <a href="http://chrome.google.com/">download and install Google Chrome.</a><br /> | ||||
| 		<div class="alt"> | ||||
| 			Alternatively, you may proceed to OpenWebRX, but it's not supposed to work as expected. <br /> | ||||
| 			<a href="/?unsupported">Click here</a> if you still want to try OpenWebRX.</a> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </body> | ||||
| </html> | ||||
|  | ||||
							
								
								
									
										378
									
								
								openwebrx.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,378 @@ | ||||
|  | ||||
| """ | ||||
| OpenWebRX: open-source web based SDR for everyone! | ||||
|  | ||||
| This file is part of OpenWebRX. | ||||
|  | ||||
|     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, | ||||
|     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. | ||||
|  | ||||
|     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> | ||||
|  | ||||
| """ | ||||
|  | ||||
| # http://www.codeproject.com/Articles/462525/Simple-HTTP-Server-and-Client-in-Python | ||||
| # some ideas are used from the artice above | ||||
|  | ||||
|  | ||||
| import os | ||||
| import code | ||||
| import importlib | ||||
| import plugins | ||||
| import plugins.dsp | ||||
| import thread | ||||
| import time | ||||
| import subprocess | ||||
| import os  | ||||
| from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer | ||||
| from SocketServer import ThreadingMixIn | ||||
| import fcntl | ||||
| import time | ||||
| import md5 | ||||
| import random | ||||
| import threading | ||||
| import dl | ||||
| import sys | ||||
| import traceback | ||||
| from collections import namedtuple | ||||
| import Queue | ||||
| import ctypes | ||||
|  | ||||
| #import rtl_mus | ||||
| import rxws | ||||
| import uuid | ||||
| import config_webrx as cfg | ||||
|  | ||||
| def import_all_plugins(directory): | ||||
| 	for subdir in os.listdir(directory): | ||||
| 		if os.path.isdir(directory+subdir) and not subdir[0]=="_": | ||||
| 			exact_path=directory+subdir+"/plugin.py" | ||||
| 			if os.path.isfile(exact_path): | ||||
| 				importname=(directory+subdir+"/plugin").replace("/",".") | ||||
| 				print "[openwebrx-import] Found plugin:",importname | ||||
| 				importlib.import_module(importname) | ||||
|  | ||||
| class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer): | ||||
|     pass  | ||||
|  | ||||
| def main(): | ||||
| 	global clients | ||||
| 	global clients_mutex | ||||
|  | ||||
| 	print | ||||
| 	print "OpenWebRX - Open Source Web Based SDR for Everyone  | for license see LICENSE file in the package" | ||||
| 	print "_________________________________________________________________________________________________" | ||||
| 	print  | ||||
| 	print "Author contact info:    Andras Retzler, HA7ILM <randras@sdr.hu>" | ||||
| 	print  | ||||
|  | ||||
| 	#Load plugins | ||||
| 	import_all_plugins("plugins/dsp/") | ||||
|  | ||||
| 	#Change process name to "openwebrx" (to be seen in ps) | ||||
| 	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, "openwebrx", 0, 0, 0) | ||||
| 				break | ||||
| 	except: | ||||
| 		pass | ||||
|  | ||||
| 	#Start rtl thread | ||||
| 	if cfg.start_rtl_thread: | ||||
| 		rtl_thread=threading.Thread(target = lambda:subprocess.Popen(cfg.start_rtl_command, shell=True), args=()) | ||||
| 		rtl_thread.start() | ||||
| 		print "[openwebrx-main] Started rtl thread: "+cfg.start_rtl_command | ||||
|  | ||||
| 	#Run rtl_mus.py in a different OS thread | ||||
| 	rtl_mus_thread=threading.Thread(target = lambda:subprocess.Popen("python 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	 | ||||
|  | ||||
| 	#Initialize clients | ||||
| 	clients=[] | ||||
| 	clients_mutex=threading.Lock() | ||||
|  | ||||
| 	#Start spectrum thread | ||||
| 	print "[openwebrx-main] Starting spectrum thread." | ||||
| 	spectrum_thread=threading.Thread(target = spectrum_thread_function, args = ()) | ||||
| 	spectrum_thread.start() | ||||
| 	 | ||||
| 	#threading.Thread(target = measure_thread_function, args = ()).start() | ||||
| 	 | ||||
| 	#Start HTTP thread | ||||
| 	httpd = MultiThreadHTTPServer(('', cfg.web_port), WebRXHandler) | ||||
| 	print('[openwebrx-main] Starting HTTP server.') | ||||
| 	httpd.serve_forever() | ||||
|  | ||||
|  | ||||
| # This is a debug function below: | ||||
| measure_value=0 | ||||
| def measure_thread_function(): | ||||
| 	global measure_value | ||||
| 	while True:	 | ||||
| 		print "[openwebrx-measure] value is",measure_value | ||||
| 		measure_value=0 | ||||
| 		time.sleep(1) | ||||
|  | ||||
|  | ||||
| def spectrum_thread_function(): | ||||
| 	global clients_mutex | ||||
| 	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) | ||||
| 	sleep_sec=0.87/cfg.fft_fps | ||||
| 	print "[openwebrx-spectrum] Spectrum thread initialized successfully. Thread id:", ctypes.CDLL('/lib/i386-linux-gnu/libc.so.6').syscall(224) | ||||
| 	dsp.start() | ||||
| 	print "[openwebrx-spectrum] Spectrum thread started."  | ||||
| 	while True: | ||||
| 		data=dsp.read(cfg.fft_size*4) | ||||
| 		#print "gotcha",len(data),"bytes of spectrum data via spectrum_thread_function()" | ||||
| 		clients_mutex.acquire() | ||||
| 		for i in range(0,len(clients)): | ||||
| 			if (clients[i].ws_started): | ||||
| 				if clients[i].spectrum_queue.full(): | ||||
| 					close_client(i, False) | ||||
| 				else: | ||||
| 					clients[i].spectrum_queue.put([data]) # add new string by "reference" to all clients | ||||
| 		clients_mutex.release() | ||||
| 	 | ||||
| def get_client_by_id(client_id, use_mutex=True): | ||||
| 	global clients_mutex | ||||
| 	global clients | ||||
| 	output=-1 | ||||
| 	if use_mutex: clients_mutex.acquire() | ||||
| 	for i in range(0,len(clients)): | ||||
| 		if(clients[i].id==client_id): | ||||
| 			output=i | ||||
| 			break | ||||
| 	if use_mutex: clients_mutex.release() | ||||
| 	if output==-1: | ||||
| 		raise ClientNotFoundException | ||||
| 	else: | ||||
| 		return output | ||||
|  | ||||
| def log_client(client, what): | ||||
| 	print "[openwebrx-httpd] client {0}#{1} :: {2}".format(client.ip,client.id,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() | ||||
| 	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: | ||||
| 			close_client(i, False) | ||||
| 			correction+=1 | ||||
| 	clients_mutex.release() | ||||
|  | ||||
| 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")	 | ||||
| 	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 | ||||
| 	clients_mutex.acquire() | ||||
| 	clients.append(new_client) | ||||
| 	log_client(new_client,"client added. Clients now: {0}".format(len(clients))) | ||||
| 	clients_mutex.release() | ||||
| 	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() | ||||
| 	del clients[i] | ||||
| 	if use_mutex: clients_mutex.release() | ||||
| 	 | ||||
| class WebRXHandler(BaseHTTPRequestHandler):     | ||||
| 	def proc_read_thread(): | ||||
| 		pass | ||||
|  | ||||
| 	def do_GET(self): | ||||
| 		global dsp_plugin | ||||
| 		rootdir = 'htdocs'  | ||||
| 		self.path=self.path.replace("..","") | ||||
| 		path_temp_parts=self.path.split("?") | ||||
| 		self.path=path_temp_parts[0] | ||||
| 		request_param=path_temp_parts[1] if(len(path_temp_parts)>1) else ""  | ||||
| 		try: | ||||
| 			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[:4]=="/ws/": | ||||
| 				try: | ||||
| 					# ========= WebSocket handshake  ========= | ||||
| 					try:				 | ||||
| 						rxws.handshake(self) | ||||
| 						clients_mutex.acquire()				 | ||||
| 						client_i=get_client_by_id(self.path[4:], False) | ||||
| 						myclient=clients[client_i] | ||||
| 						clients_mutex.release() | ||||
| 					except rxws.WebSocketException: | ||||
| 						self.send_error(400, 'Bad request.') | ||||
| 						return | ||||
| 					except ClientNotFoundException: | ||||
| 						self.send_error(400, 'Bad request.') | ||||
| 						return | ||||
|  | ||||
| 					# ========= Client handshake ========= | ||||
| 					rxws.send(self, "CLIENT DE SERVER openwebrx.py") | ||||
| 					client_ans=rxws.recv(self, True) | ||||
| 					if client_ans[:16]!="SERVER DE CLIENT": | ||||
| 						rxws.send("ERR Bad answer.") | ||||
| 						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)) | ||||
|  | ||||
| 					# ========= Initialize DSP ========= | ||||
| 					dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() | ||||
| 					dsp.set_samp_rate(cfg.samp_rate) | ||||
| 					dsp.set_demodulator("nfm") | ||||
| 					dsp.set_offset_freq(0) | ||||
| 					dsp.set_bpf(-4000,4000) | ||||
| 					dsp.start() | ||||
| 					 | ||||
| 					while True: | ||||
| 						# ========= send audio ========= | ||||
| 						temp_audio_data=dsp.read(1024*8) | ||||
| 						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 ")  | ||||
| 							# (it seems GNU Radio exchanges the first and second part of the FFT output, we correct it) | ||||
|  | ||||
| 						# ========= process commands ========= | ||||
| 						while True: | ||||
| 							rdata=rxws.recv(self, False) | ||||
| 							if not rdata: break | ||||
| 							#try: | ||||
| 							elif rdata[:3]=="SET": | ||||
| 								print "[openwebrx-httpd:ws,%d] command: %s"%(client_i,rdata) | ||||
| 								pairs=rdata[4:].split(" ") | ||||
| 								bpf_set=False | ||||
| 								new_bpf=dsp.get_bpf() | ||||
| 								filter_limit=dsp.get_output_rate()/2 | ||||
| 								for pair in pairs: | ||||
| 									param_name, param_value = pair.split("=") | ||||
| 									if param_name == "low_cut" and -filter_limit <= float(param_value) <= filter_limit: | ||||
| 										bpf_set=True | ||||
| 										new_bpf[0]=param_value | ||||
| 									elif param_name == "high_cut" and -filter_limit <= float(param_value) <= filter_limit: | ||||
| 										bpf_set=True | ||||
| 										new_bpf[1]=param_value | ||||
| 									elif param_name == "offset_freq" and -cfg.samp_rate/2 <= float(param_value) <= cfg.samp_rate/2: | ||||
| 										dsp.set_offset_freq(param_value) | ||||
| 									elif param_name=="mod": | ||||
| 										dsp.stop() | ||||
| 										dsp.set_demodulator(param_value) | ||||
| 										dsp.start() | ||||
| 									else: | ||||
| 										print "[openwebrx-httpd:ws] invalid parameter" | ||||
| 								if bpf_set: | ||||
| 									dsp.set_bpf(*new_bpf) | ||||
| 								#code.interact(local=locals()) | ||||
| 				except: | ||||
| 					print "[openwebrx-httpd] exception happened at all" | ||||
| 					exc_type, exc_value, exc_traceback = sys.exc_info() | ||||
| 					if exc_value[0]==32: #"broken pipe", client disconnected | ||||
| 						pass | ||||
| 					elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected					 | ||||
| 						pass | ||||
| 					else:	 | ||||
| 						print "[openwebrx-httpd] error: ",exc_type,exc_value | ||||
| 						traceback.print_tb(exc_traceback) | ||||
| 				#delete disconnected client | ||||
| 				try: | ||||
| 					dsp.stop() | ||||
| 					del dsp | ||||
| 				except: | ||||
| 					pass | ||||
| 				clients_mutex.acquire() | ||||
| 				id_to_close=get_client_by_id(myclient.id,False) | ||||
| 				close_client(id_to_close,False) | ||||
| 				clients_mutex.release() | ||||
| 				return | ||||
| 			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>") | ||||
| 					return | ||||
| 				self.send_response(200) | ||||
| 				if(("wrx","html","htm").count(extension)): | ||||
| 					self.send_header('Content-type','text/html') | ||||
| 				elif(extension=="js"): | ||||
| 					self.send_header('Content-type','text/javascript') | ||||
| 				elif(extension=="css"): | ||||
| 					self.send_header('Content-type','text/css') | ||||
| 				self.end_headers() | ||||
| 				if extension == "wrx": | ||||
| 					replace_dictionary=( | ||||
| 						("%[RX_PHOTO_DESC]",cfg.photo_desc), | ||||
| 						("%[CLIENT_ID]",generate_client_id(self.client_address[0])), | ||||
| 						("%[WS_URL]","ws://"+cfg.server_hostname+":"+str(cfg.web_port)+"/ws/"), | ||||
| 						("%[RX_TITLE]",cfg.receiver_name), | ||||
| 						("%[RX_LOC]",cfg.receiver_location), | ||||
| 						("%[RX_QRA]",cfg.receiver_qra), | ||||
| 						("%[RX_ASL]",str(cfg.receiver_asl)), | ||||
| 						("%[RX_GPS]",str(cfg.receiver_gps[0])+","+str(cfg.receiver_gps[1])), | ||||
| 						("%[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) | ||||
| 					) | ||||
| 					for rule in replace_dictionary: | ||||
| 						while data.find(rule[0])!=-1: | ||||
| 							data=data.replace(rule[0],rule[1]) | ||||
| 				self.wfile.write(data) | ||||
| 				f.close() | ||||
| 			return | ||||
| 		except IOError: | ||||
| 			self.send_error(404, 'Invalid path.') | ||||
| 		except: | ||||
| 			exc_type, exc_value, exc_traceback = sys.exc_info() | ||||
| 			print "[openwebrx-httpd] exception happened (outside):", exc_type, exc_value | ||||
| 			traceback.print_tb(exc_traceback) | ||||
|  | ||||
| class ClientNotFoundException(Exception): | ||||
| 	pass | ||||
|  | ||||
| if __name__=="__main__": | ||||
| 	main() | ||||
|  | ||||
							
								
								
									
										0
									
								
								plugins/__init__.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								plugins/__init__.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										0
									
								
								plugins/dsp/__init__.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								plugins/dsp/__init__.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										0
									
								
								plugins/dsp/csdr/__init__.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								plugins/dsp/csdr/__init__.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										135
									
								
								plugins/dsp/csdr/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,135 @@ | ||||
| import subprocess | ||||
| import time | ||||
| import os | ||||
| import code | ||||
|  | ||||
| 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.fft_size = 1024 | ||||
| 		self.fft_fps = 5 | ||||
| 		self.offset_freq = 0 | ||||
| 		self.low_cut = -4000 | ||||
| 		self.high_cut = 4000 | ||||
| 		self.bpf_transition_bw = 300 #Hz, and this is a constant | ||||
| 		self.running = False | ||||
| 		chain_begin="nc localhost 4951 | csdr convert_u8_f | csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} 0.005 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 48000 | csdr fastagc_ff | csdr convert_f_i16", | ||||
| 			"am" :  chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_i16", | ||||
| 			"ssb" :  chain_begin + "csdr realpart_cf | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_i16", | ||||
| 			"fft" : "nc -vv localhost 4951 | csdr convert_u8_f | csdr fft_cc {fft_size} {fft_block_size} | csdr logpower_cf -70" | ||||
| 			} | ||||
| 		self.demodulator = "nfm" | ||||
| 		self.name = "csdr" | ||||
| 		try:	 | ||||
| 			subprocess.Popen("nc",stdout=subprocess.PIPE,stderr=subprocess.PIPE) | ||||
| 		except: | ||||
| 			print "[openwebrx-plugin:csdr] error: netcat not found, please install netcat!" | ||||
|  | ||||
| 	def set_samp_rate(self,samp_rate): | ||||
| 		#to change this, restart is required | ||||
| 		self.samp_rate=samp_rate | ||||
| 		self.decimation=1 | ||||
| 		while self.samp_rate/(self.decimation+1)>self.output_rate: | ||||
| 			self.decimation+=1 | ||||
| 		self.last_decimation=float(self.if_samp_rate())/self.output_rate | ||||
|  | ||||
| 	def if_samp_rate(self): | ||||
| 		return self.samp_rate/self.decimation | ||||
|  | ||||
| 	def get_name(self): | ||||
| 		return self.name | ||||
| 	 | ||||
| 	def get_output_rate(self): | ||||
| 		return self.output_rate | ||||
|  | ||||
|  | ||||
| 	def set_demodulator(self,demodulator): | ||||
| 		#to change this, restart is required | ||||
| 		self.demodulator=demodulator | ||||
|  | ||||
| 	def set_fft_size(self,fft_size): | ||||
| 		#to change this, restart is required | ||||
| 		self.fft_size=fft_size | ||||
|  | ||||
| 	def set_fft_fps(self,fft_fps): | ||||
| 		#to change this, restart is required | ||||
| 		self.fft_fps=fft_fps | ||||
| 	 | ||||
| 	def fft_block_size(self): | ||||
| 		return self.samp_rate/self.fft_fps | ||||
|  | ||||
| 	def set_offset_freq(self,offset_freq): | ||||
| 		self.offset_freq=offset_freq | ||||
| 		if self.running:  | ||||
| 			self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate)) | ||||
| 			self.shift_pipe_file.flush() | ||||
| 	 | ||||
| 	def set_bpf(self,low_cut,high_cut): | ||||
| 		self.low_cut=low_cut | ||||
| 		self.high_cut=high_cut | ||||
| 		if self.running:  | ||||
| 			self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) ) | ||||
| 			self.bpf_pipe_file.flush() | ||||
| 		 | ||||
| 	def get_bpf(self): | ||||
| 		return [self.low_cut, self.high_cut] | ||||
|  | ||||
| 	def mkfifo(self,path): | ||||
| 		try: | ||||
| 			os.unlink(path) | ||||
| 		except: | ||||
| 			pass | ||||
| 		os.mkfifo(path)	 | ||||
|  | ||||
| 	def start(self): | ||||
| 		command_base=self.chains[self.demodulator] | ||||
| 		 | ||||
| 		#create control pipes for csdr | ||||
| 		pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self)) | ||||
| 		self.bpf_pipe = self.shift_pipe = None | ||||
| 		if "{bpf_pipe}" in command_base: | ||||
| 			self.bpf_pipe=pipe_base_path+"bpf" | ||||
| 			self.mkfifo(self.bpf_pipe) | ||||
| 		if "{shift_pipe}" in command_base: | ||||
| 			self.shift_pipe=pipe_base_path+"shift" | ||||
| 			self.mkfifo(self.shift_pipe) | ||||
|  | ||||
| 		#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()) | ||||
| 		print "[openwebrx-dsp-plugin:csdr] Command =",command | ||||
| 		#code.interact(local=locals()) | ||||
| 		self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) | ||||
| 		self.running = True | ||||
|  | ||||
| 		#open control pipes for csdr and send initialization data | ||||
| 		if self.bpf_pipe != None:  | ||||
| 			self.bpf_pipe_file=open(self.bpf_pipe,"w") | ||||
| 			self.set_bpf(self.low_cut,self.high_cut) | ||||
| 		if self.shift_pipe != None:  | ||||
| 			self.shift_pipe_file=open(self.shift_pipe,"w") | ||||
| 			self.set_offset_freq(self.offset_freq) | ||||
|  | ||||
| 	def read(self,size): | ||||
| 		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) | ||||
| 		self.running = False | ||||
|  | ||||
| 	def restart(self): | ||||
| 		self.stop() | ||||
| 		self.start() | ||||
|  | ||||
| 	def __del__(self): | ||||
| 		self.stop() | ||||
| 		del(self.process) | ||||
| 	 | ||||
							
								
								
									
										
											BIN
										
									
								
								plugins/dsp/csdr/plugin.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										514
									
								
								rtl_mus.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,514 @@ | ||||
| ''' | ||||
| 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> | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 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/>. | ||||
|  | ||||
| ----- | ||||
|  | ||||
| 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 dl | ||||
|  | ||||
| import code | ||||
| import traceback | ||||
|  | ||||
| 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() | ||||
| 			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]) | ||||
|  | ||||
| 	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) | ||||
| 		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(250) | ||||
| 			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.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) | ||||
| 		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(16348) | ||||
| 		if cfg.watchdog_interval: | ||||
| 			watchdog_data_count+=16348 | ||||
| 		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 commands.empty() | ||||
|  | ||||
| 	def handle_write(self): | ||||
| 		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): | ||||
| 		global clients_mutex | ||||
| 		global clients | ||||
| 		clients_mutex.acquire() | ||||
| 		for i in range(0,len(clients)): | ||||
| 			if clients[i][0].ident==self.ident: | ||||
| 				try: | ||||
| 					self.socket.close() | ||||
| 				except: | ||||
| 					pass | ||||
| 				try: | ||||
| 					self.asyncore.close() | ||||
| 					del self.asyncore | ||||
| 				except: | ||||
| 					pass | ||||
| 				break | ||||
| 		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 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  | ||||
|  | ||||
| 	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 | ||||
|  | ||||
| 	# === 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() | ||||
							
								
								
									
										148
									
								
								rxws.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,148 @@ | ||||
| """ | ||||
| rxws: WebSocket methods implemented for OpenWebRX | ||||
|  | ||||
| This file is part of OpenWebRX. | ||||
|  | ||||
|     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, | ||||
|     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. | ||||
|  | ||||
|     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> | ||||
|  | ||||
| """ | ||||
|  | ||||
| import base64 | ||||
| import sha | ||||
| import select | ||||
|  | ||||
| class WebSocketException(Exception): | ||||
| 	pass | ||||
|  | ||||
| def handshake(myself): | ||||
| 	my_client_id=myself.path[4:] | ||||
| 	my_headers=myself.headers.items() | ||||
| 	my_header_keys=map(lambda x:x[0],my_headers) | ||||
| 	h_key_exists=lambda x:my_header_keys.count(x) | ||||
| 	h_value=lambda x:my_headers[my_header_keys.index(x)][1] | ||||
| 	#print "The Lambdas(tm)" | ||||
| 	#print h_key_exists("upgrade") | ||||
| 	#print h_value("upgrade") | ||||
| 	#print h_key_exists("sec-websocket-key") | ||||
| 	if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")): | ||||
| 		raise WebSocketException | ||||
| 	ws_key=h_value("sec-websocket-key") | ||||
| 	ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest()) | ||||
| 	#A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')] | ||||
| 	myself.connection.send("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n") | ||||
|  | ||||
| def get_header(size): | ||||
| 	#this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php | ||||
| 	ws_first_byte=0b10000010 # FIN=1, OP=2 | ||||
| 	if(size>125): | ||||
| 		ws_second_byte=126 # The following two bytes will indicate frame size | ||||
| 		extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP | ||||
| 	else: | ||||
| 		ws_second_byte=size | ||||
| 		#256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data] | ||||
| 		extended_size="" | ||||
| 	return chr(ws_first_byte)+chr(ws_second_byte)+extended_size | ||||
|  | ||||
| def code_payload(data, masking_key=""): | ||||
| 	# both encode or decode | ||||
| 	if masking_key=="": | ||||
| 		key = (61, 84, 35, 6) | ||||
| 	else: | ||||
| 		key = [ord(i) for i in masking_key] | ||||
| 	encoded="" | ||||
| 	for i in range(0,len(data)): | ||||
| 		encoded+=chr(ord(data[i])^key[i%4]) | ||||
| 	return encoded | ||||
|  | ||||
| def xxdg(data): | ||||
| 	output="" | ||||
| 	for i in range(0,len(data)/8): | ||||
| 		output+=xxd(data[i:i+8]) | ||||
| 		if i%2: output+="\n" | ||||
| 		else: output+="  " | ||||
| 	return output | ||||
| 		 | ||||
|  | ||||
| def xxd(data): | ||||
| 	#diagnostic purposes only | ||||
| 	output="" | ||||
| 	for d in data: | ||||
| 		output+=hex(ord(d))[2:].zfill(2)+" "  | ||||
| 	return output | ||||
|  | ||||
| def recv(myself, blocking=False, debug=False): | ||||
| 	bufsize=70000 | ||||
| 	myself.connection.setblocking(blocking) | ||||
| 	if debug: print "ws_recv begin" | ||||
| 	try: | ||||
| 		data=myself.connection.recv(6) | ||||
| 		#print "rxws.recv bytes:",xxd(data)	 | ||||
| 	except: | ||||
| 		if debug: print "ws_recv error"	 | ||||
| 		return "" | ||||
| 	if debug: print "ws_recv recved" | ||||
| 	if(len(data)==0): return "" | ||||
| 	fin=ord(data[0])&128!=0 | ||||
| 	is_text_frame=ord(data[0])&15==1 | ||||
| 	length=ord(data[1])&0x7f | ||||
| 	data+=myself.connection.recv(length) | ||||
| 	#print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data) | ||||
| 	has_one_byte_length=length<125 | ||||
| 	masked=ord(data[1])&0x80!=0 | ||||
| 	#print "len=", length, len(data)-2 | ||||
| 	#print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked) | ||||
| 	#print xxd(data) | ||||
| 	if fin and is_text_frame and has_one_byte_length: | ||||
| 		if masked: | ||||
| 			return code_payload(data[6:], data[2:6]) | ||||
| 		else: | ||||
| 			return data[2:] | ||||
|  | ||||
| #Useful links for ideas on WebSockets: | ||||
| #  http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side | ||||
| #  https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_server | ||||
| #  http://tools.ietf.org/html/rfc6455#section-5.2	 | ||||
|  | ||||
|  | ||||
| def flush(myself): | ||||
| 	lR,lW,lX = select.select([],[myself.connection,],[],60) | ||||
| 	 | ||||
|  | ||||
| def send(myself, data, begin_id="", debug=0): | ||||
| 	base_frame_size=35000 #could guess by MTU? | ||||
| 	debug=0 | ||||
| 	#try: | ||||
| 	while True: | ||||
| 		counter=0 | ||||
| 		from_end=len(data)-counter | ||||
| 		if from_end+len(begin_id)>base_frame_size: | ||||
| 			data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)] | ||||
| 			header=get_header(len(data_to_send)) | ||||
| 			flush(myself) | ||||
| 			myself.connection.send(header+data_to_send) | ||||
| 			if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header)) | ||||
| 		else: | ||||
| 			data_to_send=begin_id+data[counter:] | ||||
| 			header=get_header(len(data_to_send)) | ||||
| 			flush(myself) | ||||
| 			myself.connection.send(header+data_to_send) | ||||
| 			if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header)) | ||||
| 			#if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send) | ||||
| 			break | ||||
| 		counter+=base_frame_size-len(begin_id) | ||||
| 	#except: | ||||
| 	#	pass | ||||
							
								
								
									
										
											BIN
										
									
								
								screenshot
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 MiB | 
 ha7ilm
					ha7ilm