829 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			829 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
								 | 
							
								#!/usr/bin/env python3
							 | 
						||
| 
								 | 
							
								# -*- coding: utf-8 -*- 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import os 
							 | 
						||
| 
								 | 
							
								import tornado.httpserver
							 | 
						||
| 
								 | 
							
								import tornado.ioloop
							 | 
						||
| 
								 | 
							
								import tornado.web
							 | 
						||
| 
								 | 
							
								import tornado.websocket
							 | 
						||
| 
								 | 
							
								import alsaaudio
							 | 
						||
| 
								 | 
							
								import threading
							 | 
						||
| 
								 | 
							
								import time
							 | 
						||
| 
								 | 
							
								import numpy 
							 | 
						||
| 
								 | 
							
								import gc
							 | 
						||
| 
								 | 
							
								from opus.decoder import Decoder as OpusDecoder
							 | 
						||
| 
								 | 
							
								import datetime
							 | 
						||
| 
								 | 
							
								import configparser
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import Hamlib
							 | 
						||
| 
								 | 
							
								from rtlsdr import RtlSdr
							 | 
						||
| 
								 | 
							
								import numpy as np
							 | 
						||
| 
								 | 
							
								import math
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								############ Global variables ##################################
							 | 
						||
| 
								 | 
							
								CTRX=None
							 | 
						||
| 
								 | 
							
								config = configparser.ConfigParser()
							 | 
						||
| 
								 | 
							
								config.read('UHRR.conf')
							 | 
						||
| 
								 | 
							
								e="No"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								############ Global functions ##################################
							 | 
						||
| 
								 | 
							
								def writte_log(logmsg):
							 | 
						||
| 
								 | 
							
									logfile = open(config['SERVER']['log_file'],"w") 
							 | 
						||
| 
								 | 
							
									msg = str(datetime.datetime.now())+":"+str(logmsg)
							 | 
						||
| 
								 | 
							
									logfile.write(msg) 
							 | 
						||
| 
								 | 
							
									print(msg)
							 | 
						||
| 
								 | 
							
									logfile.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								############ BaseHandler tornado ##############
							 | 
						||
| 
								 | 
							
								class BaseHandler(tornado.web.RequestHandler):
							 | 
						||
| 
								 | 
							
									def get_current_user(self):
							 | 
						||
| 
								 | 
							
										return self.get_secure_cookie("user")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								############ Generate and send FFT from RTLSDR ##############
							 | 
						||
| 
								 | 
							
								is_rtlsdr_present = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
									FFTSIZE=4096
							 | 
						||
| 
								 | 
							
									nbBuffer=24
							 | 
						||
| 
								 | 
							
									nbsamples=nbBuffer/2*FFTSIZE
							 | 
						||
| 
								 | 
							
									ptime=nbsamples/int(config['PANADAPTER']['sample_rate'])
							 | 
						||
| 
								 | 
							
									sdr_windows = eval("np."+config['PANADAPTER']['fft_window']+ "(FFTSIZE)") 
							 | 
						||
| 
								 | 
							
									fftpaquetlen=int(FFTSIZE*8/2048)
							 | 
						||
| 
								 | 
							
									sdr = RtlSdr()
							 | 
						||
| 
								 | 
							
									sdr.sample_rate = int(config['PANADAPTER']['sample_rate']) # Hz
							 | 
						||
| 
								 | 
							
									sdr.center_freq = int(config['PANADAPTER']['center_freq']) # Hz
							 | 
						||
| 
								 | 
							
									sdr.freq_correction = int(config['PANADAPTER']['freq_correction']) # PPM
							 | 
						||
| 
								 | 
							
									sdr.gain = int(config['PANADAPTER']['gain']) #or 'auto'
							 | 
						||
| 
								 | 
							
								except:
							 | 
						||
| 
								 | 
							
									is_rtlsdr_present = False
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
								AudioPanaHandlerClients = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class loadFFTdata(threading.Thread):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def __init__(self):
							 | 
						||
| 
								 | 
							
										threading.Thread.__init__(self)
							 | 
						||
| 
								 | 
							
										self.get_log_power_spectrum_w = np.empty(FFTSIZE)
							 | 
						||
| 
								 | 
							
										for i in range(FFTSIZE):
							 | 
						||
