refactor into more reasonable namespaces

This commit is contained in:
Jakob Ketterl 2019-05-12 15:56:18 +02:00
parent 210fe5352f
commit da37d03104
6 changed files with 254 additions and 246 deletions

View File

@ -1,6 +1,7 @@
from http.server import HTTPServer
from owrx.http import RequestHandler
from owrx.config import PropertyManager, FeatureDetector
from owrx.config import PropertyManager
from owrx.feature import FeatureDetector
from owrx.source import SdrService
from socketserver import ThreadingMixIn
from owrx.sdrhu import SdrHuUpdater

View File

@ -1,8 +1,7 @@
import os
import logging
logger = logging.getLogger(__name__)
class Property(object):
def __init__(self, value = None):
self.value = value
@ -99,67 +98,3 @@ class PropertyManager(object):
continue
self[name] = value
return self
class UnknownFeatureException(Exception):
pass
class RequirementMissingException(Exception):
pass
class FeatureDetector(object):
features = {
"core": [ "csdr", "nmux" ],
"rtl_sdr": [ "rtl_sdr" ],
"sdrplay": [ "rx_tools" ],
"hackrf": [ "hackrf_transfer" ]
}
def is_available(self, feature):
return self.has_requirements(self.get_requirements(feature))
def get_requirements(self, feature):
try:
return FeatureDetector.features[feature]
except KeyError:
raise UnknownFeatureException("Feature \"{0}\" is not known.".format(feature))
def has_requirements(self, requirements):
passed = True
for requirement in requirements:
methodname = "has_" + requirement
if hasattr(self, methodname) and callable(getattr(self, methodname)):
passed = passed and getattr(self, methodname)()
else:
logger.error("detection of requirement {0} not implement. please fix in code!".format(requirement))
return passed
def has_csdr(self):
return os.system("csdr 2> /dev/null") != 32512
def has_nmux(self):
return os.system("nmux --help 2> /dev/null") != 32512
def has_rtl_sdr(self):
return os.system("rtl_sdr --help 2> /dev/null") != 32512
def has_rx_tools(self):
return os.system("rx_sdr --help 2> /dev/null") != 32512
"""
To use a HackRF, compile the HackRF host tools from its "stdout" branch:
git clone https://github.com/mossmann/hackrf/
cd hackrf
git fetch
git checkout origin/stdout
cd host
mkdir build
cd build
cmake .. -DINSTALL_UDEV_RULES=ON
make
sudo make install
"""
def has_hackrf_transfer(self):
# TODO i don't have a hackrf, so somebody doublecheck this.
# TODO also check if it has the stdout feature
return os.system("hackrf_transfer --help 2> /dev/null") != 32512

180
owrx/connection.py Normal file
View File

