diff --git a/setup.py b/setup.py index 071842a..4149104 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup setup( name="vbancmd", - version="0.3.0", + version="0.4.0", description="VBAN CMD Python API", packages=["vbancmd"], install_requires=["toml"], diff --git a/tests/__init__.py b/tests/__init__.py index febfbd6..bafbf16 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,7 +9,7 @@ kind_id = random.choice(tuple(kind.id for kind in kinds.all)) opts = { "ip": "codey.local", - "streamname": "testing", + "streamname": "codey", "port": 6990, "bps": 0, "sync": True, diff --git a/vbancmd/dataclass.py b/vbancmd/dataclass.py index 635e2ea..36f3328 100644 --- a/vbancmd/dataclass.py +++ b/vbancmd/dataclass.py @@ -33,8 +33,9 @@ class VBAN_VMRT_Packet_Data: _stripLabelUTF8c60: bytes _busLabelUTF8c60: bytes - def isdirty(self, other): - """defines the dirty flag""" + def pdirty(self, other): + """True iff any defined parameter has changed""" + return not ( self._stripState == other._stripState and self._busState == other._busState diff --git a/vbancmd/subject.py b/vbancmd/subject.py new file mode 100644 index 0000000..a3252fe --- /dev/null +++ b/vbancmd/subject.py @@ -0,0 +1,35 @@ +class Subject: + def __init__(self): + """list of current observers""" + + self._observables = [] + + def notify(self, modifier=None, data=None): + """run callbacks on update""" + + for observer in self._observables: + observer.on_update(modifier, data) + + def add(self, observer): + """adds an observer to observables""" + + if observer not in self._observables: + self._observables.append(observer) + + def remove(self, observer): + """removes an observer from observables""" + + try: + self._observables.remove(observer) + except ValueError: + pass + + def get(self) -> list: + """returns the current observables""" + + return self._observables + + def clear(self): + """clears the observables list""" + + self._observables.clear() diff --git a/vbancmd/vbancmd.py b/vbancmd/vbancmd.py index 4fdeb73..cad184b 100644 --- a/vbancmd/vbancmd.py +++ b/vbancmd/vbancmd.py @@ -19,6 +19,7 @@ from .strip import InputStrip from .bus import OutputBus from .command import Command from .util import script +from .subject import Subject class VbanCmd(abc.ABC): @@ -66,6 +67,8 @@ class VbanCmd(abc.ABC): self._public_packet = None self.running = True self._pdirty = False + self._ldirty = False + self.subject = Subject() self.cache = {} def __enter__(self): @@ -87,9 +90,9 @@ class VbanCmd(abc.ABC): worker = Thread(target=self._send_register_rt, daemon=True) worker.start() self._public_packet = self._get_rt() - worker2 = Thread(target=self._keepupdated, daemon=True) + worker2 = Thread(target=self._updates, daemon=True) worker2.start() - self._clear_dirty() + self.clear_dirty() def _send_register_rt(self): """ @@ -147,29 +150,71 @@ class VbanCmd(abc.ABC): """True iff a parameter has changed""" return self._pdirty + @property + def ldirty(self): + """True iff a level value has changed.""" + return self._ldirty + @property def public_packet(self): return self._public_packet - def _clear_dirty(self): + def clear_dirty(self): while self.pdirty: pass - def _keepupdated(self) -> NoReturn: + def _updates(self) -> NoReturn: """ Continously update public packet in background. - Set parameter dirty flag. + Set parameter and level dirty flags. Update public packet only if new private packet is found. + Then notify observers of updates to states. + This function to be run in its own thread. """ while self.running: private_packet = self._get_rt() - self._pdirty = private_packet.isdirty(self.public_packet) - if not private_packet == self.public_packet: + + private_input_levels = private_packet.inputlevels + public_input_levels = self.public_packet.inputlevels + strip_comp = [ + not a == b + for a, b in zip( + private_input_levels, + public_input_levels, + ) + ] + private_output_levels = private_packet.outputlevels + public_output_levels = self.public_packet.outputlevels + bus_comp = [ + not a == b + for a, b in zip( + private_output_levels, + public_output_levels, + ) + ] + + self._pdirty = private_packet.pdirty(self.public_packet) + self._ldirty = any(any(list_) for list_ in [strip_comp, bus_comp]) + + if self._public_packet != private_packet: self._public_packet = private_packet + if self.pdirty: + self.subject.notify("pdirty") + if self.ldirty: + self.subject.notify( + "ldirty", + [ + public_input_levels, + strip_comp, + public_output_levels, + bus_comp, + ], + ) + sleep(self._delay) def _get_rt(self) -> VBAN_VMRT_Packet_Data: """Attempt to fetch data packet until a valid one found""" @@ -281,7 +326,6 @@ class VbanCmd(abc.ABC): ) def logout(self): - """sets thread flag, closes sockets""" self.running = False sleep(0.2) self._rt_register_socket.close() @@ -315,6 +359,8 @@ def _make_remote(kind: NamedTuple) -> VbanCmd: self.kind = kind self.phys_in, self.virt_in = kind.ins self.phys_out, self.virt_out = kind.outs + self.strip_comp = [False for _ in range(2 * self.phys_in + 8 * self.virt_in)] + self.bus_comp = [False for _ in range(8 * (self.phys_out + self.virt_out))] self.strip = tuple( InputStrip.make((i < self.phys_in), self, i) for i in range(self.phys_in + self.virt_in)