| 
								 | 
							
											self.get_log_power_spectrum_w[i] = 0.5 * (1. - math.cos((2 * math.pi * i) / (FFTSIZE - 1)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def run(self):
							 | 
						||
| 
								 | 
							
										while True:
							 | 
						||
| 
								 | 
							
											time.sleep(ptime)
							 | 
						||
| 
								 | 
							
											self.getFFT_data()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
									def get_log_power_spectrum(self,data):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											pulse = 10
							 | 
						||
| 
								 | 
							
											rejected_count = 0
							 | 
						||
| 
								 | 
							
											power_spectrum = np.zeros(FFTSIZE)
							 | 
						||
| 
								 | 
							
											db_adjust = 20. * math.log10(FFTSIZE * 2 ** 15)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											# Time-domain analysis: Often we have long normal signals interrupted
							 | 
						||
| 
								 | 
							
											# by huge wide-band pulses that degrade our power spectrum average.
							 | 
						||
| 
								 | 
							
											# We find the "normal" signal level, by computing the median of the
							 | 
						||
| 
								 | 
							
											# absolute value.  We only do this for the first buffer of a chunk,
							 | 
						||
| 
								 | 
							
											# using the median for the remaining buffers in the chunk.
							 | 
						||
| 
								 | 
							
											# A "noise pulse" is a signal level greater than some threshold
							 | 
						||
| 
								 | 
							
											# times the median.  When such a pulse is found, we skip the current
							 | 
						||
| 
								 | 
							
											# buffer.  It would be better to blank out just the pulse, but that
							 | 
						||
| 
								 | 
							
											# would be more costly in CPU time.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											# Find the median abs value of first buffer to use for this chunk.
							 | 
						||
| 
								 | 
							
											td_median = np.median(np.abs(data[:FFTSIZE]))
							 | 
						||
| 
								 | 
							
											# Calculate our current threshold relative to measured median.
							 | 
						||
| 
								 | 
							
											td_threshold = pulse * td_median
							 | 
						||
| 
								 | 
							
											nbuf_taken = 0  # Actual number of buffers accumulated
							 | 
						||
| 
								 | 
							
											for ic in range(nbBuffer-1):
							 | 
						||
| 
								 | 
							
												start=ic * int(FFTSIZE/2)
							 | 
						||
| 
								 | 
							
												end=start+FFTSIZE
							 | 
						||
| 
								 | 
							
												td_segment = data[start:end]*sdr_windows
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												# remove the 0hz spike
							 | 
						||
| 
								 | 
							
												td_segment = np.subtract(td_segment, np.average(td_segment))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												td_max = np.amax(np.abs(td_segment))  # Do we have a noise pulse?
							 | 
						||
| 
								 | 
							
												if td_max < td_threshold:  # No, get pwr spectrum etc.
							 | 
						||
| 
								 | 
							
													# EXPERIMENTAL TAPERfd
							 | 
						||
| 
								 | 
							
													td_segment *= self.get_log_power_spectrum_w
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													fd_spectrum = np.fft.fft(td_segment)
							 | 
						||
| 
								 | 
							
													# Frequency-domain:
							 | 
						||
| 
								 | 
							
													# Rotate array to place 0 freq. in center.  (It was at left.)
							 | 
						||
| 
								 | 
							
													fd_spectrum_rot = np.fft.fftshift(fd_spectrum)
							 | 
						||
| 
								 | 
							
													# Compute the real-valued squared magnitude (ie power) and 
							 | 
						||
| 
								 | 
							
													# accumulate into pwr_acc.
							 | 
						||
| 
								 | 
							
													# fastest way to sum |z|**2 ??
							 | 
						||
| 
								 | 
							
													nbuf_taken += 1
							 | 
						||
| 
								 | 
							
													power_spectrum = power_spectrum + \
							 | 
						||
| 
								 | 
							
														np.real(fd_spectrum_rot * fd_spectrum_rot.conj())
							 | 
						||
| 
								 | 
							
												else:  # Yes, abort buffer.
							 | 
						||
| 
								 | 
							
													rejected_count += 1
							 | 
						||
| 
								 | 
							
													# if DEBUG: print "REJECT! %d" % self.rejected_count
							 | 
						||
| 
								 | 
							
											if nbuf_taken > 0:
							 | 
						||
| 
								 | 
							
												power_spectrum = power_spectrum / nbuf_taken  # normalize the sum.
							 | 
						||
| 
								 | 
							
											else:
							 | 
						||
| 
								 | 
							
												power_spectrum = np.ones(FFTSIZE)  # if no good buffers!
							 | 
						||
| 
								 | 
							
											# Convert to dB. Note log(0) = "-inf" in Numpy. It can happen if ADC 
							 | 
						||
| 
								 | 
							
											# isn't working right. Numpy issues a warning.
							 | 
						||
| 
								 | 
							
											log_power_spectrum = 10. * np.log10(power_spectrum)
							 | 
						||
| 
								 | 
							
											return log_power_spectrum - db_adjust  # max poss. signal = 0 dB
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def getFFT_data(self):
							 | 
						||
| 
								 | 
							
										samples = sdr.read_samples(nbsamples)
							 | 
						||
| 
								 | 
							
										samples = np.imag(samples) + 1j * np.real(samples)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										max_pow = -254
							 | 
						||
| 
								 | 
							
										min_pow = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										power = self.get_log_power_spectrum(samples)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										# search whole data set for maximum and minimum value
							 | 
						||
| 
								 | 
							
										for dat in power:
							 | 
						||
| 
								 | 
							
											if dat > max_pow:
							 | 
						||
| 
								 | 
							
												max_pow = dat
							 | 
						||
| 
								 | 
							
											elif dat < min_pow:
							 | 
						||
| 
								 | 
							
												min_pow = dat
							 | 
						||
| 
								 | 
							
												
							 | 
						||
| 
								 | 
							
										byteslist=bytearray()
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											for dat in power:
							 | 
						||
| 
								 | 
							
												try:
							 | 
						||
| 
								 | 
							
													byteslist.append(self.FFTmymap(dat, min_pow, max_pow, 0, 255))
							 | 
						||
| 
								 | 
							
												except (RuntimeError, TypeError, NameError):
							 | 
						||
| 
								 | 
							
													byteslist.append(255)
							 | 
						||
| 
								 | 
							
													pass
							 | 
						||
| 
								 | 
							
											byteslist+=bytearray((65280+int(min_pow)).to_bytes(2, byteorder="big"))
							 | 
						||
| 
								 | 
							
											byteslist+=bytearray((65280+int(max_pow)).to_bytes(2, byteorder="big")) 
							 | 
						||
| 
								 | 
							
											for c in AudioPanaHandlerClients:
							 | 
						||
| 
								 | 
							
												c.fftframes.append(bytes(byteslist))
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											return None
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def FFTmymap(self, x, in_min, in_max, out_min, out_max):
							 | 
						||
| 
								 | 
							
										ret=int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
							 | 
						||
| 
								 | 
							
										return ret
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class WS_panFFTHandler(tornado.websocket.WebSocketHandler):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									@tornado.gen.coroutine
							 | 
						||
| 
								 | 
							
									def sendFFT(self):
							 | 
						||
| 
								 | 
							
										global ptime, fftpaquetlen
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											while len(self.fftframes)>0:
							 | 
						||
| 
								 | 
							
												yield self.write_message(self.fftframes[0],binary=True)
							 | 
						||
| 
								 | 
							
												del self.fftframes[0] 
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											return None
							 | 
						||
| 
								 | 
							
										tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=ptime), self.sendFFT)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def open(self):
							 | 
						||
| 
								 | 
							
										global is_rtlsdr_present
							 | 
						||
| 
								 | 
							
										print('new connection on FFT socket, is_rtlsdr_present = '+str(is_rtlsdr_present))
							 | 
						||
| 
								 | 
							
										if self not in AudioPanaHandlerClients:
							 | 
						||
| 
								 | 
							
											AudioPanaHandlerClients.append(self)
							 | 
						||
| 
								 | 
							
										self.fftframes = []
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
									def on_message(self, data) :
							 | 
						||
| 
								 | 
							
										print(data)
							 | 
						||
| 
								 | 
							
										if str(data)=="ready":
							 | 
						||
| 
								 | 
							
											self.sendFFT()
							 | 
						||
| 
								 | 
							
										elif str(data)=="init":
							 | 
						||
| 
								 | 
							
											self.write_message("fftsr:"+str(config['PANADAPTER']['sample_rate']));
							 | 
						||
| 
								 | 
							
											self.write_message("fftsz:"+str(FFTSIZE));
							 | 
						||
