HamRemote/UHRR
2023-02-09 08:33:01 +01:00

829 lines
30 KiB
Python
Executable File

#!/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()