@ -0,0 +1,180 @@
from owrx.config import PropertyManager
from owrx.source import DspManager, CpuUsageThread, SdrService, ClientReporterThread
import json
import logging
logger = logging.getLogger(__name__)
class OpenWebRxClient(object):
config_keys = ["waterfall_colors", "waterfall_min_level", "waterfall_max_level",
"waterfall_auto_level_margin", "lfo_offset", "samp_rate", "fft_size", "fft_fps",
"audio_compression", "fft_compression", "max_clients", "start_mod",
"client_audio_buffer_size", "start_freq", "center_freq", "mathbox_waterfall_colors",
"mathbox_waterfall_history_length", "mathbox_waterfall_frequency_resolution"]
def __init__(self, conn):
self.conn = conn
ClientReporterThread.getSharedInstance().addClient(self)
self.dsp = None
self.sdr = None
self.configProps = None
pm = PropertyManager.getSharedInstance()
self.setSdr()
# send receiver info
receiver_keys = ["receiver_name", "receiver_location", "receiver_qra", "receiver_asl", "receiver_gps",
"photo_title", "photo_desc"]
receiver_details = dict((key, pm.getPropertyValue(key)) for key in receiver_keys)
self.write_receiver_details(receiver_details)
profiles = [{"name": s.getName() + " " + p["name"], "id":sid + "|" + pid} for (sid, s) in SdrService.getSources().items() for (pid, p) in s.getProfiles().items()]
self.write_profiles(profiles)
CpuUsageThread.getSharedInstance().add_client(self)
def sendConfig(self, key, value):
config = dict((key, self.configProps[key]) for key in OpenWebRxClient.config_keys)
# TODO mathematical properties? hmmmm
config["start_offset_freq"] = self.configProps["start_freq"] - self.configProps["center_freq"]
self.write_config(config)
def setSdr(self, id = None):
next = SdrService.getSource(id)
if (next == self.sdr):
return
self.stopDsp()
if self.configProps is not None:
self.configProps.unwire(self.sendConfig)
self.sdr = next
# send initial config
self.configProps = self.sdr.getProps().collect(*OpenWebRxClient.config_keys).defaults(PropertyManager.getSharedInstance())
self.configProps.wire(self.sendConfig)
self.sendConfig(None, None)
self.sdr.addSpectrumClient(self)
def startDsp(self):
if self.dsp is None:
self.dsp = DspManager(self, self.sdr)
self.dsp.start()
def close(self):
self.stopDsp()
CpuUsageThread.getSharedInstance().remove_client(self)
try:
ClientReporterThread.getSharedInstance().removeClient(self)
except ValueError:
pass
logger.debug("connection closed")
def stopDsp(self):
if self.dsp is not None:
self.dsp.stop()
self.dsp = None
if self.sdr is not None:
self.sdr.removeSpectrumClient(self)
def setParams(self, params):
# only the keys in the protected property manager can be overridden from the web
protected = self.sdr.getProps().collect("samp_rate", "center_freq", "rf_gain", "type") \
.defaults(PropertyManager.getSharedInstance())
for key, value in params.items():
protected[key] = value
def setDspProperties(self, params):
for key, value in params.items():
self.dsp.setProperty(key, value)
def protected_send(self, data):
try:
self.conn.send(data)
# these exception happen when the socket is closed
except OSError:
self.close()
except ValueError:
self.close()
def write_spectrum_data(self, data):
self.protected_send(bytes([0x01]) + data)
def write_dsp_data(self, data):
self.protected_send(bytes([0x02]) + data)
def write_s_meter_level(self, level):
self.protected_send({"type":"smeter","value":level})
def write_cpu_usage(self, usage):
self.protected_send({"type":"cpuusage","value":usage})
def write_clients(self, clients):
self.protected_send({"type":"clients","value":clients})
def write_secondary_fft(self, data):
self.protected_send(bytes([0x03]) + data)
def write_secondary_demod(self, data):
self.protected_send(bytes([0x04]) + data)
def write_secondary_dsp_config(self, cfg):
self.protected_send({"type":"secondary_config", "value":cfg})
def write_config(self, cfg):
self.protected_send({"type":"config","value":cfg})
def write_receiver_details(self, details):
self.protected_send({"type":"receiver_details","value":details})
def write_profiles(self, profiles):
self.protected_send({"type":"profiles","value":profiles})
class WebSocketMessageHandler(object):
def __init__(self):
self.handshake = None
self.client = None
self.dsp = None
def handleTextMessage(self, conn, message):
if (message[:16] == "SERVER DE CLIENT"):
# maybe put some more info in there? nothing to store yet.
self.handshake = "completed"
logger.debug("client connection intitialized")
self.client = OpenWebRxClient(conn)
return
if not self.handshake:
logger.warning("not answering client request since handshake is not complete")
return
try:
message = json.loads(message)
if "type" in message:
if message["type"] == "dspcontrol":
if "action" in message and message["action"] == "start":
self.client.startDsp()
if "params" in message:
params = message["params"]
self.client.setDspProperties(params)
if message["type"] == "config":
if "params" in message:
self.client.setParams(message["params"])
if message["type"] == "setsdr":
if "params" in message:
self.client.setSdr(message["params"]["sdr"])
if message["type"] == "selectprofile":
if "params" in message and "profile" in message["params"]:
profile = message["params"]["profile"].split("|")
self.client.setSdr(profile[0])
self.client.sdr.activateProfile(profile[1])
else:
logger.warning("received message without type: {0}".format(message))
except json.JSONDecodeError:
logger.warning("message is not json: {0}".format(message))
def handleBinaryMessage(self, conn, data):
logger.error("unsupported binary message, discarding")
def handleClose(self, conn):
if self.client:
self.client.close()