| 
								 | 
							
											self.write_message("fftst");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def on_close(self):
							 | 
						||
| 
								 | 
							
										print('connection closed for FFT socket')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								############ websocket for send RX audio from TRX ##############
							 | 
						||
| 
								 | 
							
								flagWavstart = False
							 | 
						||
| 
								 | 
							
								AudioRXHandlerClients = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class loadWavdata(threading.Thread):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def __init__(self):
							 | 
						||
| 
								 | 
							
										global flagWavstart
							 | 
						||
| 
								 | 
							
										threading.Thread.__init__(self)
							 | 
						||
| 
								 | 
							
										#self.inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, channels=1, rate=8000, format=alsaaudio.PCM_FORMAT_FLOAT_LE, periodsize=256, device=config['AUDIO']['inputdevice'])
							 | 
						||
| 
								 | 
							
										self.inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, device=config['AUDIO']['inputdevice'])
							 | 
						||
| 
								 | 
							
										self.inp.setchannels(1)
							 | 
						||
| 
								 | 
							
										self.inp.setrate(8000)
							 | 
						||
| 
								 | 
							
										self.inp.setformat(alsaaudio.PCM_FORMAT_FLOAT_LE)
							 | 
						||
| 
								 | 
							
										self.inp.setperiodsize(256)
							 | 
						||
| 
								 | 
							
										print('recording...')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def run(self):
							 | 
						||
| 
								 | 
							
										global Wavframes, flagWavstart
							 | 
						||
| 
								 | 
							
										ret=b''
							 | 
						||
| 
								 | 
							
										while True:
							 | 
						||
| 
								 | 
							
											while not flagWavstart:
							 | 
						||
| 
								 | 
							
												time.sleep(0.5)
							 | 
						||
| 
								 | 
							
											l, ret = self.inp.read()
							 | 
						||
| 
								 | 
							
											if l > 0:
							 | 
						||
| 
								 | 
							
												for c in AudioRXHandlerClients:
							 | 
						||
| 
								 | 
							
													c.Wavframes.append(ret)
							 | 
						||
| 
								 | 
							
											else:
							 | 
						||
| 
								 | 
							
												print("overrun")
							 | 
						||
| 
								 | 
							
												time.sleep(0.01)
							 | 
						||
| 
								 | 
							
												
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class WS_AudioRXHandler(tornado.websocket.WebSocketHandler):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def open(self):
							 | 
						||
| 
								 | 
							
										self.set_nodelay(True)
							 | 
						||
| 
								 | 
							
										global flagWavstart
							 | 
						||
| 
								 | 
							
										if self not in AudioRXHandlerClients:
							 | 
						||
| 
								 | 
							
											AudioRXHandlerClients.append(self)
							 | 
						||
| 
								 | 
							
										self.Wavframes = []
							 | 
						||
| 
								 | 
							
										print('new connection on AudioRXHandler socket.')
							 | 
						||
| 
								 | 
							
										flagWavstart = True
							 | 
						||
| 
								 | 
							
										self.tailstream()
							 | 
						||
| 
								 | 
							
										self.set_nodelay(True)
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									@tornado.gen.coroutine
							 | 
						||
| 
								 | 
							
									def tailstream(self):
							 | 
						||
| 
								 | 
							
										while flagWavstart:
							 | 
						||
| 
								 | 
							
											while len(self.Wavframes)==0:
							 | 
						||
| 
								 | 
							
												yield tornado.gen.sleep(0.1)
							 | 
						||
| 
								 | 
							
											yield self.write_message(self.Wavframes[0],binary=True)
							 | 
						||
| 
								 | 
							
											del self.Wavframes[0] 
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
									def on_close(self):
							 | 
						||
| 
								 | 
							
										if self in AudioRXHandlerClients:
							 | 
						||
| 
								 | 
							
											AudioRXHandlerClients.remove(self)
							 | 
						||
| 
								 | 
							
										global flagWavstart
							 | 
						||
| 
								 | 
							
										print('connection closed for audioRX')
							 | 
						||
| 
								 | 
							
										if len(AudioRXHandlerClients)<=0:
							 | 
						||
| 
								 | 
							
											flagWavstart = False
							 | 
						||
| 
								 | 
							
										self.Wavframes = []
							 | 
						||
| 
								 | 
							
										gc.collect()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								############ websocket for control TX ##############
							 | 
						||
| 
								 | 
							
								last_AudioTXHandler_msg_time=0
							 | 
						||
| 
								 | 
							
								AudioTXHandlerClients = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class WS_AudioTXHandler(tornado.websocket.WebSocketHandler):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def stoppttontimeout(self):
							 | 
						||
| 
								 | 
							
										global last_AudioTXHandler_msg_time
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											if time.time() > last_AudioTXHandler_msg_time + 10:
							 | 
						||
| 
								 | 
							
												if self.ws_connection and CTRX.infos["PTT"]==True:
							 | 
						||
| 
								 | 
							
													CTRX.setPTT("false")
							 | 
						||
| 
								 | 
							
													print("stop ptt on timeout")
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											return None
							 | 
						||
| 
								 | 
							
										tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=1), self.stoppttontimeout)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def TX_init(self, msg) :
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										itrate, is_encoded, op_rate, op_frm_dur = [int(i) for i in msg.split(',')]
							 | 
						||
| 
								 | 
							
										self.is_encoded = is_encoded
							 | 
						||
| 
								 | 
							
										self.decoder = OpusDecoder(op_rate, 1)
							 | 
						||
| 
								 | 
							
										self.frame_size = op_frm_dur * op_rate
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										device = config['AUDIO']['outputdevice']
							 | 
						||
| 
								 | 
							
										self.inp = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK, channels=1, rate=itrate, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=2048, device=device)		
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									def open(self):
							 | 
						||
| 
								 | 
							
										global last_AudioTXHandler_msg_time, AudioTXHandlerClients
							 | 
						||
| 
								 | 
							
										if self not in AudioTXHandlerClients:
							 | 
						||
| 
								 | 
							
											AudioTXHandlerClients.append(self)
							 | 
						||
| 
								 | 
							
											print('new connection on AudioTXHandler socket.')
							 | 
						||
| 
								 | 
							
										last_AudioTXHandler_msg_time=time.time()
							 | 
						||
| 
								 | 
							
										self.stoppttontimeout()
							 | 
						||
