Universal_HamRadio_Remote_H.../UHRR
2020-10-27 21:54:52 -04:00

478 lines
15 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
############ Global variables ##################################
CTRX=None
config = configparser.ConfigParser()
config.read('UHRR.conf')
e="No"
############ websocket for send RX audio from TRX ##############
flagWavstart = False
AudioRXHandlerClients = []
class loadWavdata(threading.Thread):
def __init__(self):
global flagWavstart
threading.Thread.__init__(self)
device = device = config['AUDIO']['inputdevice']
self.inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, channels=1, rate=8000, format=alsaaudio.PCM_FORMAT_FLOAT_LE, periodsize=256, device=device)
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")
class 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()
@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 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()
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)
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 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.')
@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
print("Shutdown TRX")
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()
############ Main ##############
class MainHandler(tornado.web.RequestHandler):
def get(self):
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")
class ConfigHandler(tornado.web.RequestHandler):
def get(self):
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 port:<input type="text" name="SERVER.port" value="""+config['SERVER']['port']+"""><br/><br/>""")
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/>""")
self.write("""[AUDIO]<br/><br/>""")
self.write("""AUDIO outputdevice:<select name="AUDIO.outputdevice">""")
if(config['AUDIO']['outputdevice']!="null"):
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><br/><br/>""")
self.write("""AUDIO inputdevice:<select name="AUDIO.inputdevice">""")
if(config['AUDIO']['inputdevice']!="null"):
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><br/><br/>""")
self.write("""[HAMLIB]<br/><br/>""")
self.write("""HAMLIB com port:<select name="HAMLIB.rig_pathname">""")
if(config['HAMLIB']['rig_pathname']!="null"):
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><br/><br/>""")
self.write("""HAMLIB radio model:<select name="HAMLIB.rig_model">""")
if(config['HAMLIB']['rig_model']!="null"):
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><br/><br/>""")
self.write("""HAMLIB radio model:<select name="HAMLIB.rig_rate">""")
if(config['HAMLIB']['rig_rate']!="null"):
self.write("""<option value="""+config['HAMLIB']['rig_rate']+""" selected>"""+config['HAMLIB']['rig_rate']+"""</option>""")
# https://github.com/Hamlib/Hamlib/blob/master/src/serial.c shows the supported baud rates:
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><br/><br/>""")
self.write("""HAMLIB auto tx poweroff:<select name="HAMLIB.trxautopower">""")
if(config['HAMLIB']['trxautopower']!="null"):
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><br/><br/>""")
self.write("""<input type="submit" value="Save & Restart server"><br/><br/></form>Possible problem:"""+e+"""</html>""")
def post(self):
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>your are redirect automaticly...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)
if __name__ == "__main__":
try:
threadloadWavdata = loadWavdata()
threadloadWavdata.start()
CTRX = TRXRIG()
threadticksTRXRIG = ticksTRXRIG()
threadticksTRXRIG.start()
if(config['HAMLIB']['trxautopower']=="True"):
threadsurveilTRX = threadtimeoutTRXshutdown()
threadsurveilTRX.start()
app = tornado.web.Application([
(r'/audioRX', AudioRXHandler),
(r'/audioTX', AudioTXHandler),
(r'/CTRX', ControlTRX),
(r'/CONFIG', ConfigHandler),
(r'/', MainHandler),
(r'/(.*)', tornado.web.StaticFileHandler, { 'path' : './www' })
],debug=bool(config['SERVER']['debug']), websocket_ping_interval=10)
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()