Improve profile handling
* introduce profile sources * subscriptions can handle config change events * web config changes to profile changes will now take effect immediately
This commit is contained in:
@ -1,4 +1,10 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from owrx.config import Config
|
||||
from abc import ABC, ABCMeta, abstractmethod
|
||||
from typing import List
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AudioChopperProfile(ABC):
|
||||
@ -13,3 +19,68 @@ class AudioChopperProfile(ABC):
|
||||
@abstractmethod
|
||||
def decoder_commandline(self, file):
|
||||
pass
|
||||
|
||||
|
||||
class ProfileSourceSubscriber(ABC):
|
||||
@abstractmethod
|
||||
def onProfilesChanged(self):
|
||||
pass
|
||||
|
||||
|
||||
class ProfileSource(ABC):
|
||||
def __init__(self):
|
||||
self.subscribers = []
|
||||
|
||||
@abstractmethod
|
||||
def getProfiles(self) -> List[AudioChopperProfile]:
|
||||
pass
|
||||
|
||||
def subscribe(self, subscriber: ProfileSourceSubscriber):
|
||||
if subscriber in self.subscribers:
|
||||
return
|
||||
self.subscribers.append(subscriber)
|
||||
|
||||
def unsubscribe(self, subscriber: ProfileSourceSubscriber):
|
||||
if subscriber not in self.subscribers:
|
||||
return
|
||||
self.subscribers.remove(subscriber)
|
||||
|
||||
def fireProfilesChanged(self):
|
||||
for sub in self.subscribers.copy():
|
||||
try:
|
||||
sub.onProfilesChanged()
|
||||
except Exception:
|
||||
logger.exception("Error while notifying profile subscriptions")
|
||||
|
||||
|
||||
class ConfigWiredProfileSource(ProfileSource, metaclass=ABCMeta):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.configSub = None
|
||||
|
||||
@abstractmethod
|
||||
def getPropertiesToWire(self) -> List[str]:
|
||||
pass
|
||||
|
||||
def subscribe(self, subscriber: ProfileSourceSubscriber):
|
||||
super().subscribe(subscriber)
|
||||
if self.subscribers and self.configSub is None:
|
||||
self.configSub = Config.get().filter(*self.getPropertiesToWire()).wire(self.fireProfilesChanged)
|
||||
|
||||
def unsubscribe(self, subscriber: ProfileSourceSubscriber):
|
||||
super().unsubscribe(subscriber)
|
||||
if not self.subscribers and self.configSub is not None:
|
||||
self.configSub.cancel()
|
||||
self.configSub = None
|
||||
|
||||
def fireProfilesChanged(self, *args):
|
||||
super().fireProfilesChanged()
|
||||
|
||||
|
||||
class StaticProfileSource(ProfileSource):
|
||||
def __init__(self, profiles: List[AudioChopperProfile]):
|
||||
super().__init__()
|
||||
self.profiles = profiles
|
||||
|
||||
def getProfiles(self) -> List[AudioChopperProfile]:
|
||||
return self.profiles
|
||||
|
@ -1,10 +1,10 @@
|
||||
from owrx.modes import Modes, AudioChopperMode
|
||||
from csdr.output import Output
|
||||
from itertools import groupby
|
||||
from abc import ABCMeta
|
||||
import threading
|
||||
from owrx.audio import ProfileSourceSubscriber
|
||||
from owrx.audio.wav import AudioWriter
|
||||
from multiprocessing.connection import wait
|
||||
from multiprocessing.connection import Pipe, wait
|
||||
|
||||
import logging
|
||||
|
||||
@ -12,26 +12,45 @@ logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
class AudioChopper(threading.Thread, Output, metaclass=ABCMeta):
|
||||
class AudioChopper(threading.Thread, Output, ProfileSourceSubscriber):
|
||||
def __init__(self, active_dsp, mode_str: str):
|
||||
self.read_fn = None
|
||||
self.doRun = True
|
||||
self.dsp = active_dsp
|
||||
self.writers = []
|
||||
mode = Modes.findByModulation(mode_str)
|
||||
if mode is None or not isinstance(mode, AudioChopperMode):
|
||||
raise ValueError("Mode {} is not an audio chopper mode".format(mode_str))
|
||||
sorted_profiles = sorted(mode.getProfiles(), key=lambda p: p.getInterval())
|
||||
groups = {interval: list(group) for interval, group in groupby(sorted_profiles, key=lambda p: p.getInterval())}
|
||||
self.read_fn = None
|
||||
self.writers = [AudioWriter(active_dsp, interval, profiles) for interval, profiles in groups.items()]
|
||||
self.doRun = True
|
||||
self.profile_source = mode.get_profile_source()
|
||||
self.writersChangedOut = None
|
||||
self.writersChangedIn = None
|
||||
super().__init__()
|
||||
|
||||
def stop_writers(self):
|
||||
while self.writers:
|
||||
self.writers.pop().stop()
|
||||
|
||||
def setup_writers(self):
|
||||
self.stop_writers()
|
||||
sorted_profiles = sorted(self.profile_source.getProfiles(), key=lambda p: p.getInterval())
|
||||
groups = {interval: list(group) for interval, group in groupby(sorted_profiles, key=lambda p: p.getInterval())}
|
||||
self.writers = [AudioWriter(self.dsp, interval, profiles) for interval, profiles in groups.items()]
|
||||
for w in self.writers:
|
||||
w.start()
|
||||
self.writersChangedOut.send(None)
|
||||
|
||||
def supports_type(self, t):
|
||||
return t == "audio"
|
||||
|
||||
def receive_output(self, t, read_fn):
|
||||
self.read_fn = read_fn
|
||||
self.start()
|
||||
|
||||
def run(self) -> None:
|
||||
logger.debug("Audio chopper starting up")
|
||||
for w in self.writers:
|
||||
w.start()
|
||||
self.writersChangedOut, self.writersChangedIn = Pipe()
|
||||
self.setup_writers()
|
||||
self.profile_source.subscribe(self)
|
||||
while self.doRun:
|
||||
data = None
|
||||
try:
|
||||
@ -45,12 +64,22 @@ class AudioChopper(threading.Thread, Output, metaclass=ABCMeta):
|
||||
w.write(data)
|
||||
|
||||
logger.debug("Audio chopper shutting down")
|
||||
for w in self.writers:
|
||||
w.stop()
|
||||
self.profile_source.unsubscribe(self)
|
||||
self.stop_writers()
|
||||
self.writersChangedOut.close()
|
||||
self.writersChangedIn.close()
|
||||
|
||||
def onProfilesChanged(self):
|
||||
logger.debug("profile change received, resetting writers...")
|
||||
self.setup_writers()
|
||||
|
||||
def read(self):
|
||||
try:
|
||||
readers = wait([w.outputReader for w in self.writers])
|
||||
return [r.recv() for r in readers]
|
||||
except (EOFError, OSError):
|
||||
return None
|
||||
while True:
|
||||
try:
|
||||
readers = wait([w.outputReader for w in self.writers] + [self.writersChangedIn])
|
||||
received = [(r, r.recv()) for r in readers]
|
||||
data = [d for r, d in received if r is not self.writersChangedIn]
|
||||
if data:
|
||||
return data
|
||||
except (EOFError, OSError):
|
||||
return None
|
||||
|
Reference in New Issue
Block a user