| 
								 | 
							
										self.set_nodelay(True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def on_message(self, data) :
							 | 
						||
| 
								 | 
							
										global last_AudioTXHandler_msg_time
							 | 
						||
| 
								 | 
							
										last_AudioTXHandler_msg_time=time.time()
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										if str(data).startswith('m:') :
							 | 
						||
| 
								 | 
							
											self.TX_init(str(data[2:]))
							 | 
						||
| 
								 | 
							
										elif str(data).startswith('s:') :
							 | 
						||
| 
								 | 
							
											self.inp.close()
							 | 
						||
| 
								 | 
							
										else :
							 | 
						||
| 
								 | 
							
											if self.is_encoded :
							 | 
						||
| 
								 | 
							
												pcm = self.decoder.decode(data, self.frame_size, False)
							 | 
						||
| 
								 | 
							
												self.inp.write(pcm)
							 | 
						||
| 
								 | 
							
												gc.collect()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											else :
							 | 
						||
| 
								 | 
							
												self.inp.write(data)
							 | 
						||
| 
								 | 
							
												gc.collect()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def on_close(self):
							 | 
						||
| 
								 | 
							
										global AudioTXHandlerClients
							 | 
						||
| 
								 | 
							
										if(hasattr(self,"inp")):
							 | 
						||
| 
								 | 
							
											self.inp.close()
							 | 
						||
| 
								 | 
							
										if self in AudioTXHandlerClients:
							 | 
						||
| 
								 | 
							
											AudioTXHandlerClients.remove(self)
							 | 
						||
| 
								 | 
							
										if (not len(AudioTXHandlerClients)) and (CTRX.infos["PTT"]==True):
							 | 
						||
| 
								 | 
							
											CTRX.setPTT("false")
							 | 
						||
| 
								 | 
							
										print('connection closed for TX socket')		
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								############ websocket for control TRX ##############
							 | 
						||
| 
								 | 
							
								ControlTRXHandlerClients = []
							 | 
						||
| 
								 | 
							
								LastPing = time.time()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TRXRIG:
							 | 
						||
| 
								 | 
							
									def __init__(self):
							 | 
						||
| 
								 | 
							
										self.spoints = {"0":-54, "1":-48, "2":-42, "3":-36, "4":-30, "5":-24, "6":-18, "7":-12, "8":-6, "9":0, "10":10, "20":20, "30":30, "40":40, "50":50, "60":60}
							 | 
						||
| 
								 | 
							
										self.infos = {}
							 | 
						||
| 
								 | 
							
										self.infos["PTT"]=False
							 | 
						||
| 
								 | 
							
										self.infos["powerstat"]=False
							 | 
						||
| 
								 | 
							
										self.serialport = Hamlib.hamlib_port_parm_serial
							 | 
						||
| 
								 | 
							
										self.serialport.rate=config['HAMLIB']['rig_rate']
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE)
							 | 
						||
| 
								 | 
							
											self.rig_model = "RIG_MODEL_"+str(config['HAMLIB']['rig_model'])
							 | 
						||
| 
								 | 
							
											self.rig_pathname = config['HAMLIB']['rig_pathname']
							 | 
						||
| 
								 | 
							
											self.rig = Hamlib.Rig(Hamlib.__dict__[self.rig_model])  # Look up the model's numerical index in Hamlib's symbol dictionary.
							 | 
						||
| 
								 | 
							
											self.rig.set_conf("rig_pathname", self.rig_pathname)
							 | 
						||
| 
								 | 
							
											if(config['HAMLIB']['rig_rate']!=""):
							 | 
						||
| 
								 | 
							
												self.rig.set_conf("serial_speed", str(config['HAMLIB']['rig_rate']))
							 | 
						||
| 
								 | 
							
											if(config['HAMLIB']['data_bits']!=""): 
							 | 
						||
| 
								 | 
							
												self.rig.set_conf("data_bits", str(config['HAMLIB']['data_bits'])) #8 as default
							 | 
						||
| 
								 | 
							
											if(config['HAMLIB']['stop_bits']!=""):
							 | 
						||
| 
								 | 
							
												self.rig.set_conf("stop_bits", str(config['HAMLIB']['stop_bits'])) #2 as default
							 | 
						||
| 
								 | 
							
											if(config['HAMLIB']['serial_parity']!=""):
							 | 
						||
| 
								 | 
							
												self.rig.set_conf("serial_parity", str(config['HAMLIB']['serial_parity']))# None as default NONE ODD EVEN MARK SPACE
							 | 
						||
| 
								 | 
							
											if(config['HAMLIB']['serial_handshake']!=""):
							 | 
						||
| 
								 | 
							
												self.rig.set_conf("serial_handshake", str(config['HAMLIB']['serial_handshake'])) # None as default NONE XONXOFF HARDWARE 
							 | 
						||
| 
								 | 
							
											if(config['HAMLIB']['dtr_state']!=""):
							 | 
						||
| 
								 | 
							
												self.rig.set_conf("dtr_state", str(config['HAMLIB']['dtr_state'])) #ON or OFF
							 | 
						||
| 
								 | 
							
											if(config['HAMLIB']['rts_state']!=""):
							 | 
						||
| 
								 | 
							
												self.rig.set_conf("rts_state", str(config['HAMLIB']['rts_state'])) #ON or OFF
							 | 
						||
| 
								 | 
							
											self.rig.set_conf("retry", config['HAMLIB']['retry'])
							 | 
						||
| 
								 | 
							
											self.rig.open()
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not open a communication channel to the rig via Hamlib!")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.setPower(1)
							 | 
						||
| 
								 | 
							
										self.getvfo()
							 | 
						||
| 
								 | 
							
										self.getFreq()
							 | 
						||
| 
								 | 
							
										self.getMode()
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def parsedbtospoint(self,spoint):
							 | 
						||
| 
								 | 
							
										for key, value in self.spoints.items():
							 | 
						||
| 
								 | 
							
											if (spoint<value):
							 | 
						||
| 
								 | 
							
												return key
							 | 
						||
| 
								 | 
							
												break
							 | 
						||
| 
								 | 
							
												
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def getvfo(self):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											self.infos["VFO"] = (self.rig.get_vfo())
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not obtain the current VFO via Hamlib!")
							 | 
						||
| 
								 | 
							
										return self.infos["VFO"]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def setFreq(self,frequency):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											self.rig.set_freq(Hamlib.RIG_VFO_CURR, float(frequency))
							 | 
						||
| 
								 | 
							
											self.getFreq()
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not set the frequency via Hamlib!")
							 | 
						||