View File

@ -1,11 +1,11 @@
import os
import mimetypes
from datetime import datetime
from owrx.websocket import WebSocketConnection
from owrx.config import PropertyManager
from owrx.source import DspManager, CpuUsageThread, SdrService, ClientReporterThread
from owrx.source import ClientReporterThread
from owrx.connection import WebSocketMessageHandler
from owrx.version import openwebrx_version
import json
import os
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
@ -79,180 +79,6 @@ class IndexController(AssetsController):
def handle_request(self):
self.serve_file("index.wrx", "text/html")
class OpenWebRxClient(object):
config_keys = ["waterfall_colors", "waterfall_min_level", "waterfall_max_level",
"waterfall_auto_level_margin", "lfo_offset", "samp_rate", "fft_size", "fft_fps",
"audio_compression", "fft_compression", "max_clients", "start_mod",
"client_audio_buffer_size", "start_freq", "center_freq", "mathbox_waterfall_colors",
"mathbox_waterfall_history_length", "mathbox_waterfall_frequency_resolution"]
def __init__(self, conn):
self.conn = conn
ClientReporterThread.getSharedInstance().addClient(self)
self.dsp = None
self.sdr = None
self.configProps = None
pm = PropertyManager.getSharedInstance()
self.setSdr()
# send receiver info
receiver_keys = ["receiver_name", "receiver_location", "receiver_qra", "receiver_asl", "receiver_gps",
"photo_title", "photo_desc"]
receiver_details = dict((key, pm.getPropertyValue(key)) for key in receiver_keys)
self.write_receiver_details(receiver_details)
profiles = [{"name": s.getName() + " " + p["name"], "id":sid + "|" + pid} for (sid, s) in SdrService.getSources().items() for (pid, p) in s.getProfiles().items()]
self.write_profiles(profiles)
CpuUsageThread.getSharedInstance().add_client(self)
def sendConfig(self, key, value):
config = dict((key, self.configProps[key]) for key in OpenWebRxClient.config_keys)
# TODO mathematical properties? hmmmm
config["start_offset_freq"] = self.configProps["start_freq"] - self.configProps["center_freq"]
self.write_config(config)
def setSdr(self, id = None):
next = SdrService.getSource(id)
if (next == self.sdr):
return
self.stopDsp()
if self.configProps is not None:
self.configProps.unwire(self.sendConfig)
self.sdr = next
# send initial config
self.configProps = self.sdr.getProps().collect(*OpenWebRxClient.config_keys).defaults(PropertyManager.getSharedInstance())
self.configProps.wire(self.sendConfig)
self.sendConfig(None, None)
self.sdr.addSpectrumClient(self)
def startDsp(self):
if self.dsp is None:
self.dsp = DspManager(self, self.sdr)
self.dsp.start()
def close(self):
self.stopDsp()
CpuUsageThread.getSharedInstance().remove_client(self)
try:
ClientReporterThread.getSharedInstance().removeClient(self)
except ValueError:
pass
logger.debug("connection closed")
def stopDsp(self):
if self.dsp is not None:
self.dsp.stop()
self.dsp = None
if self.sdr is not None:
self.sdr.removeSpectrumClient(self)
def setParams(self, params):
# only the keys in the protected property manager can be overridden from the web
protected = self.sdr.getProps().collect("samp_rate", "center_freq", "rf_gain", "type") \
.defaults(PropertyManager.getSharedInstance())
for key, value in params.items():
protected[key] = value
def setDspProperties(self, params):
for key, value in params.items():
self.dsp.setProperty(key, value)
def protected_send(self, data):
try:
self.conn.send(data)
# these exception happen when the socket is closed
except OSError:
self.close()
except ValueError:
self.close()
def write_spectrum_data(self, data):
self.protected_send(bytes([0x01]) + data)
def write_dsp_data(self, data):
self.protected_send(bytes([0x02]) + data)
def write_s_meter_level(self, level):
self.protected_send({"type":"smeter","value":level})
def write_cpu_usage(self, usage):
self.protected_send({"type":"cpuusage","value":usage})
def write_clients(self, clients):
self.protected_send({"type":"clients","value":clients})
def write_secondary_fft(self, data):
self.protected_send(bytes([0x03]) + data)
def write_secondary_demod(self, data):
self.protected_send(bytes([0x04]) + data)
def write_secondary_dsp_config(self, cfg):
self.protected_send({"type":"secondary_config", "value":cfg})
def write_config(self, cfg):
self.protected_send({"type":"config","value":cfg})
def write_receiver_details(self, details):
self.protected_send({"type":"receiver_details","value":details})
def write_profiles(self, profiles):
self.protected_send({"type":"profiles","value":profiles})
class WebSocketMessageHandler(object):
def __init__(self):
self.handshake = None
self.client = None
self.dsp = None
def handleTextMessage(self, conn, message):
if (message[:16] == "SERVER DE CLIENT"):
# maybe put some more info in there? nothing to store yet.
self.handshake = "completed"
logger.debug("client connection intitialized")
self.client = OpenWebRxClient(conn)
return
if not self.handshake:
logger.warning("not answering client request since handshake is not complete")
return
try:
message = json.loads(message)
if "type" in message:
if message["type"] == "dspcontrol":
if "action" in message and message["action"] == "start":
self.client.startDsp()
if "params" in message:
params = message["params"]
self.client.setDspProperties(params)
if message["type"] == "config":
if "params" in message:
self.client.setParams(message["params"])
if message["type"] == "setsdr":
if "params" in message:
self.client.setSdr(message["params"]["sdr"])
if message["type"] == "selectprofile":
if "params" in message and "profile" in message["params"]:
profile = message["params"]["profile"].split("|")
self.client.setSdr(profile[0])
self.client.sdr.activateProfile(profile[1])
else:
logger.warning("received message without type: {0}".format(message))
except json.JSONDecodeError:
logger.warning("message is not json: {0}".format(message))
def handleBinaryMessage(self, conn, data):
logger.error("unsupported binary message, discarding")
def handleClose(self, conn):
if self.client:
self.client.close()
class WebSocketController(Controller):
def handle_request(self):
conn = WebSocketConnection(self.handler, WebSocketMessageHandler())

