many fixes and new features like IMA ADPCM compression

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

View File

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