| 
								 | 
							
										return self.infos["FREQ"]
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def getFreq(self):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											self.infos["FREQ"] = (int(self.rig.get_freq()))
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not obtain the current frequency via Hamlib!")
							 | 
						||
| 
								 | 
							
										return self.infos["FREQ"]
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def setMode(self,MODE):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
										    
							 | 
						||
| 
								 | 
							
											self.rig.set_mode(Hamlib.rig_parse_mode(MODE))
							 | 
						||
| 
								 | 
							
											self.getMode()
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not set the mode via Hamlib!")
							 | 
						||
| 
								 | 
							
										return self.infos["MODE"]
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def getMode(self):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											(mode, width) = self.rig.get_mode()
							 | 
						||
| 
								 | 
							
											self.infos["MODE"] = Hamlib.rig_strrmode(mode).upper()
							 | 
						||
| 
								 | 
							
											self.infos["WIDTH"] = width
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not obtain the current Mode via Hamlib!")
							 | 
						||
| 
								 | 
							
										return self.infos["MODE"]
							 | 
						||
| 
								 | 
							
										return ""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def getStrgLVL(self):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											self.infos["StrgLVLi"] = self.rig.get_level_i(Hamlib.RIG_LEVEL_STRENGTH)
							 | 
						||
| 
								 | 
							
											self.infos["StrgLVL"] = self.parsedbtospoint(self.infos["StrgLVLi"])
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not obtain the current Strength signal RX level via Hamlib!")
							 | 
						||
| 
								 | 
							
										return self.infos["StrgLVL"]
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def setPTT(self,status):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											if status == "true":
							 | 
						||
| 
								 | 
							
												self.rig.set_ptt(Hamlib.RIG_VFO_CURR,Hamlib.RIG_PTT_ON)
							 | 
						||
| 
								 | 
							
												self.infos["PTT"]=True
							 | 
						||
| 
								 | 
							
											else:
							 | 
						||
| 
								 | 
							
												self.rig.set_ptt(Hamlib.RIG_VFO_CURR,Hamlib.RIG_PTT_OFF)
							 | 
						||
| 
								 | 
							
												self.infos["PTT"]=False
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not set the mode via Hamlib!")
							 | 
						||
| 
								 | 
							
										return self.infos["PTT"]
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def getPTT(self,status):
							 | 
						||
| 
								 | 
							
										return self.infos["PTT"]
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def setPower(self,status=1):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											if status:
							 | 
						||
| 
								 | 
							
												self.rig.set_powerstat(Hamlib.RIG_POWER_ON)
							 | 
						||
| 
								 | 
							
											else:
							 | 
						||
| 
								 | 
							
												self.rig.set_powerstat(Hamlib.RIG_POWER_OFF)
							 | 
						||
| 
								 | 
							
											self.infos["powerstat"] = status
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("Could not set power status via Hamlib!")
							 | 
						||
| 
								 | 
							
										return self.infos["powerstat"]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class ticksTRXRIG(threading.Thread):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def __init__(self):
							 | 
						||