65
owrx/feature.py Normal file
View File

@ -0,0 +1,65 @@
import os
import logging
logger = logging.getLogger(__name__)
class UnknownFeatureException(Exception):
pass
class FeatureDetector(object):
features = {
"core": [ "csdr", "nmux" ],
"rtl_sdr": [ "rtl_sdr" ],
"sdrplay": [ "rx_tools" ],
"hackrf": [ "hackrf_transfer" ]
}
def is_available(self, feature):
return self.has_requirements(self.get_requirements(feature))
def get_requirements(self, feature):
try:
return FeatureDetector.features[feature]
except KeyError:
raise UnknownFeatureException("Feature \"{0}\" is not known.".format(feature))
def has_requirements(self, requirements):
passed = True
for requirement in requirements:
methodname = "has_" + requirement
if hasattr(self, methodname) and callable(getattr(self, methodname)):
passed = passed and getattr(self, methodname)()
else:
logger.error("detection of requirement {0} not implement. please fix in code!".format(requirement))
return passed
def has_csdr(self):
return os.system("csdr 2> /dev/null") != 32512
def has_nmux(self):
return os.system("nmux --help 2> /dev/null") != 32512
def has_rtl_sdr(self):
return os.system("rtl_sdr --help 2> /dev/null") != 32512
def has_rx_tools(self):
return os.system("rx_sdr --help 2> /dev/null") != 32512
"""
To use a HackRF, compile the HackRF host tools from its "stdout" branch:
git clone https://github.com/mossmann/hackrf/
cd hackrf
git fetch
git checkout origin/stdout
cd host
mkdir build
cd build
cmake .. -DINSTALL_UDEV_RULES=ON
make
sudo make install
"""
def has_hackrf_transfer(self):
# TODO i don't have a hackrf, so somebody doublecheck this.
# TODO also check if it has the stdout feature
return os.system("hackrf_transfer --help 2> /dev/null") != 32512

View File

@ -1,5 +1,6 @@
import subprocess
from owrx.config import PropertyManager, FeatureDetector, UnknownFeatureException
from owrx.config import PropertyManager
from owrx.feature import FeatureDetector, UnknownFeatureException
import threading
import csdr
import time