openwebrx-clone/owrx/connection.py

358 lines
11 KiB
Python
Raw Normal View History

from owrx.config import PropertyManager
from owrx.dsp import DspManager
from owrx.cpu import CpuUsageThread
from owrx.sdr import SdrService
from owrx.client import ClientRegistry
from owrx.feature import FeatureDetector
from owrx.version import openwebrx_version
from owrx.bands import Bandplan
2019-09-27 22:53:58 +00:00
from owrx.bookmarks import Bookmarks
from owrx.map import Map
from owrx.locator import Locator
from multiprocessing import Queue
2019-12-08 20:11:36 +00:00
from queue import Full
import json
import threading
import logging
logger = logging.getLogger(__name__)
2019-07-01 17:49:58 +00:00
class Client(object):
def __init__(self, conn):
self.conn = conn
self.multiprocessingPipe = Queue(100)
def mp_passthru():
run = True
while run:
try:
data = self.multiprocessingPipe.get()
2019-09-22 11:16:24 +00:00
self.send(data)
except (EOFError, OSError):
run = False
2019-09-22 11:16:24 +00:00
threading.Thread(target=mp_passthru).start()
2019-07-01 17:49:58 +00:00
2019-09-22 11:16:24 +00:00
def send(self, data):
self.conn.send(data)
2019-07-01 17:49:58 +00:00
def close(self):
self.conn.close()
self.multiprocessingPipe.close()
def mp_send(self, data):
2019-12-08 20:11:36 +00:00
try:
self.multiprocessingPipe.put(data, block=False)
except Full:
self.close()
2019-07-01 17:49:58 +00:00
2019-09-27 22:27:42 +00:00
def handleTextMessage(self, conn, message):
pass
def handleBinaryMessage(self, conn, data):
logger.error("unsupported binary message, discarding")
def handleClose(self):
self.close()
2019-07-01 17:49:58 +00:00
class OpenWebRxReceiverClient(Client):
config_keys = [
"waterfall_colors",
"waterfall_min_level",
"waterfall_max_level",
"waterfall_auto_level_margin",
"samp_rate",
"fft_size",
"fft_fps",
"audio_compression",
"fft_compression",
"max_clients",
"start_mod",
"start_freq",
"center_freq",
"mathbox_waterfall_colors",
"mathbox_waterfall_history_length",
"mathbox_waterfall_frequency_resolution",
"initial_squelch_level",
2019-11-23 16:22:20 +00:00
"profile_id",
]
2019-07-01 17:49:58 +00:00
def __init__(self, conn):
2019-07-01 17:49:58 +00:00
super().__init__(conn)
self.dsp = None
self.sdr = None
self.configSub = None
2019-11-26 19:10:26 +00:00
self.connectionProperties = {}
2019-06-03 22:39:22 +00:00
ClientRegistry.getSharedInstance().addClient(self)
pm = PropertyManager.getSharedInstance()
self.setSdr()
# send receiver info
receiver_keys = [
"receiver_name",
"receiver_location",
"receiver_asl",
"receiver_gps",
"photo_title",
"photo_desc",
]
receiver_details = dict((key, pm.getPropertyValue(key)) for key in receiver_keys)
receiver_details["locator"] = Locator.fromCoordinates(receiver_details["receiver_gps"])
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)
features = FeatureDetector().feature_availability()
self.write_features(features)
CpuUsageThread.getSharedInstance().add_client(self)
def handleTextMessage(self, conn, message):
try:
message = json.loads(message)
if "type" in message:
if message["type"] == "dspcontrol":
if "action" in message and message["action"] == "start":
self.startDsp()
if "params" in message:
params = message["params"]
self.setDspProperties(params)
2019-11-26 19:10:26 +00:00
elif message["type"] == "config":
if "params" in message:
self.setParams(message["params"])
2019-11-26 19:10:26 +00:00
elif message["type"] == "setsdr":
if "params" in message:
self.setSdr(message["params"]["sdr"])
2019-11-26 19:10:26 +00:00
elif message["type"] == "selectprofile":
if "params" in message and "profile" in message["params"]:
profile = message["params"]["profile"].split("|")
self.setSdr(profile[0])
self.sdr.activateProfile(profile[1])
2019-11-26 19:10:26 +00:00
elif message["type"] == "connectionproperties":
if "params" in message:
self.connectionProperties = message["params"]
if self.dsp:
self.setDspProperties(self.connectionProperties)
else:
logger.warning("received message without type: {0}".format(message))
except json.JSONDecodeError:
logger.warning("message is not json: {0}".format(message))
def setSdr(self, id=None):
next = SdrService.getSource(id)
2019-10-13 12:17:32 +00:00
if next is None:
self.handleSdrFailure("sdr device failed")
return
if next == self.sdr:
return
self.stopDsp()
if self.configSub is not None:
self.configSub.cancel()
self.configSub = None
self.sdr = next
self.startDsp()
# send initial config
2019-11-26 19:10:26 +00:00
self.setDspProperties(self.connectionProperties)
configProps = (
self.sdr.getProps()
.collect(*OpenWebRxReceiverClient.config_keys)
.defaults(PropertyManager.getSharedInstance())
)
def sendConfig(key, value):
config = dict((key, configProps[key]) for key in OpenWebRxReceiverClient.config_keys)
# TODO mathematical properties? hmmmm
config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"]
2019-11-23 16:22:20 +00:00
# TODO this is a hack to support multiple sdrs
config["sdr_id"] = self.sdr.getId()
self.write_config(config)
cf = configProps["center_freq"]
srh = configProps["samp_rate"] / 2
frequencyRange = (cf - srh, cf + srh)
2019-07-21 21:39:11 +00:00
self.write_dial_frequendies(Bandplan.getSharedInstance().collectDialFrequencies(frequencyRange))
2019-09-27 22:53:58 +00:00
bookmarks = [b.__dict__() for b in Bookmarks.getSharedInstance().getBookmarks(frequencyRange)]
self.write_bookmarks(bookmarks)
self.configSub = configProps.wire(sendConfig)
sendConfig(None, None)
self.sdr.addSpectrumClient(self)
2019-10-13 12:17:32 +00:00
def handleSdrFailure(self, message):
self.write_sdr_error(message)
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)
2019-05-12 16:10:24 +00:00
ClientRegistry.getSharedInstance().removeClient(self)
if self.configSub is not None:
self.configSub.cancel()
self.configSub = None
2019-07-01 17:49:58 +00:00
super().close()
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", "if_gain")
.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 write_spectrum_data(self, data):
self.mp_send(bytes([0x01]) + data)
2019-07-01 17:49:58 +00:00
def write_dsp_data(self, data):
2019-09-22 11:16:24 +00:00
self.send(bytes([0x02]) + data)
2019-07-01 17:49:58 +00:00
def write_s_meter_level(self, level):
2019-09-22 11:16:24 +00:00
self.send({"type": "smeter", "value": level})
2019-07-01 17:49:58 +00:00
def write_cpu_usage(self, usage):
self.mp_send({"type": "cpuusage", "value": usage})
2019-07-01 17:49:58 +00:00
def write_clients(self, clients):
self.mp_send({"type": "clients", "value": clients})
2019-07-01 17:49:58 +00:00
def write_secondary_fft(self, data):
2019-09-22 11:16:24 +00:00
self.send(bytes([0x03]) + data)
2019-07-01 17:49:58 +00:00
def write_secondary_demod(self, data):
2019-11-23 00:12:21 +00:00
message = data.decode("ascii")
self.send({"type": "secondary_demod", "value": message})
2019-07-01 17:49:58 +00:00
def write_secondary_dsp_config(self, cfg):
2019-09-22 11:16:24 +00:00
self.send({"type": "secondary_config", "value": cfg})
2019-07-01 17:49:58 +00:00
def write_config(self, cfg):
2019-09-22 11:16:24 +00:00
self.send({"type": "config", "value": cfg})
2019-07-01 17:49:58 +00:00
def write_receiver_details(self, details):
2019-09-22 11:16:24 +00:00
self.send({"type": "receiver_details", "value": details})
2019-07-01 17:49:58 +00:00
def write_profiles(self, profiles):
2019-09-22 11:16:24 +00:00
self.send({"type": "profiles", "value": profiles})
2019-07-01 17:49:58 +00:00
def write_features(self, features):
2019-09-22 11:16:24 +00:00
self.send({"type": "features", "value": features})
2019-07-01 17:49:58 +00:00
def write_metadata(self, metadata):
2019-09-22 11:16:24 +00:00
self.send({"type": "metadata", "value": metadata})
2019-07-06 18:03:17 +00:00
def write_wsjt_message(self, message):
2019-09-22 11:16:24 +00:00
self.send({"type": "wsjt_message", "value": message})
2019-07-06 18:03:17 +00:00
def write_dial_frequendies(self, frequencies):
2019-09-22 11:16:24 +00:00
self.send({"type": "dial_frequencies", "value": frequencies})
2019-09-27 22:53:58 +00:00
def write_bookmarks(self, bookmarks):
self.send({"type": "bookmarks", "value": bookmarks})
def write_aprs_data(self, data):
2019-09-22 11:16:24 +00:00
self.send({"type": "aprs_data", "value": data})
2019-10-12 18:46:32 +00:00
def write_sdr_error(self, message):
self.send({"type": "sdr_error", "value": message})
2019-07-01 17:49:58 +00:00
class MapConnection(Client):
def __init__(self, conn):
super().__init__(conn)
pm = PropertyManager.getSharedInstance()
2019-07-07 18:46:12 +00:00
self.write_config(pm.collect("google_maps_api_key", "receiver_gps", "map_position_retention_time").__dict__())
2019-07-01 17:49:58 +00:00
Map.getSharedInstance().addClient(self)
def handleTextMessage(self, conn, message):
pass
def close(self):
Map.getSharedInstance().removeClient(self)
super().close()
2019-07-01 17:49:58 +00:00
def write_config(self, cfg):
2019-09-22 11:16:24 +00:00
self.send({"type": "config", "value": cfg})
2019-07-01 17:49:58 +00:00
def write_update(self, update):
self.mp_send({"type": "update", "value": update})
2019-07-01 17:49:58 +00:00
class WebSocketMessageHandler(object):
def __init__(self):
self.handshake = None
def handleTextMessage(self, conn, message):
if message[:16] == "SERVER DE CLIENT":
meta = message[17:].split(" ")
self.handshake = {v[0]: "=".join(v[1:]) for v in map(lambda x: x.split("="), meta)}
conn.send("CLIENT DE SERVER server=openwebrx version={version}".format(version=openwebrx_version))
logger.debug("client connection intitialized")
if "type" in self.handshake:
if self.handshake["type"] == "receiver":
client = OpenWebRxReceiverClient(conn)
2019-07-01 17:49:58 +00:00
if self.handshake["type"] == "map":
client = MapConnection(conn)
# backwards compatibility
else:
client = OpenWebRxReceiverClient(conn)
# hand off all further communication to the correspondig connection
conn.setMessageHandler(client)
return
if not self.handshake:
logger.warning("not answering client request since handshake is not complete")
return
2019-09-27 22:27:42 +00:00
def handleBinaryMessage(self, conn, data):
pass
def handleClose(self):
pass