| 
								 | 
							
										threading.Thread.__init__(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def run(self):
							 | 
						||
| 
								 | 
							
										while True:
							 | 
						||
| 
								 | 
							
											if CTRX.infos["powerstat"]:
							 | 
						||
| 
								 | 
							
												CTRX.getStrgLVL()
							 | 
						||
| 
								 | 
							
											time.sleep(0.1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class WS_ControlTRX(tornado.websocket.WebSocketHandler):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def send_to_all_clients(self,msg):
							 | 
						||
| 
								 | 
							
										print ("Send to all: "+msg)
							 | 
						||
| 
								 | 
							
										for client in ControlTRXHandlerClients:
							 | 
						||
| 
								 | 
							
											client.write_message(msg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def sendPTINFOS(self):
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											if self.StrgLVL != CTRX.infos["StrgLVL"]:
							 | 
						||
| 
								 | 
							
												self.write_message("getSignalLevel:"+str(CTRX.infos["StrgLVL"]))
							 | 
						||
| 
								 | 
							
												self.StrgLVL=CTRX.infos["StrgLVL"]
							 | 
						||
| 
								 | 
							
										except:
							 | 
						||
| 
								 | 
							
											print("error TXMETER")
							 | 
						||
| 
								 | 
							
											return None
							 | 
						||
| 
								 | 
							
										tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=float(config['CTRL']['interval_smeter_update'])), self.sendPTINFOS)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def open(self):
							 | 
						||
| 
								 | 
							
										if self not in ControlTRXHandlerClients:
							 | 
						||
| 
								 | 
							
											ControlTRXHandlerClients.append(self)
							 | 
						||
| 
								 | 
							
											self.StrgLVL=0
							 | 
						||
| 
								 | 
							
											self.sendPTINFOS()
							 | 
						||
| 
								 | 
							
										CTRX.setPower(1)
							 | 
						||
| 
								 | 
							
										print('new connection on ControlTRX socket.')
							 | 
						||
| 
								 | 
							
										if(is_rtlsdr_present):
							 | 
						||
| 
								 | 
							
											self.write_message("panfft")
							 | 
						||
| 
								 | 
							
										self.set_nodelay(True)
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									@tornado.gen.coroutine
							 | 
						||
| 
								 | 
							
									def on_message(self, data) :
							 | 
						||
| 
								 | 
							
										global LastPing
							 | 
						||
| 
								 | 
							
										if bool(config['CTRL']['debug']):
							 | 
						||
| 
								 | 
							
											print(data)
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											(action, datato) = data.split(':')
							 | 
						||
| 
								 | 
							
										except ValueError:
							 | 
						||
| 
								 | 
							
											action = data
							 | 
						||
| 
								 | 
							
											pass
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
										if(action == "PING"):
							 | 
						||
| 
								 | 
							
												self.write_message("PONG")
							 | 
						||
| 
								 | 
							
										elif(action == "getFreq"):
							 | 
						||
| 
								 | 
							
											yield self.send_to_all_clients("getFreq:"+str(CTRX.getFreq()))
							 | 
						||
| 
								 | 
							
										elif(action == "setFreq"):
							 | 
						||
| 
								 | 
							
											yield self.send_to_all_clients("getFreq:"+str(CTRX.setFreq(datato)))
							 | 
						||
| 
								 | 
							
										elif(action == "getMode"):
							 | 
						||
| 
								 | 
							
											yield self.send_to_all_clients("getMode:"+str(CTRX.getMode()))	
							 | 
						||
| 
								 | 
							
										elif(action == "setMode"):
							 | 
						||
| 
								 | 
							
											yield self.send_to_all_clients("getMode:"+str(CTRX.setMode(datato)))
							 | 
						||
| 
								 | 
							
										elif(action == "setPTT"):
							 | 
						||
| 
								 | 
							
											yield self.send_to_all_clients("getPTT:"+str(CTRX.setPTT(datato)))	
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										LastPing = time.time();
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
									def on_close(self):
							 | 
						||
| 
								 | 
							
										if self in ControlTRXHandlerClients:
							 | 
						||
| 
								 | 
							
											ControlTRXHandlerClients.remove(self)	
							 | 
						||
| 
								 | 
							
										gc.collect()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def timeoutTRXshutdown():
							 | 
						||
| 
								 | 
							
									global LastPing
							 | 
						||
| 
								 | 
							
									if(LastPing+300) < time.time():
							 | 
						||
| 
								 | 
							
										print("Shutdown TRX")
							 | 
						||
| 
								 | 
							
										CTRX.setPower(0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class threadtimeoutTRXshutdown(threading.Thread):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def __init__(self):
							 | 
						||
| 
								 | 
							
										threading.Thread.__init__(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def run(self):
							 | 
						||
| 
								 | 
							
										while True:
							 | 
						||
| 
								 | 
							
											time.sleep(60)
							 | 
						||
| 
								 | 
							
											timeoutTRXshutdown()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								############ Config ##############
							 | 
						||
| 
								 | 
							
								class ConfigHandler(BaseHandler):
							 | 
						||
| 
								 | 
							
									def get(self):
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
										if bool(config['SERVER']['auth']) and not self.current_user:
							 | 
						||
| 
								 | 
							
											self.redirect("/login")
							 | 
						||
| 
								 | 
							
											return
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
										self.application.settings.get("compiled_template_cache", False)
							 | 
						||
| 
								 | 
							
										self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
							 | 
						||
| 
								 | 
							
										try:
							 | 
						||
| 
								 | 
							
											from serial.tools.list_ports import comports
							 | 
						||
| 
								 | 
							
										except ImportError:
							 | 
						||
| 
								 | 
							
											return None
							 | 
						||
| 
								 | 
							
										audiodevicesoutput=[s for s in alsaaudio.pcms(0) if "plughw" in s]
							 | 
						||
| 
								 | 
							
										audiodevicesinput=[s for s in alsaaudio.pcms(1) if "plughw" in s]
							 | 
						||
| 
								 | 
							
										comports=list(comports())
							 | 
						||
| 
								 | 
							
										rig_models=[s[10:] for s in dir(Hamlib) if "RIG_MODEL_" in s]
							 | 
						||
| 
								 | 
							
										self.write("""<html><form method="POST" action="/CONFIG">""")
							 | 
						||
| 
								 | 
							
										self.write("""[SERVER]<br/><br/>""")
							 | 
						||
| 
								 | 
							
										self.write("""SERVER TCP/IP port:<input type="text" name="SERVER.port" value="""+config['SERVER']['port']+""">Defautl:<b>8888</b>.The server port<br/><br/>""")
							 | 
						||
| 
								 | 
							
										self.write("""SERVER Authentification type:<input type="text" name="SERVER.auth" value="""+config['SERVER']['auth']+"""> Defautl:<b>leave blank</b>. Else you can use "FILE" or/and "PAM".<br/><br/>""")
							 | 
						||
| 
								 | 
							
										self.write("""SERVER database users file:<input type="text" name="SERVER.db_users_file" value="""+config['SERVER']['db_users_file']+"""> Defautl:<b>UHRR_users.db</b> Only if you use Authentification type "FILE".<br/><br/>""")
							 | 
						||
| 
								 | 
							
										self.write("""You can change database users file in UHRR.conf.<br/> To add a user in FILE type, add it in UHRR_users.db (default file name).<br/>Add one account per line as login password.<br/>""")
							 | 
						||
| 
								 | 
							
										self.write("""If you plan to use PAM you can add account in command line: adduser --no-create-home --system thecallsign.<br/><br/>""")
							 | 
						||
| 
								 | 
							
										self.write("""If you want to change certfile and keyfile, replace "UHRH.crt" and "UHRH.key" in the boot folder, and when the pi boot, it will use those files to start http ssl.<br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										self.write("""[AUDIO]<br/><br/>""")
							 | 
						||
| 
								 | 
							
										self.write("""AUDIO outputdevice:<select name="AUDIO.outputdevice">""")
							 | 
						||
| 
								 | 
							
										if(config['AUDIO']['outputdevice']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['AUDIO']['outputdevice']+""" selected>"""+config['AUDIO']['outputdevice']+"""</option>""")
							 | 
						||
| 
								 | 
							
										for c in audiodevicesoutput:
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+c+""">"""+c+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Output from audio soundcard to the mic input of TRX.<br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.write("""AUDIO inputdevice:<select name="AUDIO.inputdevice">""")
							 | 
						||
| 
								 | 
							
										if(config['AUDIO']['inputdevice']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['AUDIO']['inputdevice']+""" selected>"""+config['AUDIO']['inputdevice']+"""</option>""")
							 | 
						||
| 
								 | 
							
										for c in audiodevicesinput:
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+c+""">"""+c+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Input from audio soundcard from the speaker output of TRX.<br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										self.write("""[HAMLIB]<br/><br/>""")
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB radio model:<select name="HAMLIB.rig_model">""")
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['rig_model']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['HAMLIB']['rig_model']+""" selected>"""+config['HAMLIB']['rig_model']+"""</option>""")
							 | 
						||
| 
								 | 
							
										for c in rig_models:
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+c+""">"""+c+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Hamlib trx model.<br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB serial port:<select name="HAMLIB.rig_pathname">""")
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['rig_pathname']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['HAMLIB']['rig_pathname']+""" selected>"""+config['HAMLIB']['rig_pathname']+"""</option>""")
							 | 
						||
| 
								 | 
							
										for c in comports:
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+str(c.device)+""">"""+str(c.device)+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Serial port of the CAT interface.<br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB radio rate:<select name="HAMLIB.rig_rate">""")
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['rig_rate']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['HAMLIB']['rig_rate']+""" selected>"""+config['HAMLIB']['rig_rate']+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=230400>230400</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=115200>115200</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=57600>57600</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=38400>38400</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=19200>19200</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=9600>9600</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=4800>4800</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=2400>2400</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=1200>1200</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=600>600</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=300>300</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=150>150</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Serial port baud rate.<br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB auto tx poweroff:<select name="HAMLIB.trxautopower">""")
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['trxautopower']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['HAMLIB']['trxautopower']+""" selected>"""+config['HAMLIB']['trxautopower']+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"True\">True</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"False\">False</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Set to auto power off the trx when it's not in use<br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										CDVALUE=""
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['data_bits']!=""):
							 | 
						||
| 
								 | 
							
											CDVALUE=config['HAMLIB']['data_bits']
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB serial data bits:<input type="text" name="HAMLIB.data_bits" value="""+CDVALUE+"""> Leave blank to use the HAMIB default value.<br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										CDVALUE=""
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['stop_bits']!=""):
							 | 
						||
| 
								 | 
							
											CDVALUE=config['HAMLIB']['stop_bits']
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB serial stop bits:<input type="text" name="HAMLIB.stop_bits" value="""+CDVALUE+"""> Leave blank to use the HAMIB default value.<br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB serial parity:<select name="HAMLIB.serial_parity">""")
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['serial_parity']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['HAMLIB']['serial_parity']+""" selected>"""+config['HAMLIB']['serial_parity']+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"\"></option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"None\">None</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"Odd\">Odd</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"Even\">Even</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"Mark\">Mark</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"Space\">Space</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB serial handshake:<select name="HAMLIB.serial_handshake">""")
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['serial_handshake']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['HAMLIB']['serial_handshake']+""" selected>"""+config['HAMLIB']['serial_handshake']+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"\"></option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"None\">None</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"XONXOFF\">XONXOFF</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"Hardware\">Hardware</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB dtr state:<select name="HAMLIB.dtr_state">""")
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['dtr_state']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['HAMLIB']['dtr_state']+""" selected>"""+config['HAMLIB']['dtr_state']+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"\"></option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"ON\">ON</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"OFF\">OFF</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB rts state:<select name="HAMLIB.rts_state">""")
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['rts_state']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['HAMLIB']['rts_state']+""" selected>"""+config['HAMLIB']['rts_state']+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"\"></option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"ON\">ON</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=\"OFF\">OFF</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										self.write("""[PANADAPTER]<br/><br/>""")
							 | 
						||
| 
								 | 
							
										self.write("""PANADAPTER FI frequency (hz):<input type="text" name="PANADAPTER.center_freq" value="""+config['PANADAPTER']['center_freq']+"""><br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.write("""HAMLIB radio rate (samples/s):<select name="PANADAPTER.sample_rate">""")
							 | 
						||
