Universal_HamRadio_Remote_H.../UHRR

422 lines
13 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
import Hamlib
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-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
############ websocket for send RX audio from TRX ##############
flagWavstart = False
AudioRXHandlerClients = []
class loadWavdata(threading.Thread):
def __init__(self):
global flagWavstart
threading.Thread.__init__(self)
2020-10-02 23:20:12 +00:00
device = device = config['AUDIO']['inputdevice']
2020-09-26 00:46:56 +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=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()
2020-09-27 03:40:13 +00:00
############ websocket for control TX ##############
class AudioTXHandler(tornado.websocket.WebSocketHandler):
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):
print('new connection on AudioTXHandler socket.')
def on_message(self, data) :
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):
if(hasattr(self,"inp")):
self.inp.close()
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 = {}
self.serialport = Hamlib.hamlib_port_parm_serial
self.serialport.rate=38400
2020-10-02 23:20:12 +00:00
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']
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)
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:
logging.error("Could not open a communication channel to the rig via Hamlib!")
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:
logging.error("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:
logging.error("Could not set the frequency via Hamlib!")
return self.infos["FREQ"]
def getFreq(self):
try:
self.infos["FREQ"] = (int(self.rig.get_freq()))
except:
logging.error("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:
logging.error("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:
logging.error("Could not obtain the current Mode via Hamlib!")
return self.infos["MODE"]
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:
logging.error("Could not obtain the current Strength signal RX level via Hamlib!")
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:
logging.error("Could not set the mode via Hamlib!")
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:
logging.error("Could not set power status via Hamlib!")
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:
CTRX.getStrgLVL()
time.sleep(0.5)
2020-09-26 00:46:56 +00:00
class ControlTRX(tornado.websocket.WebSocketHandler):
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.')
@tornado.gen.coroutine
def on_message(self, data) :
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.write_message("getFreq:"+str(CTRX.getFreq())) #CTRX()[data]()
elif(action == "setFreq"):
yield self.write_message("getFreq:"+str(CTRX.setFreq(datato)))
2020-09-27 03:40:13 +00:00
elif(action == "getMode"):
yield self.write_message("getMode:"+str(CTRX.getMode()))
elif(action == "setMode"):
yield self.write_message("getMode:"+str(CTRX.setMode(datato)))
elif(action == "setPTT"):
yield self.write_message("getPTT:"+str(CTRX.setPTT(datato)))
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
print("Shutdown TRX")
if(LastPing+300) < time.time():
print("Shutdown TRX")
CTRX.setPower(0)
class threadtimeoutTRXshutdown(threading.Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
while True:
time.sleep(60)
timeoutTRXshutdown()
2020-09-26 00:46:56 +00:00
############ Main ##############
class MainHandler(tornado.web.RequestHandler):
2020-10-02 23:20:12 +00:00
2020-09-26 00:46:56 +00:00
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")
2020-10-02 23:20:12 +00:00
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/>""")
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']!="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/>""")
2020-09-26 00:46:56 +00:00
2020-10-02 23:20:12 +00:00
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/>""")
2020-10-08 22:35:49 +00:00
self.write("""HAMLIB auto tx poweroff:<select name="HAMLIB.TXautopower">""")
if(config['HAMLIB']['TXautopower']!="null"):
self.write("""<option value="""+config['HAMLIB']['TXautopower']+""" selected>"""+config['HAMLIB']['TXautopower']+"""</option>""")
self.write("""<option value=\"True\">\"True\"</option>""")
self.write("""<option value=\"False\">\"False\"</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):
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...</body></html>""")
self.flush()
time.sleep(2)
os.system("sleep 2;./UHRR &")
os._exit(1)
if __name__ == "__main__":
2020-09-27 03:40:13 +00:00
2020-10-02 23:20:12 +00:00
try:
threadloadWavdata = loadWavdata()
threadloadWavdata.start()
CTRX = TRXRIG()
CTRX.setPower(1)
threadticksTRXRIG = ticksTRXRIG()
threadticksTRXRIG.start()
2020-10-08 22:35:49 +00:00
if(config['HAMLIB']['TXautopower']==True):
threadsurveilTRX = threadtimeoutTRXshutdown()
threadsurveilTRX.start()
2020-10-02 23:20:12 +00:00
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']))
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']))
2020-09-26 00:46:56 +00:00
print('http server started')
tornado.ioloop.IOLoop.instance().start()