Universal_HamRadio_Remote_H.../UHRR

824 lines
30 KiB
Plaintext
Raw Normal View History

2020-09-26 00:46:56 +00:00
#!/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
2020-09-27 03:40:13 +00:00
from opus.decoder import Decoder as OpusDecoder
import datetime
2020-10-02 23:20:12 +00:00
import configparser
import sys
2020-10-12 23:09:58 +00:00
import Hamlib
2020-11-10 03:11:31 +00:00
from rtlsdr import RtlSdr
import numpy as np
import math
2020-09-26 00:46:56 +00:00
############ Global variables ##################################
CTRX=None
2020-10-02 23:20:12 +00:00
config = configparser.ConfigParser()
config.read('UHRR.conf')
e="No"
2020-09-26 00:46:56 +00:00
2020-11-15 01:43:50 +00:00
############ 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()
2020-11-10 03:11:31 +00:00
2020-11-23 16:39:07 +00:00
############ BaseHandler tornado ##############
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
return self.get_secure_cookie("user")
############ Generate and send FFT from RTLSDR ##############
2020-11-10 03:11:31 +00:00
is_rtlsdr_present = True
try:
2020-11-22 03:27:27 +00:00
FFTSIZE=4096
2020-11-10 03:11:31 +00:00
nbBuffer=24
nbsamples=nbBuffer/2*FFTSIZE
ptime=nbsamples/int(config['PANADAPTER']['sample_rate'])
2020-11-27 20:27:20 +00:00
sdr_windows = eval("np."+config['PANADAPTER']['fft_window']+ "(FFTSIZE)")
fftpaquetlen=int(FFTSIZE*8/2048)
2020-11-27 20:30:00 +00:00
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'
2020-11-10 03:11:31 +00:00
except:
is_rtlsdr_present = False
2020-11-27 20:27:20 +00:00
2020-11-27 19:04:00 +00:00
2020-11-10 03:11:31 +00:00
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:
2020-11-22 03:27:27 +00:00
time.sleep(ptime)
2020-11-10 03:11:31 +00:00
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
2020-11-27 19:04:00 +00:00
td_segment = data[start:end]*sdr_windows
2020-11-10 03:11:31 +00:00
# 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)
2020-11-23 04:31:10 +00:00
max_pow = -254
2020-11-10 03:11:31 +00:00
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
2020-11-23 04:31:10 +00:00
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
2020-11-10 03:11:31 +00:00
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
2020-11-27 20:27:20 +00:00
2020-11-22 03:27:27 +00:00
2020-11-23 16:39:07 +00:00
class WS_panFFTHandler(tornado.websocket.WebSocketHandler):
2020-11-10 03:11:31 +00:00
@tornado.gen.coroutine
def sendFFT(self):
2020-11-22 03:27:27 +00:00
global ptime, fftpaquetlen
2020-11-10 03:11:31 +00:00
try:
while len(self.fftframes)>0:
2020-11-23 04:31:10 +00:00
yield self.write_message(self.fftframes[0],binary=True)
2020-11-10 03:11:31 +00:00
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 = []
2020-11-22 03:27:27 +00:00
def on_message(self, data) :
print(data)
if str(data)=="ready":
2020-11-10 03:11:31 +00:00
self.sendFFT()
2020-11-22 03:27:27 +00:00
elif str(data)=="init":
self.write_message("fftsr:"+str(config['PANADAPTER']['sample_rate']));
self.write_message("fftsz:"+str(FFTSIZE));
self.write_message("fftst");
2020-11-10 03:11:31 +00:00
def on_close(self):
print('connection closed for FFT socket')
2020-09-26 00:46:56 +00:00
############ websocket for send RX audio from TRX ##############
flagWavstart = False
AudioRXHandlerClients = []
class loadWavdata(threading.Thread):
def __init__(self):
global flagWavstart
threading.Thread.__init__(self)
2020-11-08 16:44:54 +00:00
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'])
2020-09-26 00:46:56 +00:00
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")
2020-11-08 16:44:54 +00:00
time.sleep(0.01)
2020-09-26 00:46:56 +00:00
2020-11-23 16:39:07 +00:00
class WS_AudioRXHandler(tornado.websocket.WebSocketHandler):
2020-09-26 00:46:56 +00:00
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()
2020-12-05 13:55:18 +00:00
self.set_nodelay(True)
2020-09-26 00:46:56 +00:00
@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()
2020-09-27 03:40:13 +00:00
############ websocket for control TX ##############
2020-10-09 22:46:59 +00:00
last_AudioTXHandler_msg_time=0
AudioTXHandlerClients = []
2020-09-27 03:40:13 +00:00
2020-11-23 16:39:07 +00:00
class WS_AudioTXHandler(tornado.websocket.WebSocketHandler):
2020-09-27 03:40:13 +00:00
2020-10-09 22:46:59 +00:00
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)
2020-09-27 03:40:13 +00:00
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
2020-10-02 23:20:12 +00:00
device = config['AUDIO']['outputdevice']
2020-09-27 03:40:13 +00:00
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):
2020-10-09 22:46:59 +00:00
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()
2020-12-05 13:55:18 +00:00
self.set_nodelay(True)
2020-09-27 03:40:13 +00:00
def on_message(self, data) :
2020-10-09 22:46:59 +00:00
global last_AudioTXHandler_msg_time
last_AudioTXHandler_msg_time=time.time()
2020-09-27 03:40:13 +00:00
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):
2020-10-09 22:46:59 +00:00
global AudioTXHandlerClients
2020-09-27 03:40:13 +00:00
if(hasattr(self,"inp")):
self.inp.close()
2020-10-09 22:46:59 +00:00
if self in AudioTXHandlerClients:
AudioTXHandlerClients.remove(self)
if (not len(AudioTXHandlerClients)) and (CTRX.infos["PTT"]==True):
CTRX.setPTT("false")
2020-09-27 03:40:13 +00:00
print('connection closed for TX socket')
2020-09-26 00:46:56 +00:00
############ websocket for control TRX ##############
ControlTRXHandlerClients = []
2020-10-08 22:35:49 +00:00
LastPing = time.time()
2020-09-26 00:46:56 +00:00
class TRXRIG:
def __init__(self):
2020-09-27 03:40:13 +00:00
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}
2020-09-26 00:46:56 +00:00
self.infos = {}
2020-10-09 22:46:59 +00:00
self.infos["PTT"]=False
self.infos["powerstat"]=False
2020-09-26 00:46:56 +00:00
self.serialport = Hamlib.hamlib_port_parm_serial
2020-10-12 23:09:58 +00:00
self.serialport.rate=config['HAMLIB']['rig_rate']
2020-10-02 23:20:12 +00:00
try:
2020-10-12 20:56:21 +00:00
Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE)
2020-10-02 23:20:12 +00:00
self.rig_model = "RIG_MODEL_"+str(config['HAMLIB']['rig_model'])
self.rig_pathname = config['HAMLIB']['rig_pathname']
2020-09-26 00:46:56 +00:00
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
2020-10-02 23:20:12 +00:00
self.rig.set_conf("retry", config['HAMLIB']['retry'])
2020-09-26 00:46:56 +00:00
self.rig.open()
except:
2020-10-12 23:09:58 +00:00
print("Could not open a communication channel to the rig via Hamlib!")
self.setPower(1)
2020-09-26 00:46:56 +00:00
self.getvfo()
self.getFreq()
self.getMode()
2020-09-27 03:40:13 +00:00
def parsedbtospoint(self,spoint):
for key, value in self.spoints.items():
if (spoint<value):
return key
break
2020-09-26 00:46:56 +00:00
def getvfo(self):
try:
self.infos["VFO"] = (self.rig.get_vfo())
except:
2020-10-12 23:09:58 +00:00
print("Could not obtain the current VFO via Hamlib!")
2020-09-26 00:46:56 +00:00
return self.infos["VFO"]
def setFreq(self,frequency):
try:
self.rig.set_freq(Hamlib.RIG_VFO_CURR, float(frequency))
self.getFreq()
except:
2020-10-12 23:09:58 +00:00
print("Could not set the frequency via Hamlib!")
2020-09-26 00:46:56 +00:00
return self.infos["FREQ"]
def getFreq(self):
try:
self.infos["FREQ"] = (int(self.rig.get_freq()))
except:
2020-10-12 23:09:58 +00:00
print("Could not obtain the current frequency via Hamlib!")
2020-09-26 00:46:56 +00:00
return self.infos["FREQ"]
def setMode(self,MODE):
try:
self.rig.set_mode(Hamlib.rig_parse_mode(MODE))
self.getMode()
except:
2020-10-12 23:09:58 +00:00
print("Could not set the mode via Hamlib!")
2020-09-26 00:46:56 +00:00
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:
2020-10-12 23:09:58 +00:00
print("Could not obtain the current Mode via Hamlib!")
2020-09-26 00:46:56 +00:00
return self.infos["MODE"]
2020-10-12 23:09:58 +00:00
return ""
2020-09-26 00:46:56 +00:00
def getStrgLVL(self):
try:
2020-09-27 03:40:13 +00:00
self.infos["StrgLVLi"] = self.rig.get_level_i(Hamlib.RIG_LEVEL_STRENGTH)
self.infos["StrgLVL"] = self.parsedbtospoint(self.infos["StrgLVLi"])
2020-09-26 00:46:56 +00:00
except:
2020-10-12 23:09:58 +00:00
print("Could not obtain the current Strength signal RX level via Hamlib!")
2020-09-26 00:46:56 +00:00
return self.infos["StrgLVL"]
2020-09-27 03:40:13 +00:00
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:
2020-10-12 23:09:58 +00:00
print("Could not set the mode via Hamlib!")
2020-09-27 03:40:13 +00:00
return self.infos["PTT"]
def getPTT(self,status):
return self.infos["PTT"]
2020-09-26 00:46:56 +00:00
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:
2020-10-12 23:09:58 +00:00
print("Could not set power status via Hamlib!")
2020-09-26 00:46:56 +00:00
return self.infos["powerstat"]
2020-09-27 03:40:13 +00:00
class ticksTRXRIG(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
if CTRX.infos["powerstat"]:
CTRX.getStrgLVL()
2020-10-09 15:55:03 +00:00
time.sleep(0.1)
2020-09-27 03:40:13 +00:00
2020-11-23 16:39:07 +00:00
class WS_ControlTRX(tornado.websocket.WebSocketHandler):
2020-09-27 03:40:13 +00:00
2020-10-09 22:46:59 +00:00
def send_to_all_clients(self,msg):
print ("Send to all: "+msg)
for client in ControlTRXHandlerClients:
client.write_message(msg)
2020-09-27 03:40:13 +00:00
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
2020-10-02 23:20:12 +00:00
tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=float(config['CTRL']['interval_smeter_update'])), self.sendPTINFOS)
2020-09-27 03:40:13 +00:00
2020-09-26 00:46:56 +00:00
def open(self):
if self not in ControlTRXHandlerClients:
ControlTRXHandlerClients.append(self)
2020-09-27 03:40:13 +00:00
self.StrgLVL=0
self.sendPTINFOS()
2020-10-08 22:35:49 +00:00
CTRX.setPower(1)
2020-09-26 00:46:56 +00:00
print('new connection on ControlTRX socket.')
2020-11-10 03:11:31 +00:00
if(is_rtlsdr_present):
self.write_message("panfft")
2020-12-05 13:55:18 +00:00
self.set_nodelay(True)
2020-09-26 00:46:56 +00:00
@tornado.gen.coroutine
def on_message(self, data) :
global LastPing
2020-10-02 23:20:12 +00:00
if bool(config['CTRL']['debug']):
print(data)
2020-09-26 00:46:56 +00:00
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()))
2020-09-26 00:46:56 +00:00
elif(action == "setFreq"):
2020-10-09 22:46:59 +00:00
yield self.send_to_all_clients("getFreq:"+str(CTRX.setFreq(datato)))
2020-09-27 03:40:13 +00:00
elif(action == "getMode"):
2020-10-09 22:46:59 +00:00
yield self.send_to_all_clients("getMode:"+str(CTRX.getMode()))
2020-09-27 03:40:13 +00:00
elif(action == "setMode"):
2020-10-09 22:46:59 +00:00
yield self.send_to_all_clients("getMode:"+str(CTRX.setMode(datato)))
2020-09-27 03:40:13 +00:00
elif(action == "setPTT"):
yield self.send_to_all_clients("getPTT:"+str(CTRX.setPTT(datato)))
LastPing = time.time();
2020-09-26 00:46:56 +00:00
def on_close(self):
if self in ControlTRXHandlerClients:
ControlTRXHandlerClients.remove(self)
gc.collect()
2020-10-08 22:35:49 +00:00
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)
2020-10-08 22:35:49 +00:00
def run(self):
while True:
time.sleep(60)
timeoutTRXshutdown()
2020-11-15 01:43:50 +00:00
############ Config ##############
class ConfigHandler(BaseHandler):
2020-09-26 00:46:56 +00:00
def get(self):
2020-11-15 01:43:50 +00:00
if bool(config['SERVER']['auth']) and not self.current_user:
self.redirect("/login")
return
2020-10-02 23:20:12 +00:00
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/>""")
2020-11-15 01:43:50 +00:00
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/>""")
2020-10-02 23:20:12 +00:00
self.write("""If you whant 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/>""")
2020-09-26 00:46:56 +00:00
2020-10-02 23:20:12 +00:00
self.write("""[AUDIO]<br/><br/>""")
self.write("""AUDIO outputdevice:<select name="AUDIO.outputdevice">""")
if(config['AUDIO']['outputdevice']!=""):
2020-10-02 23:20:12 +00:00
self.write("""<option value="""+config['AUDIO']['outputdevice']+""" selected>"""+config['AUDIO']['outputdevice']+"""</option>""")
for c in audiodevicesoutput:
self.write("""<option value="""+c+""">"""+c+"""</option>""")
2020-11-15 01:43:50 +00:00
self.write("""</select> Output from audio soundcard to the mic input of TRX.<br/><br/>""")
2020-10-02 23:20:12 +00:00
self.write("""AUDIO inputdevice:<select name="AUDIO.inputdevice">""")
if(config['AUDIO']['inputdevice']!=""):
2020-10-02 23:20:12 +00:00
self.write("""<option value="""+config['AUDIO']['inputdevice']+""" selected>"""+config['AUDIO']['inputdevice']+"""</option>""")
for c in audiodevicesinput:
self.write("""<option value="""+c+""">"""+c+"""</option>""")
2020-11-15 01:43:50 +00:00
self.write("""</select> Input from audio soundcard from the speaker output of TRX.<br/><br/>""")
2020-09-26 00:46:56 +00:00
2020-10-02 23:20:12 +00:00
self.write("""[HAMLIB]<br/><br/>""")
2020-11-15 01:43:50 +00:00
2020-10-02 23:20:12 +00:00
self.write("""HAMLIB radio model:<select name="HAMLIB.rig_model">""")
if(config['HAMLIB']['rig_model']!=""):
2020-10-02 23:20:12 +00:00
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>""")
2020-11-15 01:43:50 +00:00
self.write("""</select> Hamlib trx model.<br/><br/>""")
self.write("""HAMLIB serial port:<select name="HAMLIB.rig_pathname">""")
if(config['HAMLIB']['rig_pathname']!=""):
2020-11-15 01:43:50 +00:00
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/>""")
2020-10-02 23:20:12 +00:00
self.write("""HAMLIB radio rate:<select name="HAMLIB.rig_rate">""")
if(config['HAMLIB']['rig_rate']!=""):
2020-10-12 23:09:58 +00:00
self.write("""<option value="""+config['HAMLIB']['rig_rate']+""" selected>"""+config['HAMLIB']['rig_rate']+"""</option>""")
2020-10-28 01:54:52 +00:00
self.write("""<option value=230400>230400</option>""")
2020-10-28 01:50:18 +00:00
self.write("""<option value=115200>115200</option>""")
self.write("""<option value=57600>57600</option>""")
2020-10-12 23:17:19 +00:00
self.write("""<option value=38400>38400</option>""")
2020-10-28 01:50:18 +00:00
self.write("""<option value=19200>19200</option>""")
2020-10-12 23:17:19 +00:00
self.write("""<option value=9600>9600</option>""")
self.write("""<option value=4800>4800</option>""")
2020-10-28 01:50:18 +00:00
self.write("""<option value=2400>2400</option>""")
self.write("""<option value=1200>1200</option>""")
2020-10-28 01:54:52 +00:00
self.write("""<option value=600>600</option>""")
self.write("""<option value=300>300</option>""")
self.write("""<option value=150>150</option>""")
2020-11-15 01:43:50 +00:00
self.write("""</select> Serial port baud rate.<br/><br/>""")
2020-10-12 23:09:58 +00:00
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>""")
2020-11-15 01:43:50 +00:00
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/>""")
2020-11-10 03:11:31 +00:00
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/>""")
2020-11-10 03:11:31 +00:00
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']!=""):
2020-11-10 03:11:31 +00:00
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/>""")
2020-10-08 22:35:49 +00:00
2020-11-10 03:11:31 +00:00
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/>""")
2020-11-27 19:04:00 +00:00
self.write("""PANADAPTER windowing:<select name="PANADAPTER.fft_window">""")
if(config['PANADAPTER']['fft_window']!=""):
2020-11-27 19:04:00 +00:00
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/>""")
2020-10-02 23:20:12 +00:00
self.write("""<input type="submit" value="Save & Restart server"><br/><br/></form>Possible problem:"""+e+"""</html>""")
def post(self):
2020-11-15 01:43:50 +00:00
if bool(config['SERVER']['auth']) and not self.current_user:
self.redirect("/login")
return
2020-10-02 23:20:12 +00:00
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>""")
2020-10-02 23:20:12 +00:00
self.flush()
time.sleep(2)
os.system("sleep 2;./UHRR &")
os._exit(1)
2020-11-15 01:43:50 +00:00
############ 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(" ")
2020-11-15 01:43:50 +00:00
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")
2020-10-02 23:20:12 +00:00
if __name__ == "__main__":
2020-09-27 03:40:13 +00:00
2020-10-02 23:20:12 +00:00
try:
2020-11-10 03:11:31 +00:00
if is_rtlsdr_present:
threadFFT = loadFFTdata()
threadFFT.start()
2020-10-02 23:20:12 +00:00
threadloadWavdata = loadWavdata()
threadloadWavdata.start()
CTRX = TRXRIG()
threadticksTRXRIG = ticksTRXRIG()
threadticksTRXRIG.start()
if(config['HAMLIB']['trxautopower']=="True"):
2020-10-08 22:35:49 +00:00
threadsurveilTRX = threadtimeoutTRXshutdown()
threadsurveilTRX.start()
2020-10-02 23:20:12 +00:00
app = tornado.web.Application([
2020-11-23 16:39:07 +00:00
(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' }),
2020-10-02 23:20:12 +00:00
(r'/CONFIG', ConfigHandler),
(r'/', MainHandler),
(r'/(.*)', tornado.web.StaticFileHandler, { 'path' : './www' })
2020-11-15 01:43:50 +00:00
],debug=bool(config['SERVER']['debug']), websocket_ping_interval=10, cookie_secret=config['SERVER']['cookie_secret'])
2020-10-02 23:20:12 +00:00
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']))
2020-09-26 00:46:56 +00:00
http_server = tornado.httpserver.HTTPServer(app, ssl_options={
2020-10-02 23:20:12 +00:00
"certfile": os.path.join(config['SERVER']['certfile']),
"keyfile": os.path.join(config['SERVER']['keyfile']),
2020-09-26 00:46:56 +00:00
})
2020-10-02 23:20:12 +00:00
http_server.listen(int(config['SERVER']['port']))
print('HTTP server started.')
2020-10-28 01:50:18 +00:00
tornado.ioloop.IOLoop.instance().start()
2020-11-15 01:43:50 +00:00