| 
								 | 
							
										if(config['PANADAPTER']['sample_rate']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['PANADAPTER']['sample_rate']+""" selected>"""+config['PANADAPTER']['sample_rate']+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=3200000>3200000</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=2880000>2880000</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=2400000>2400000</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=1800000>1800000</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=1440000>1440000</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=1200000>1200000</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=1020000>1020000</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value=960000>960000</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select><br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.write("""PANADAPTER frequency correction (ppm):<input type="text" name="PANADAPTER.freq_correction" value="""+config['PANADAPTER']['freq_correction']+"""><br/><br/>""")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										self.write("""PANADAPTER initial gain:<input type="text" name="PANADAPTER.gain" value="""+config['PANADAPTER']['gain']+"""><br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.write("""PANADAPTER windowing:<select name="PANADAPTER.fft_window">""")
							 | 
						||
| 
								 | 
							
										if(config['PANADAPTER']['fft_window']!=""):
							 | 
						||
| 
								 | 
							
											self.write("""<option value="""+config['PANADAPTER']['fft_window']+""" selected>"""+config['PANADAPTER']['fft_window']+"""</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value="bartlett">bartlett</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value="blackman">blackman</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value="hamming">hamming</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""<option value="hanning">hanning</option>""")
							 | 
						||
| 
								 | 
							
										self.write("""</select><br/><br/>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										self.write("""<input type="submit" value="Save & Restart server"><br/><br/></form>Possible problem:"""+e+"""</html>""")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def post(self):
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
										if bool(config['SERVER']['auth']) and not self.current_user:
							 | 
						||
| 
								 | 
							
											self.redirect("/login")
							 | 
						||
| 
								 | 
							
											return
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
										for x in self.request.arguments:
							 | 
						||
| 
								 | 
							
											(s,o)=x.split(".")
							 | 
						||
| 
								 | 
							
											v=self.get_argument(x)
							 | 
						||
| 
								 | 
							
											print(s,o,v)
							 | 
						||
| 
								 | 
							
											if config.has_option(s,o):
							 | 
						||
| 
								 | 
							
												config[s][o]=v
							 | 
						||
| 
								 | 
							
										with open('UHRR.conf', 'w') as configfile:
							 | 
						||
| 
								 | 
							
											config.write(configfile)
							 | 
						||
| 
								 | 
							
										self.write("""<html><head><script>window.setTimeout(function() {window.location.href = 'https://'+window.location.hostname+':'+ '"""+config['SERVER']['port']+"""';}, 10000);</script><head><body>You will be redirected automatically. Please wait...<br><img width="40px" height=40px" src="../img/spinner.gif"></body></html>""")
							 | 
						||
| 
								 | 
							
										self.flush() 
							 | 
						||
| 
								 | 
							
										time.sleep(2)
							 | 
						||
| 
								 | 
							
										os.system("sleep 2;./UHRR &")
							 | 
						||
| 
								 | 
							
										os._exit(1)
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
								############ Login ##############
							 | 
						||
| 
								 | 
							
								class AuthLoginHandler(BaseHandler):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def get(self):
							 | 
						||
| 
								 | 
							
										if not bool(config['SERVER']['auth']):
							 | 
						||
| 
								 | 
							
											self.redirect("/")
							 | 
						||
| 
								 | 
							
											return
							 | 
						||
| 
								 | 
							
										self.write('<html><body><form action="/login" method="post">'
							 | 
						||
| 
								 | 
							
													'CallSign: <input type="text" name="name"></br>'
							 | 
						||
| 
								 | 
							
													'Password: <input type="password" name="passwd"></br>'
							 | 
						||
| 
								 | 
							
													'<input type="submit" value="Sign in">'
							 | 
						||
| 
								 | 
							
													'</form></body></html>')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def post(self):
							 | 
						||
| 
								 | 
							
										if self.get_argument("name") != "" and self.get_argument("passwd") != "":
							 | 
						||
| 
								 | 
							
											if self.bind(self.get_argument("name"),self.get_argument("passwd")):
							 | 
						||
| 
								 | 
							
												self.set_secure_cookie("user", self.get_argument("name"))
							 | 
						||
| 
								 | 
							
												self.set_cookie("callsign", self.get_argument("name"))
							 | 
						||
| 
								 | 
							
												self.set_cookie("autha", "1")
							 | 
						||
| 
								 | 
							
											else:
							 | 
						||
| 
								 | 
							
												writte_log("Auth error for CallSign:"+str(self.get_argument("name")))
							 | 
						||
| 
								 | 
							
										self.redirect("/")
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
									def bind(self,user="",password=""):
							 | 
						||
| 
								 | 
							
										retval = False
							 | 
						||
| 
								 | 
							
										if (user!="" and password!=""):
							 | 
						||
| 
								 | 
							
											if config['SERVER']['auth'].find("FILE") != -1: #test with users db file
							 | 
						||
| 
								 | 
							
												f = open(config['SERVER']['db_users_file'], "r") 
							 | 
						||
| 
								 | 
							
												for x in f:
							 | 
						||
| 
								 | 
							
													if x[0]!="#":
							 | 
						||
| 
								 | 
							
														db=x.strip('\n').split(" ")
							 | 
						||
| 
								 | 
							
														if db[0] == user and db[1]== password:
							 | 
						||
| 
								 | 
							
															retval = True
							 | 
						||
| 
								 | 
							
															break		
							 | 
						||
| 
								 | 
							
											if not retval and config['SERVER']['auth'].find("PAM") != -1:#test with pam module
							 | 
						||
| 
								 | 
							
												if config['SERVER']['pam_account'].find(user) != -1:
							 | 
						||
| 
								 | 
							
													import pam
							 | 
						||
| 
								 | 
							
													retval = pam.authenticate(user, password) 
							 | 
						||
| 
								 | 
							
										return retval
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
								class AuthLogoutHandler(BaseHandler):
							 | 
						||
| 
								 | 
							
									def get(self):
							 | 
						||
| 
								 | 
							
										self.clear_cookie("user")
							 | 
						||
| 
								 | 
							
										self.clear_cookie("autha")
							 | 
						||
| 
								 | 
							
										self.redirect(self.get_argument("next", "/"))
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
								############ Main ##############
							 | 
						||
| 
								 | 
							
								class MainHandler(BaseHandler):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									def get(self):
							 | 
						||
| 
								 | 
							
										print("Tornado current user:"+str(self.current_user))
							 | 
						||
| 
								 | 
							
										if bool(config['SERVER']['auth']) and not self.current_user:
							 | 
						||
| 
								 | 
							
											self.redirect("/login")
							 | 
						||
| 
								 | 
							
											return
							 | 
						||
| 
								 | 
							
										self.application.settings.get("compiled_template_cache", False)
							 | 
						||
| 
								 | 
							
										self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
							 | 
						||
| 
								 | 
							
										self.render("www/index.html")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if __name__ == "__main__":	
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									try:
							 | 
						||
| 
								 | 
							
										if is_rtlsdr_present:
							 | 
						||
| 
								 | 
							
											threadFFT = loadFFTdata()
							 | 
						||
| 
								 | 
							
											threadFFT.start()
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
										threadloadWavdata = loadWavdata()
							 | 
						||
| 
								 | 
							
										threadloadWavdata.start()
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										CTRX = TRXRIG()
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										threadticksTRXRIG = ticksTRXRIG()
							 | 
						||
| 
								 | 
							
										threadticksTRXRIG.start()
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										if(config['HAMLIB']['trxautopower']=="True"):
							 | 
						||
| 
								 | 
							
											threadsurveilTRX = threadtimeoutTRXshutdown()
							 | 
						||
| 
								 | 
							
											threadsurveilTRX.start()
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										app = tornado.web.Application([
							 | 
						||
| 
								 | 
							
										(r'/login', AuthLoginHandler),
							 | 
						||
| 
								 | 
							
										(r'/logout', AuthLogoutHandler),
							 | 
						||
| 
								 | 
							
										(r'/WSaudioRX', WS_AudioRXHandler),
							 | 
						||
| 
								 | 
							
										(r'/WSaudioTX', WS_AudioTXHandler),
							 | 
						||
| 
								 | 
							
										(r'/WSCTRX', WS_ControlTRX),
							 | 
						||
| 
								 | 
							
										(r'/WSpanFFT', WS_panFFTHandler),
							 | 
						||
| 
								 | 
							
										(r'/(panfft.*)', tornado.web.StaticFileHandler, { 'path' : './www/panadapter' }),
							 | 
						||
| 
								 | 
							
										(r'/CONFIG', ConfigHandler),
							 | 
						||
| 
								 | 
							
										(r'/', MainHandler),
							 | 
						||
| 
								 | 
							
										(r'/(.*)', tornado.web.StaticFileHandler, { 'path' : './www' })
							 | 
						||
| 
								 | 
							
										],debug=bool(config['SERVER']['debug']), websocket_ping_interval=10, cookie_secret=config['SERVER']['cookie_secret'])
							 | 
						||
| 
								 | 
							
									except:
							 | 
						||
| 
								 | 
							
										e = str(sys.exc_info())
							 | 
						||
| 
								 | 
							
										print(e)
							 | 
						||
| 
								 | 
							
										app = tornado.web.Application([
							 | 
						||
| 
								 | 
							
										(r'/CONFIG', ConfigHandler),
							 | 
						||
| 
								 | 
							
										(r'/', ConfigHandler),
							 | 
						||
| 
								 | 
							
										(r'/(.*)', tornado.web.StaticFileHandler, { 'path' : './www' })
							 | 
						||
| 
								 | 
							
										],debug=bool(config['SERVER']['debug']))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									http_server = tornado.httpserver.HTTPServer(app, ssl_options={
							 | 
						||
| 
								 | 
							
									"certfile": os.path.join(config['SERVER']['certfile']),
							 | 
						||
| 
								 | 
							
									"keyfile": os.path.join(config['SERVER']['keyfile']),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
									http_server.listen(int(config['SERVER']['port']))
							 | 
						||
| 
								 | 
							
									print('HTTP server started.')
							 | 
						||
| 
								 | 
							
									tornado.ioloop.IOLoop.instance().start()
							 | 
						||
| 
								 | 
							
								
							 |