diff --git a/htdocs/index.wrx b/htdocs/index.wrx index 2cd64da..885e3ef 100644 --- a/htdocs/index.wrx +++ b/htdocs/index.wrx @@ -22,23 +22,16 @@ OpenWebRX | Open Source SDR Web App for Everyone! - --> diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index c7fd7c0..6efc93d 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -79,7 +79,8 @@ is_chrome = /Chrome/.test(navigator.userAgent); function init_rx_photo() { - e("webrx-top-photo-clip").style.maxHeight=rx_photo_height.toString()+"px"; + var clip = e("webrx-top-photo-clip"); + clip.style.maxHeight=clip.clientHeight+"px"; window.setTimeout(function() { animate(e("webrx-rx-photo-title"),"opacity","",1,0,1,500,30); },1000); window.setTimeout(function() { animate(e("webrx-rx-photo-desc"),"opacity","",1,0,1,500,30); },1500); window.setTimeout(function() { close_rx_photo() },2500); @@ -1145,6 +1146,46 @@ var COMPRESS_FFT_PAD_N=10; //should be the same as in csdr.c function on_ws_recv(evt) { + if (typeof evt.data == 'string') { + // text messages + if (evt.data.substr(0, 16) == "CLIENT DE SERVER") { + divlog("Server acknowledged WebSocket connection."); + } else { + try { + json = JSON.parse(evt.data) + switch (json.type) { + case "config": + config = json.value; + window.waterfall_colors = config.waterfall_colors; + window.waterfall_min_level_default = config.waterfall_min_level; + window.waterfall_max_level_default = config.waterfall_max_level; + window.waterfall_auto_level_margin = config.waterfall_auto_level_margin; + waterfallColorsDefault(); + + bandwidth = config.samp_rate; + center_freq = config.shown_center_freq; + fft_size = config.fft_size; + fft_fps = config.fft_fps; + audio_compression = config.audio_compression; + divlog( "Audio stream is "+ ((audio_compression=="adpcm")?"compressed":"uncompressed")+"." ) + fft_compression = config.fft_compression; + divlog( "FFT stream is "+ ((fft_compression=="adpcm")?"compressed":"uncompressed")+"." ) + max_clients_num = config.max_clients; + waterfall_init(); + audio_preinit(); + break; + default: + console.warn('received message of unknown type', json); + } + } catch (e) { + // don't lose exception + console.error(e) + } + } + } else if (evt.data instanceof ArrayBuffer) { + // binary messages + } + return if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; } // debug_ws_data_received+=evt.data.byteLength/1000; @@ -1152,8 +1193,6 @@ function on_ws_recv(evt) first3Chars=first4Chars.slice(0,3); if(first3Chars=="CLI") { - var stringData=arrayBufferToString(evt.data); - if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Server acknowledged WebSocket connection."); } if(first3Chars=="AUD") @@ -1574,7 +1613,7 @@ function parsehash() if(harr[0]=="mute") toggleMute(); else if(harr[0]=="mod") starting_mod = harr[1]; else if(harr[0]=="sql") - { + { config e("openwebrx-panel-squelch").value=harr[1]; updateSquelch(); } @@ -1692,14 +1731,10 @@ String.prototype.startswith=function(str){ return this.indexOf(str) == 0; }; //h function open_websocket() { - //if(ws_url.startswith("ws://localhost:")&&window.location.hostname!="127.0.0.1"&&window.location.hostname!="localhost") - //{ - //divlog("Server administrator should set server_hostname correctly, because it is left as \"localhost\". Now guessing hostname from page URL.",1); - ws_url="ws://"+(window.location.origin.split("://")[1])+"/ws/"; //guess automatically -> now default behaviour - //} + ws_url="ws://"+(window.location.origin.split("://")[1])+"/ws/"; //guess automatically -> now default behaviour if (!("WebSocket" in window)) divlog("Your browser does not support WebSocket, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser."); - ws = new WebSocket(ws_url+client_id); + ws = new WebSocket(ws_url); ws.onopen = on_ws_opened; ws.onmessage = on_ws_recv; ws.onclose = on_ws_closed; @@ -2196,7 +2231,7 @@ function openwebrx_init() //Synchronise volume with slider updateVolume(); - waterfallColorsDefault(); + } function iosPlayButtonClick() diff --git a/owrx/config.py b/owrx/config.py new file mode 100644 index 0000000..7e4e7e5 --- /dev/null +++ b/owrx/config.py @@ -0,0 +1,23 @@ +class Property(object): + def __init__(self, value = None): + self.value = value + def getValue(self): + return self.value + def setValue(self, value): + self.value = value + +class PropertyManager(object): + sharedInstance = None + @staticmethod + def getSharedInstance(): + if PropertyManager.sharedInstance is None: + PropertyManager.sharedInstance = PropertyManager() + return PropertyManager.sharedInstance + + def __init__(self): + self.properties = {} + + def getProperty(self, name): + if not name in self.properties: + self.properties[name] = Property() + return self.properties[name] diff --git a/owrx/controllers.py b/owrx/controllers.py index c7989c1..12a4aa2 100644 --- a/owrx/controllers.py +++ b/owrx/controllers.py @@ -1,4 +1,6 @@ import mimetypes +from owrx.websocket import WebSocketConnection +from owrx.config import PropertyManager class Controller(object): def __init__(self, handler, matches): @@ -12,16 +14,6 @@ class Controller(object): if (type(content) == str): content = content.encode() self.handler.wfile.write(content) - def serve_file(self, file): - try: - f = open('htdocs/' + file, 'rb') - data = f.read() - f.close() - - (content_type, encoding) = mimetypes.MimeTypes().guess_type(file) - self.send_response(data, content_type = content_type) - except FileNotFoundError: - self.send_response("file not found", code = 404) def render_template(self, template, **variables): f = open('htdocs/' + template) data = f.read() @@ -38,6 +30,33 @@ class IndexController(Controller): self.render_template("index.wrx") class AssetsController(Controller): + def serve_file(self, file): + try: + f = open('htdocs/' + file, 'rb') + data = f.read() + f.close() + + (content_type, encoding) = mimetypes.MimeTypes().guess_type(file) + self.send_response(data, content_type = content_type) + except FileNotFoundError: + self.send_response("file not found", code = 404) def handle_request(self): filename = self.matches.group(1) - self.serve_file(filename) \ No newline at end of file + self.serve_file(filename) + + +class WebSocketController(Controller): + def handle_request(self): + conn = WebSocketConnection(self.handler) + conn.send("CLIENT DE SERVER openwebrx.py") + + config = {} + pm = PropertyManager.getSharedInstance() + + for key in ["waterfall_colors", "waterfall_min_level", "waterfall_max_level", "waterfall_auto_level_margin", + "shown_center_freq", "samp_rate", "fft_size", "fft_fps", "audio_compression", "fft_compression", + "max_clients"]: + + config[key] = pm.getProperty(key).getValue() + + conn.send({"type":"config","value":config}) diff --git a/owrx/http.py b/owrx/http.py index b5ac0ae..ab4399b 100644 --- a/owrx/http.py +++ b/owrx/http.py @@ -1,4 +1,4 @@ -from owrx.controllers import StatusController, IndexController, AssetsController +from owrx.controllers import StatusController, IndexController, AssetsController, WebSocketController from http.server import BaseHTTPRequestHandler import re @@ -13,7 +13,8 @@ class Router(object): mappings = [ {"route": "/", "controller": IndexController}, {"route": "/status", "controller": StatusController}, - {"regex": "/static/(.+)", "controller": AssetsController} + {"regex": "/static/(.+)", "controller": AssetsController}, + {"route": "/ws/", "controller": WebSocketController} ] def find_controller(self, path): for m in Router.mappings: diff --git a/owrx/websocket.py b/owrx/websocket.py new file mode 100644 index 0000000..8bd305e --- /dev/null +++ b/owrx/websocket.py @@ -0,0 +1,47 @@ +import base64 +import hashlib +import json + +class WebSocketConnection(object): + def __init__(self, handler): + self.handler = handler + my_headers = self.handler.headers.items() + my_header_keys = list(map(lambda x:x[0],my_headers)) + h_key_exists = lambda x:my_header_keys.count(x) + h_value = lambda x:my_headers[my_header_keys.index(x)][1] + if (not h_key_exists("Upgrade")) or not (h_value("Upgrade")=="websocket") or (not h_key_exists("Sec-WebSocket-Key")): + raise WebSocketException + ws_key = h_value("Sec-WebSocket-Key") + shakey = hashlib.sha1() + shakey.update("{ws_key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11".format(ws_key = ws_key).encode()) + ws_key_toreturn = base64.b64encode(shakey.digest()) + self.handler.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {0}\r\nCQ-CQ-de: HA5KFU\r\n\r\n".format(ws_key_toreturn.decode()).encode()) + + def get_header(self, size, opcode): + ws_first_byte = 0b10000000 | (opcode & 0x0F) + if(size>125): + return bytes([ws_first_byte, 126, (size>>8) & 0xff, size & 0xff]) + else: + # 256 bytes binary message in a single unmasked frame + return bytes([ws_first_byte, size]) + + def send(self, data): + # convenience + if (type(data) == dict): + data = json.dumps(data) + + # string-type messages are sent as text frames + if (type(data) == str): + header = self.get_header(len(data), 1) + self.handler.wfile.write(header) + self.handler.wfile.write(data.encode('utf-8')) + self.handler.wfile.flush() + # anything else as binary + else: + header = self.get_header(len(data), 2) + self.handler.wfile.write(header) + self.handler.wfile.write(data.encode()) + self.handler.wfile.flush() + +class WebSocketException(Exception): + pass diff --git a/server.py b/server.py index 2b14a52..32850a5 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,12 @@ from http.server import HTTPServer from owrx.http import RequestHandler +from owrx.config import PropertyManager + +cfg=__import__("config_webrx") +pm = PropertyManager.getSharedInstance() +for name, value in cfg.__dict__.items(): + if (name.startswith("__")): continue + pm.getProperty(name).setValue(value) server = HTTPServer(('0.0.0.0', 3000), RequestHandler) server.serve_forever()