mirror of
https://github.com/Amateur-Radio-Club-Graz/openWebTrX.git
synced 2025-12-30 00:01:20 +01:00
initial commit
This commit is contained in:
BIN
grafics/openwebtx.png
Executable file
BIN
grafics/openwebtx.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 179 KiB |
13
htdocs/index.html
Normal file
13
htdocs/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/tx/owt.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tx/owt.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="owt_head"> <div id="owt_button"><div class="openwebrx-button" onclick='owt_start();' >connect TX</div></div>
|
||||
<div id="owt_pids"></div></div><div id="owt_msg"></div>
|
||||
</body>
|
||||
</html>
|
||||
44
htdocs/owt.css
Normal file
44
htdocs/owt.css
Normal file
@@ -0,0 +1,44 @@
|
||||
#owt_head
|
||||
{
|
||||
display: inline-block;
|
||||
padding: 0px;
|
||||
vertical-align: top;
|
||||
margin-top: 20px;
|
||||
}
|
||||
#owt_head a
|
||||
{
|
||||
font-size: 11px;
|
||||
text-decoration: underline;
|
||||
color: #ccccff;
|
||||
}
|
||||
#owt_pids
|
||||
{
|
||||
width: 200px;
|
||||
}
|
||||
.owt_pid
|
||||
{
|
||||
width: calc(10% - 10px);
|
||||
height: 5px;
|
||||
display: inline-block;
|
||||
margin-right: 1px;
|
||||
background-color: #e6e7e8;
|
||||
}
|
||||
#owt_button
|
||||
{
|
||||
display: inline-block
|
||||
}
|
||||
#owt_ptt
|
||||
{
|
||||
background: #ff0000;
|
||||
}
|
||||
@media only screen and (max-height: 400px) {
|
||||
#owt_head
|
||||
{
|
||||
margin-top: 3px !important;
|
||||
}
|
||||
#owt_ptt
|
||||
{
|
||||
margin-right: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
207
htdocs/owt.js
Normal file
207
htdocs/owt.js
Normal file
@@ -0,0 +1,207 @@
|
||||
var owt_protocol = 'ws://';
|
||||
if(window.location.toString().indexOf('https://') == 0){
|
||||
owt_protocol = 'wss://';
|
||||
}
|
||||
owt_url1=owt_protocol+window.location.href.split("://")[1]
|
||||
owt_lastslash = owt_url1.lastIndexOf('/');
|
||||
owt_firstslash= owt_url1.indexOf('/',6);
|
||||
owt_url2=owt_url1.substr(0,owt_firstslash)
|
||||
//owt_url2=owt_url1.substr(0,owt_lastslash)
|
||||
owt_ws_url=owt_url2+"/tx/ws/";
|
||||
owt_button_orig=""
|
||||
var owt_webSocket;
|
||||
var owt_mediaRecorder
|
||||
function owt_start_ws(start_audio) {
|
||||
owt_webSocket = new WebSocket(owt_ws_url);
|
||||
owt_webSocket.binaryType = 'blob';
|
||||
|
||||
owt_webSocket.onmessage= function (event) {
|
||||
console.log('Message from server ', event.data);
|
||||
owt_make_msg(event.data);
|
||||
}
|
||||
owt_webSocket.onclose =function (event) {
|
||||
console.log('ws closed :(', event.data);
|
||||
owt_make_button_orig();
|
||||
owt_make_msg('TX connection closed!');
|
||||
owt_destroy_ws()
|
||||
}
|
||||
if(start_audio) {
|
||||
//owt_webSocket.onopen =owt_start_audio();
|
||||
if (MediaRecorder.isTypeSupported('audio/ogg;codecs=opus')) {
|
||||
mimeType="audio/ogg;codecs=opus";
|
||||
} else {
|
||||
mimeType="audio/webm;codecs=opus";
|
||||
}
|
||||
|
||||
//function owt_start_audio(){
|
||||
owt_webSocket.onopen = event => {
|
||||
console.log("[open] Audio Node-RED websocket connection established");
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ audio: true, video: false, channelCount: 1, sampleRate: 12000, sampleSize: 16 })
|
||||
.then(stream => {
|
||||
owt_mediaRecorder = new MediaRecorder(stream, {
|
||||
//audioBitsPerSecond : 128000,
|
||||
audioBitsPerSecond : 64000,
|
||||
//mimeType: 'audio/webm;codecs=opus',
|
||||
//mimeType: 'audio/ogg;codecs=opus',
|
||||
mimeType: mimeType,
|
||||
});
|
||||
owt_mediaRecorder.addEventListener('dataavailable', event => {
|
||||
if (event.data.size > 0) {
|
||||
//console.log(event.data.length)
|
||||
owt_webSocket.send(event.data);
|
||||
}
|
||||
});
|
||||
owt_mediaRecorder.start(100);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function owt_stop_ws() {
|
||||
track=owt_mediaRecorder.stream.getAudioTracks()
|
||||
track.forEach(function(track) { track.stop(); });
|
||||
|
||||
owt_webSocket.close()
|
||||
owt_make_button_orig()
|
||||
}
|
||||
function owt_destroy_ws() {
|
||||
if (typeof owt_mediaRecorder === "object" ) {
|
||||
a=owt_mediaRecorder.stream.getAudioTracks();
|
||||
owt_mediaRecorder.stream.removeTrack(a[0]);
|
||||
owt_mediaRecorder.removeEventListener('dataavilable',Element)
|
||||
owt_mediaRecorder.stop()
|
||||
owt_mediaRecorder=0
|
||||
console.log("audio destroyed")
|
||||
}
|
||||
owt_webSocket=0;
|
||||
console.log("ws destroyed")
|
||||
}
|
||||
function owt_make_button_running() {
|
||||
//PTT button, close link
|
||||
document.getElementById('owt_button').innerHTML="<div class='openwebrx-button' id='owt_ptt' onmousedown='owt_ptt_on();' onmouseup='owt_ptt_off();' ontouchstart='owt_ptt_on();' ontouchend='owt_ptt_off();' >PTT</div> <a onclick='owt_stop_ws();' >disconnect</a>"
|
||||
}
|
||||
function owt_make_button_orig() {
|
||||
//connect button from variable
|
||||
document.getElementById('owt_button').innerHTML=owt_button_orig
|
||||
}
|
||||
function owt_make_msg(data) {
|
||||
//if data contains config:, status: => ignore
|
||||
//else if containts msg: => split and print
|
||||
document.getElementById('owt_msg').innerHTML=""
|
||||
//else print
|
||||
}
|
||||
function owt_start() {
|
||||
//owt_vumeter()
|
||||
owt_button_orig= document.getElementById('owt_button').innerHTML
|
||||
owt_start_ws(1);
|
||||
//owt_start_audio();
|
||||
owt_make_button_running();
|
||||
}
|
||||
|
||||
function owt_ptt_on() {
|
||||
mute=false;
|
||||
toggleMute();
|
||||
owt_webSocket.send("SET:PTT=1")
|
||||
document.getElementById('owt_ptt').style.background="#fff"
|
||||
document.getElementById('owt_ptt').style.color="#f00"
|
||||
}
|
||||
function owt_ptt_off() {
|
||||
mute=true;
|
||||
toggleMute();
|
||||
owt_webSocket.send("SET:PTT=0")
|
||||
document.getElementById('owt_ptt').style.background="#f00"
|
||||
document.getElementById('owt_ptt').style.color="#fff"
|
||||
}
|
||||
//keypress
|
||||
//keydown
|
||||
//keyup
|
||||
//left 37
|
||||
//right 39
|
||||
//up 38
|
||||
//down 40
|
||||
document.addEventListener('keydown', function(e) {
|
||||
var keynum = e.keyCode || e.which;
|
||||
console.log(keynum)
|
||||
if(keynum == 32) {
|
||||
owt_ptt_on()
|
||||
}
|
||||
});
|
||||
document.addEventListener('keyup', function(e) {
|
||||
var keynum = e.keyCode || e.which;
|
||||
console.log("up"+keynum)
|
||||
if(keynum == 32) {
|
||||
owt_ptt_off()
|
||||
}
|
||||
});
|
||||
document.addEventListener('keypress', function(e) {
|
||||
var keynum = e.keyCode || e.which;
|
||||
console.log("press"+keynum)
|
||||
// if(keynum == 32) {
|
||||
// owt_ptt_off()
|
||||
// }
|
||||
});
|
||||
|
||||
//https://stackoverflow.com/questions/33322681/checking-microphone-volume-in-javascript
|
||||
function owt_vumeter() {
|
||||
document.getElementById('owt_pids').innerHTML=`
|
||||
<div class='owt_pid'></div>
|
||||
<div class='owt_pid'></div>
|
||||
<div class='owt_pid'></div>
|
||||
<div class='owt_pid'></div>
|
||||
<div class='owt_pid'></div>
|
||||
<div class='owt_pid'></div>`;
|
||||
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
video: false,
|
||||
})
|
||||
.then(function(stream) {
|
||||
const audioContext = new AudioContext();
|
||||
const analyser = audioContext.createAnalyser();
|
||||
const microphone = audioContext.createMediaStreamSource(stream);
|
||||
const scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1);
|
||||
analyser.smoothingTimeConstant = 0.8;
|
||||
analyser.fftSize = 1024;
|
||||
|
||||
microphone.connect(analyser);
|
||||
analyser.connect(scriptProcessor);
|
||||
scriptProcessor.connect(audioContext.destination);
|
||||
scriptProcessor.onaudioprocess = function() {
|
||||
const array = new Uint8Array(analyser.frequencyBinCount);
|
||||
analyser.getByteFrequencyData(array);
|
||||
const arraySum = array.reduce((a, value) => a + value, 0);
|
||||
const average = arraySum / array.length;
|
||||
//console.log(Math.round(average));
|
||||
owt_colorPids(average);
|
||||
};
|
||||
})
|
||||
.catch(function(err) {
|
||||
/* handle the error */
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
function owt_colorPids(vol) {
|
||||
//console.log('vu'+vol);
|
||||
const allPids = [...document.querySelectorAll('.owt_pid')];
|
||||
const numberOfPidsToColor = Math.round(vol / 20);
|
||||
const pidsToColor = allPids.slice(0, numberOfPidsToColor);
|
||||
for (const pid of allPids) {
|
||||
pid.style.backgroundColor = "#e6e7e8";
|
||||
}
|
||||
for (const pid of pidsToColor) {
|
||||
//console.log(pid[i]);
|
||||
pid.style.backgroundColor = "#00ce00";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO
|
||||
// //on websocket rx
|
||||
// display MSG (on air, problem [rigctl, high-swr, other user took over], ....)
|
||||
// on websocket close => display msg
|
||||
// fkt:
|
||||
// display msg
|
||||
// get parameters from ws-rx and update gui if gui present
|
||||
// send parameters from gui to ws (relay shift, ctcss, power, external IO)
|
||||
|
||||
635
tx.py
Normal file
635
tx.py
Normal file
@@ -0,0 +1,635 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
|
||||
This file is part of OpenWeb-TX (owt),
|
||||
an open-source web transceiver software with a web UI.
|
||||
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
||||
Copyright (c) 2022 by LSP/ARCG
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
|
||||
https://gist.github.com/artizirk/04eb23d957d7916c01ca632bb27d5436
|
||||
https://websockets.readthedocs.io/en/stable/index.html
|
||||
https://stackoverflow.com/questions/53689602/python-3-websockets-server-http-server-run-forever-serve-forever
|
||||
|
||||
|
||||
TODO:
|
||||
script for putting webrx into dummy mode if 1h without openwebrx
|
||||
"""
|
||||
import code
|
||||
import importlib
|
||||
import _thread
|
||||
import time
|
||||
import datetime
|
||||
import subprocess
|
||||
import os
|
||||
from socketserver import ThreadingMixIn
|
||||
import fcntl
|
||||
import random
|
||||
import threading
|
||||
import sys
|
||||
import traceback
|
||||
from collections import namedtuple
|
||||
|
||||
import ctypes
|
||||
import multiprocessing
|
||||
import psutil
|
||||
|
||||
import signal
|
||||
import socket
|
||||
from functools import reduce
|
||||
|
||||
#httpnew 202201
|
||||
import asyncio
|
||||
import functools
|
||||
import websockets
|
||||
from http import HTTPStatus
|
||||
|
||||
#pypy compatibility
|
||||
try: import dl
|
||||
except: pass
|
||||
try: import __pypy__
|
||||
except: pass
|
||||
pypy="__pypy__" in globals()
|
||||
|
||||
|
||||
config_web_port=8074
|
||||
config_audio_fifo="test_fifo"
|
||||
#config_audio_out_cmd= "opusdec --force-wav - - | aplay -D hw:1,0 -"
|
||||
#config_audio_out_cmd= "ffmpeg -y -i - -ac 2 -f alsa hw:1,0"
|
||||
config_audio_out_cmd= "ffmpeg -y -re -flags low_delay -thread_queue_size 1024 -i - -ac 2 -f alsa hw:1,0"
|
||||
#config_audio_out_cmd= "ffmpeg -y -i - -ac 2 -f pulse '0'"
|
||||
#config_audio_out_cmd= "ffmpeg -y -i - -filter_complex \"[0:a][0:a]amerge=inputs=2[a]\" -map \"[a]\" -c 2 -f alsa hw:1,0"
|
||||
#"ffmpeg -y -i - -ac 1 -f wav - | paplay --latency-msec 1"
|
||||
#https://stackoverflow.com/questions/66843134/windows-ffmpeg-send-audio-to-sound-cards-output ffplay?
|
||||
#"mpv -"
|
||||
#"ffmpeg -y -ac 1 -i - -f wav - | paplay --latency-msec 1"
|
||||
#"cat /dev/stdin >> test2.opus"
|
||||
#ffmpeg -loglevel debug -y -ac 1 -i test2.opus -f wav - | paplay
|
||||
#"cat "+config_audio_fifo+" | "
|
||||
#cmd decoding audio + playback on system
|
||||
#signal-chain gets input from stdin
|
||||
config_rig={}
|
||||
config_rig['interface']= "/dev/ttyUSB0" #serial Port of CAT interface of TRX
|
||||
config_rig['baud']= 9600 #baud rate of serial port
|
||||
config_rig['mod_fm']= "FM 0" #modulation set for FM-TX
|
||||
config_rig['mod_lsb']= "LSB 0" #modulation set for LSB-TX
|
||||
config_rig['mod_usb']= "USB 0" #modulation set for USB-TX
|
||||
#config_rig['rigctl_cmd']= "rigctld -m 1022 -s 9600 -r /dev/ttyUSB0 -P RTS -t 4532" #FT857
|
||||
config_rig['rigctl_cmd']= "rigctld -m 1023 -s 9600 -r /dev/ttyUSB0 -P RTS -t 4532" #FT897
|
||||
#config_rig['rigctl_cmd']= "rigctld -m 1023 -s 9600 -r /dev/ttyUSB0 -P RTS -t 4532" #FT817
|
||||
#"rigctld -m 120 -s 9600 -r /dev/ttyACM0 --dcd-type=NONE -P RTS" #FT817
|
||||
#command to start rigctl software
|
||||
# rigctl -l lists TRX list für '-m'
|
||||
|
||||
config_cmd={}
|
||||
config_cmd['TX']= "/home/pi/rf-route.sh tx" #cmd executed on TX /PTT-ON
|
||||
config_cmd['RX']= "/home/pi/rf-route.sh rx" #cmd executed on RX /PTT-OFF
|
||||
#config_cmd['TX_PWR_ON']= "echo 'on'" #cmd executed on Session start /TX-Power-ON
|
||||
config_cmd['TX_PWR_ON']= "/home/pi/rf-route.sh on" #cmd executed on Session start /TX-Power-ON
|
||||
#config_cmd['TX_PWR_OFF']= "echo 'off'" #cmd executed on Session start /TX-Power-OFF
|
||||
config_cmd['TX_PWR_OFF']= "/home/pi/rf-route.sh off" #cmd executed on Session start /TX-Power-OFF
|
||||
|
||||
|
||||
|
||||
|
||||
global openwebrx
|
||||
openwebrx={} # init for parameter handling
|
||||
openwebrx['modulation']= "nfm"
|
||||
global session_sempathor
|
||||
session_semaphor= 0
|
||||
|
||||
|
||||
def handle_signal_to_del(sig, frame):
|
||||
global spectrum_dsp
|
||||
if sig == signal.SIGUSR1:
|
||||
print("[openwebrx] Verbose status information on USR1 signal")
|
||||
print()
|
||||
print("time.time() =", time.time())
|
||||
print("clients_mutex.locked() =", clients_mutex.locked())
|
||||
print("clients_mutex_locker =", clients_mutex_locker)
|
||||
if server_fail: print("server_fail = ", server_fail)
|
||||
print("spectrum_thread_watchdog_last_tick =", spectrum_thread_watchdog_last_tick)
|
||||
print()
|
||||
print("clients:",len(clients))
|
||||
for client in clients:
|
||||
print()
|
||||
for key in client._fields:
|
||||
print("\t%s = %s"%(key,str(getattr(client,key))))
|
||||
elif sig == signal.SIGUSR2:
|
||||
code.interact(local=globals())
|
||||
else:
|
||||
print("[openwebrx] Ctrl+C: aborting.")
|
||||
cleanup_clients(True)
|
||||
spectrum_dsp.stop()
|
||||
os._exit(1) #not too graceful exit
|
||||
|
||||
## function start_openwebrx
|
||||
#
|
||||
# function for openwebrx thread, starts openwebrx
|
||||
# reads stdin from stdout of openwebrx for frequency and modulation
|
||||
def start_openwebrx():
|
||||
global openwebrx
|
||||
p = subprocess.Popen(['python3 -u openwebrx.py 2>&1 '],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
bufsize=0,
|
||||
shell=True,
|
||||
stdin=None,
|
||||
universal_newlines=True,
|
||||
cwd="../")
|
||||
modb=""
|
||||
print("startopenwebrx")
|
||||
for line in iter(p.stdout.readline, b''):
|
||||
# Sampling at 2400000 S/s.
|
||||
# Tuned to 144250000 Hz.
|
||||
# [openwebrx-httpd:ws,1] command: SET mod=nfm low_cut=-4000 high_cut=4000 offset_freq=0
|
||||
# csdr_s shift_addition_cc: reinitialized to 0.128407
|
||||
# csdr_s bandpass_fir_fft_cc: filter initialized, low_cut = -0.24875, high_cut = -0.00704792
|
||||
|
||||
if "sampling_rate:" in line:
|
||||
sampl= line.split("sampling_rate:",1)[1]
|
||||
sampl= int(sampl)
|
||||
openwebrx['samplerate']=sampl
|
||||
print ("S:"+str(sampl))
|
||||
|
||||
elif "center_freq:" in line:
|
||||
freq_cent= line.split("center_freq:",1)[1]
|
||||
freq_cent= int(freq_cent)
|
||||
openwebrx['centerfreq']=freq_cent
|
||||
print ("T:"+str(freq_cent))
|
||||
|
||||
elif "SET mod=" in line:
|
||||
moda= line.split("SET mod=",1)[1]
|
||||
modb= moda.split(" ")[0]
|
||||
print ("M:"+modb)
|
||||
|
||||
elif "csdr_s shift_addition_cc: reinitialized to" in line:
|
||||
offset= float(line.split("cc: reinitialized to",1)[1])
|
||||
openwebrx['offset']=offset
|
||||
print ("s:"+str(offset) )
|
||||
|
||||
if modb == "ssb":
|
||||
if "csdr_s bandpass_fir_fft_cc: filter initialized," in line:
|
||||
low_cuta= line.split("low_cut = ",1)[1]
|
||||
low_cut= low_cuta.split(",",1)[0]
|
||||
high_cut= line.split("high_cut = ",1)[1]
|
||||
#print("low_cut"+low_cut)
|
||||
#print("high_cut"+high_cut)
|
||||
low=abs(float(low_cut))
|
||||
high=abs(float(high_cut))
|
||||
if (low > high):
|
||||
mod="lsb"
|
||||
print("lsb")
|
||||
else:
|
||||
mod="usb"
|
||||
print("usb")
|
||||
openwebrx['modulation']=mod
|
||||
else:
|
||||
openwebrx['modulation']=modb
|
||||
|
||||
## function asyncWorker
|
||||
#
|
||||
# loop to handle anything for tx operation that might consume time and thereby
|
||||
# delay audio samples,
|
||||
# quick and dirty communication over global variables "aw_*"
|
||||
def asyncWorker():
|
||||
global process_audio, process_rigctl, rigctl_thread, socket_rigctl
|
||||
global aw_trx_on, aw_trx_off, aw_rx, aw_tx, aw_ptt_on, aw_ptt_off
|
||||
#global vars, tcp client, rigctl server
|
||||
#start and end rigctl
|
||||
#connect tcp client to rigtctl
|
||||
#on / off tx
|
||||
#rx / tx switch
|
||||
#anything else that needs time
|
||||
aw_trx_on= 0
|
||||
aw_trx_off= 0
|
||||
aw_rx= 0
|
||||
aw_tx= 0
|
||||
aw_ptt_on= 0
|
||||
aw_ptt_off= 0
|
||||
while True:
|
||||
a=0
|
||||
#if tx schould be switched on
|
||||
# wait x start rigctl
|
||||
if aw_trx_on:
|
||||
aw_trx_on= 0
|
||||
print("AW: trx on")
|
||||
try:
|
||||
process_txpwr = subprocess.Popen((config_cmd['TX_PWR_ON']), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
except:
|
||||
pass
|
||||
#### BEGIN RIGCTL #####
|
||||
try:
|
||||
process_rigctl = subprocess.Popen([config_rig['rigctl_cmd']], stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
except:
|
||||
pass
|
||||
time.sleep(1.2)
|
||||
try:
|
||||
rigctl_pid= process_rigctl.pid
|
||||
rigctl_thread=psutil.Process(rigctl_pid)
|
||||
time.sleep(1.8)
|
||||
print("rigctl started")
|
||||
socket_rigctl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_address = ('127.0.0.1', 4532)
|
||||
print('rigctld connecting to {} port {}'.format(*server_address))
|
||||
socket_rigctl.connect(server_address)
|
||||
socket_rigctl.sendall(b'M LSB 0\n\n')
|
||||
print("AW: trx on")
|
||||
except:
|
||||
pass
|
||||
#if tx off, take care of rigctl
|
||||
#end tcp client
|
||||
if aw_trx_off:
|
||||
aw_trx_off= 0
|
||||
try:
|
||||
socket_rigctl.sendall(("T 0\n").encode())
|
||||
time.sleep(1)
|
||||
socket_rigctl.close()
|
||||
except:
|
||||
pass
|
||||
if rigctl_thread.is_running():
|
||||
children=rigctl_thread.children(recursive=True)
|
||||
for child in children:
|
||||
try:
|
||||
child.kill()
|
||||
except:
|
||||
pass
|
||||
process_txpwr = subprocess.Popen((config_cmd['TX_PWR_OFF']), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
print("AW: trx off")
|
||||
|
||||
#set tx, relais,
|
||||
if aw_tx:
|
||||
aw_tx= 0
|
||||
process_tx = subprocess.Popen((config_cmd['TX']), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
print("AW: relais to TRX")
|
||||
|
||||
|
||||
#set rx, relais
|
||||
if aw_rx:
|
||||
aw_rx= 0
|
||||
process_tx = subprocess.Popen((config_cmd['RX']), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
print("AW: relais to SDR")
|
||||
|
||||
#set ptt on, relais, rigctl
|
||||
if aw_ptt_on:
|
||||
aw_ptt_on= 0
|
||||
process_tx = subprocess.Popen((config_cmd['TX']), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
time.sleep(0.27)
|
||||
socket_rigctl.sendall(("T 1\n").encode())
|
||||
print("AW: ptt on")
|
||||
|
||||
#set ptt of, rigctl, relais
|
||||
if aw_ptt_off:
|
||||
aw_ptt_off=0
|
||||
socket_rigctl.sendall(("T 0\n").encode())
|
||||
time.sleep(0.15)
|
||||
process_tx = subprocess.Popen((config_cmd['RX']), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
print("AW: ptt off")
|
||||
|
||||
|
||||
#if relais only, to it
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
|
||||
receiver_failed=spectrum_thread_watchdog_last_tick=rtl_thread=spectrum_dsp=server_fail=None
|
||||
|
||||
## function main
|
||||
#
|
||||
# main function, everything gets started here
|
||||
# openwebrx
|
||||
# asynchron worker
|
||||
# webserver
|
||||
# websocket
|
||||
def main():
|
||||
global process_audio
|
||||
#global clients, clients_mutex, pypy, lock_try_time, avatar_ctime, cfg, logs
|
||||
#global serverfail, rtl_thread, spectrum_thread, ws_kill, sdr_selected, spectrum_kill
|
||||
|
||||
#Change process name to "openwebrx" (to be seen in ps)
|
||||
try:
|
||||
for libcpath in ["/lib/i386-linux-gnu/libc.so.6","/lib/libc.so.6"]:
|
||||
if os.path.exists(libcpath):
|
||||
libc = dl.open(libcpath)
|
||||
libc.call("prctl", 15, "openwebrx-tx", 0, 0, 0)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
#check for external programs #todo, add rigctld, ffmpeg, ...
|
||||
if os.system("csdr_s 2> /dev/null") == 32512: #check for csdr
|
||||
print("[openwebrx-main] You need to install \"csdr_s\" to run OpenWebRX!\n")
|
||||
return
|
||||
if os.system("nmux_s --help 2> /dev/null") == 32512: #check for nmux
|
||||
print("[openwebrx-main] You need to install an up-to-date version of \"csdr\" that contains the \"nmux\" tool to run OpenWebRX! Please upgrade \"csdr\"!\n")
|
||||
return
|
||||
#if start_sdr() == False: #moved out for interactive sdr change
|
||||
# return
|
||||
|
||||
#Initialize clients
|
||||
clients=[]
|
||||
|
||||
#threading.Thread(target = measure_thread_function, args = ()).start()
|
||||
|
||||
#### BEGIN OPENWEBRX ####
|
||||
thread_openwebrx = threading.Thread(target = start_openwebrx, args= ()).start()
|
||||
#### END OPENWEBRX ####
|
||||
|
||||
#### BEGIN AsynWorker ####
|
||||
thread_asyncWorker = threading.Thread(target = asyncWorker, args= ()).start()
|
||||
#### END AsyncWorker ####
|
||||
|
||||
|
||||
####Start HTTP and WS ####
|
||||
# set first argument for the handler to current working directory
|
||||
handler = functools.partial(process_request, os.getcwd())
|
||||
start_server = websockets.serve(ws_process, "0.0.0.0", config_web_port,
|
||||
process_request=handler)
|
||||
print("Running server at http://127.0.0.1 port:"+str(config_web_port))
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
try:
|
||||
print("running")
|
||||
asyncio.get_event_loop().run_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("server crashed")
|
||||
|
||||
|
||||
def send_302_to_del(self,what):
|
||||
self.send_response(302)
|
||||
self.send_header('Content-type','text/html')
|
||||
self.send_header("Location", "{0}".format(what))
|
||||
self.end_headers()
|
||||
mime='text/html'
|
||||
data="<html><body><h1>Object moved</h1>Please <a href=\"/{0}\">click here</a> to continue.</body></html>".format(what)
|
||||
|
||||
response_headers = [
|
||||
('Server', 'asyncio websocket server'),
|
||||
('Connection', 'close'),
|
||||
]
|
||||
|
||||
response_headers.append(('Content-Type', mime))
|
||||
response_headers.append(("Location", "{0}".format(what)))
|
||||
|
||||
# Read the whole file into memory and send it out
|
||||
#body = open(full_path, 'rb').read()
|
||||
response_headers.append(('Content-Length', str(len(data))))
|
||||
return HTTPStatus.FOUND, response_headers, data
|
||||
|
||||
|
||||
#def do_GET(self):
|
||||
|
||||
## function freq_shift
|
||||
#
|
||||
# decides if frequency shift for repeater operation is needed
|
||||
# returns new frequency
|
||||
def freq_shift(f,mod):
|
||||
if (mod == "fm"):
|
||||
#10m -100e3
|
||||
if (f >= 29620e3 and f<29690e3):
|
||||
f=f-100e3
|
||||
return f
|
||||
|
||||
#6m -600e3
|
||||
if (f >= 51810e3 and f<51990e3):
|
||||
f=f-600e3
|
||||
return f
|
||||
|
||||
#2m -600e3
|
||||
if (f >= 145500e3 and f<146000e3):
|
||||
f=f-600e3
|
||||
return f
|
||||
|
||||
#70cmi -7.6e6
|
||||
if (f >= 438,6375e6 and f<439.6e6):
|
||||
f=f-7.6e6
|
||||
return f
|
||||
#439.8..400 is 9.4
|
||||
|
||||
#23cm -28e6
|
||||
if (f >= 1298.025e6 and f<1298.975e6):
|
||||
f=f-28e6
|
||||
return f
|
||||
else:
|
||||
return f
|
||||
|
||||
## function write_data
|
||||
#
|
||||
# sends http response for http server
|
||||
def write_data(path,mime_type,data): #makes http header and content
|
||||
#data=data.encode()
|
||||
response_headers = [
|
||||
('Server', 'asyncio websocket server'),
|
||||
('Connection', 'close'),
|
||||
]
|
||||
|
||||
response_headers.append(('Content-Type', mime_type))
|
||||
|
||||
response_headers.append(('Content-Length', str(len(data))))
|
||||
return HTTPStatus.OK, response_headers, data
|
||||
|
||||
## function ws_process
|
||||
#
|
||||
# handles http Websocket requests
|
||||
# main task compressed audio => stdout, must not be delayed!
|
||||
async def ws_process(websocket, path):
|
||||
global dsp_plugin, clients_mutex, clients, avatar_ctime, sw_version, receiver_failed, ws_kill, sdr_selected, process_rigctl
|
||||
global session_semaphor
|
||||
global aw_trx_on, aw_trx_off, aw_rx, aw_tx, aw_ptt_on, aw_ptt_off
|
||||
|
||||
global process_audio, rigctl_thread, socket_rigctl, openwebrx
|
||||
print("New WebSocket connection from", websocket.remote_address)
|
||||
|
||||
path=path.replace("..","")
|
||||
path_temp_parts=path.split("?")
|
||||
path=path_temp_parts[0]
|
||||
client_address= websocket.remote_address[0]
|
||||
session_is_mine= 0 #1 if this is the active session
|
||||
try:
|
||||
if path[:4]=="/ws/":
|
||||
loopstat=0
|
||||
print("[openwebrx-ws] Client requested WebSocket connection")
|
||||
try:
|
||||
#check if rigctl is still running, othervise return
|
||||
#TODO send error ws-msg to client
|
||||
if session_semaphor == 0 and path[:4]=="/ws/":
|
||||
session_semaphor= 1
|
||||
session_is_mine= 1
|
||||
#elif path[:4] != "/wc/":
|
||||
# return
|
||||
if session_is_mine :
|
||||
process_audio = subprocess.Popen((config_audio_out_cmd), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
|
||||
print("process_audio started")
|
||||
#switch tx power on
|
||||
aw_trx_on= 1
|
||||
else:
|
||||
print("WS wrong session, semaphor!")
|
||||
raise ValueError('WS wrong session, semphor!')
|
||||
#send default parameters
|
||||
#startstr=("MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} audio_compression={4} fft_compression={5} max_clients={6} sdr={7} setup".format(str(cfg.shown_center_freq[sdr_selected]),str(cfg.samp_rate[sdr_selected]),cfg.fft_size,cfg.fft_fps,cfg.audio_compression,cfg.fft_compression,cfg.max_clients,sdr_selected)).encode()
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
rdata= await asyncio.wait_for(websocket.recv(), timeout=0.01)
|
||||
except asyncio.TimeoutError:
|
||||
rdata = 0
|
||||
|
||||
if (rdata != 0):
|
||||
|
||||
if rdata[:3]=="SET":
|
||||
print(rdata)
|
||||
pairs=rdata[4:].split(" ")
|
||||
for pair in pairs:
|
||||
param_name, param_value = pair.split("=")
|
||||
if param_name == "PTT" and session_is_mine:
|
||||
if process_rigctl.poll() :
|
||||
print("no rigctl!")
|
||||
raise ValueError('No rigctl!')
|
||||
return
|
||||
|
||||
print("WS start pttW")
|
||||
freq= float(openwebrx['centerfreq'])-float(openwebrx['samplerate'])*float(openwebrx['offset'])
|
||||
mod=""
|
||||
if (openwebrx['modulation'] == "nfm"):
|
||||
mod= config_rig['mod_fm']
|
||||
elif (openwebrx['modulation'] == "lsb"):
|
||||
mod= config_rig['mod_lsb']
|
||||
elif (openwebrx['modulation'] == "usb"):
|
||||
mod= config_rig['mod_usb']
|
||||
if (mod != ""): #if modulation valid
|
||||
print("F "+str(freq))
|
||||
print("center "+str(openwebrx['centerfreq']))
|
||||
print("samp "+str(openwebrx['samplerate']))
|
||||
print("offset "+str(openwebrx['offset']))
|
||||
#TODO
|
||||
if (int(param_value)):
|
||||
freq=freq_shift(freq,mod)
|
||||
socket_rigctl.sendall(("F "+str(freq)+"\n").encode())
|
||||
socket_rigctl.sendall(("M "+mod+"\n").encode())
|
||||
aw_ptt_on= 1
|
||||
else:
|
||||
aw_ptt_off=1
|
||||
else:
|
||||
error_msg="modulation not valid for TX"
|
||||
print(error_msg)
|
||||
elif param_name == "low_cut" and -filter_limit <= int(param_value) <= filter_limit:
|
||||
a=3
|
||||
#TODO
|
||||
# repater shift, enable/disable
|
||||
# power
|
||||
else:
|
||||
print("[openwebrx-httpd:ws] invalid parameter")
|
||||
else:
|
||||
process_audio.stdin.write(rdata)
|
||||
|
||||
except:
|
||||
if session_is_mine:
|
||||
process_audio.stdin.close()
|
||||
process_audio.send_signal(signal.SIGINT)
|
||||
aw_trx_off= 1
|
||||
session_semaphor= 0
|
||||
process_audio = subprocess.Popen(("killall -9 ffmpeg"), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
print("[openwebrx-httpd:ws] exception: ",exc_type,exc_value)
|
||||
traceback.print_tb(exc_traceback) #TODO digimodes
|
||||
if exc_value[0]==32: #"broken pipe", client disconnected
|
||||
print("broken ws, client disconnected")
|
||||
elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected
|
||||
print("resource unavailable on recv")
|
||||
else:
|
||||
print ("[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value)
|
||||
|
||||
try:
|
||||
print("do_GET /ws/ delete disconnected")
|
||||
if session_is_mine:
|
||||
process_audio.stdin.close()
|
||||
process_audio.send_signal(signal.SIGINT)
|
||||
aw_trx_off= 1
|
||||
session_semaphor= 0
|
||||
process_audio = subprocess.Popen(("killall -9 ffmpeg"), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
except:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
print("[openwebrx-httpd:ws] client cannot be closed: ",exc_type,exc_value)
|
||||
traceback.print_tb(exc_traceback)
|
||||
finally:
|
||||
cmr()
|
||||
return
|
||||
except:
|
||||
print("[openwebrx-httpd:ws] error (@outside)")
|
||||
if session_is_mine:
|
||||
process_audio.stdin.close()
|
||||
process_audio.send_signal(signal.SIGINT)
|
||||
aw_trx_off= 1
|
||||
session_semaphor= 0
|
||||
process_audio = subprocess.Popen(("killall -9 ffmpeg"), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True )
|
||||
|
||||
return
|
||||
|
||||
|
||||
## function process request
|
||||
#
|
||||
# handles http GET requests
|
||||
async def process_request(sever_root, path, request_headers):
|
||||
global dsp_plugin, clients_mutex, clients, avatar_ctime, sw_version, receiver_failed
|
||||
if "Upgrade" in request_headers:
|
||||
return # Probably a WebSocket connection
|
||||
#return write_data(path,'text/html','bla')
|
||||
rootdir = 'htdocs'
|
||||
mime_type= 'text/html'
|
||||
path=path.replace("..","")
|
||||
path_temp_parts=path.split("?")
|
||||
path=path_temp_parts[0]
|
||||
user_agent= request_headers._list[1][1] # 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0'
|
||||
client_address=request_headers._list[0][1] #'127.0.0.1:8073'
|
||||
request_param=path_temp_parts[1] if(len(path_temp_parts)>1) else ""
|
||||
try:
|
||||
if path=="/" or path=="":
|
||||
path="/index.html"
|
||||
# there's even another cool tip at http://stackoverflow.com/questions/4419650/how-to-implement-timeout-in-basehttpserver-basehttprequesthandler-python
|
||||
|
||||
else:
|
||||
f=open(rootdir+path, "rb")
|
||||
data=f.read()
|
||||
extension=path[(len(path)-4):len(path)]
|
||||
extension=extension[2:] if extension[1]=='.' else extension[1:]
|
||||
if(("wrx","html","htm","html").count(extension)):
|
||||
mime_type= 'text/html'
|
||||
elif(extension=="js"):
|
||||
mime_type= 'text/javascript'
|
||||
elif(extension=="css"):
|
||||
mime_type= 'text/css'
|
||||
f.close()
|
||||
return write_data(path,mime_type,data)
|
||||
|
||||
return
|
||||
except IOError:
|
||||
#self.send_error(404, 'Invalid path.')
|
||||
return HTTPStatus.NOT_FOUND, [], b'404 NOT FOUND'
|
||||
|
||||
except:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
print("[openwebrx-httpd] error (@outside):", exc_type, exc_value)
|
||||
traceback.print_tb(exc_traceback)
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user