diff --git a/LightDrive/data_structures.py b/LightDrive/data_structures.py index c4ac961..5b132d5 100644 --- a/LightDrive/data_structures.py +++ b/LightDrive/data_structures.py @@ -11,11 +11,18 @@ class UniverseTcpBackend(UniverseGenericBackend): port: int = field(default=7500, init=False) hz: int = field(default=30, init=False) +@dataclass +class UniverseArtNetBackend(UniverseGenericBackend): + target_ip: str = field(default="127.0.0.1", init=False) + universe: int = field(default=0, init=False) + fps: int = field(default=30, init=False) + @dataclass class Universe: uuid: str = field(default_factory=lambda: str(uuid.uuid4()), init=False) name: str tcp_backend: UniverseTcpBackend = field(default_factory=UniverseTcpBackend, init=False) + artnet_backend: UniverseArtNetBackend = field(default_factory=UniverseArtNetBackend, init=False) @dataclass diff --git a/LightDrive/output/output_backends/artnet_backend.py b/LightDrive/output/output_backends/artnet_backend.py new file mode 100644 index 0000000..777b702 --- /dev/null +++ b/LightDrive/output/output_backends/artnet_backend.py @@ -0,0 +1,32 @@ +from output.output_backends.generic_output_backend import GenericOutputBackend +from stupidArtnet import StupidArtnet + +class ArtNetBackend(GenericOutputBackend): + def __init__(self, target_ip: str, universe: int, fps: int) -> None: + super().__init__() + self._target_ip = target_ip + self._universe = universe + self._fps = fps + + self._packet_size = 512 + self._device = self.create_device() + + def create_device(self) -> StupidArtnet: + device = StupidArtnet(target_ip=self._target_ip, universe=self._universe, packet_size=self._packet_size, fps=self._fps, broadcast=True) + device.start() + return device + + def set_values(self, values: list[int]) -> None: + self._device.set(values) + + def stop(self) -> None: + self._device.blackout() + self._device.stop() + + def update_configuration(self, target_ip: str, universe: int, fps: int) -> None: + self._target_ip = target_ip + self._universe = universe + self._fps = fps + + self.stop() + self._device = self.create_device() diff --git a/LightDrive/output/output_universe.py b/LightDrive/output/output_universe.py index a2fc964..f0b5bd2 100644 --- a/LightDrive/output/output_universe.py +++ b/LightDrive/output/output_universe.py @@ -1,5 +1,6 @@ from data_structures import Universe from output.output_backends.generic_output_backend import GenericOutputBackend +from output.output_backends.artnet_backend import ArtNetBackend from output.output_backends.tcp_backend import TcpBackend class OutputUniverse: @@ -16,6 +17,13 @@ def build_backends(self): tcp_backend = TcpBackend(target_ip, port, hz) self.output_backends.append(tcp_backend) + if self._universe_data.artnet_backend.enabled: + target_ip = self._universe_data.artnet_backend.target_ip + universe = self._universe_data.artnet_backend.universe + fps = self._universe_data.artnet_backend.fps + artnet_backend = ArtNetBackend(target_ip, universe, fps) + self.output_backends.append(artnet_backend) + def update_backends(self): def _get_backend(backend_type) -> GenericOutputBackend | None: for backend in self.output_backends: @@ -36,6 +44,18 @@ def _get_backend(backend_type) -> GenericOutputBackend | None: tcp_backend = TcpBackend(tcp_backend_data.target_ip, tcp_backend_data.port, tcp_backend_data.hz) self.output_backends.append(tcp_backend) + artnet_backend = _get_backend(ArtNetBackend) + if artnet_backend and self._universe_data.artnet_backend.enabled: + artnet_backend_data = self._universe_data.artnet_backend + artnet_backend.update_configuration(artnet_backend_data.target_ip, artnet_backend_data.universe, artnet_backend_data.fps) + elif artnet_backend and not self._universe_data.artnet_backend.enabled: + artnet_backend.stop() + self.output_backends.remove(artnet_backend) + elif not artnet_backend and self._universe_data.artnet_backend.enabled: + artnet_backend_data = self._universe_data.artnet_backend + artnet_backend = ArtNetBackend(artnet_backend_data.target_ip, artnet_backend_data.universe, artnet_backend_data.fps) + self.output_backends.append(artnet_backend) + def tick_output(self, values: list[int]) -> None: """ Sends data from the snippets to all output backends. diff --git a/LightDrive/qml/Pages/IoPage/ConfigureUniversePopup.qml b/LightDrive/qml/Pages/IoPage/ConfigureUniversePopup.qml index 826f93e..82df379 100644 --- a/LightDrive/qml/Pages/IoPage/ConfigureUniversePopup.qml +++ b/LightDrive/qml/Pages/IoPage/ConfigureUniversePopup.qml @@ -99,6 +99,54 @@ Popup { editable: true } } + Row { + CheckBox { + anchors.verticalCenter: parent.verticalCenter + id: artnetBackendCheckbox + } + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Enable ArtNet" + color: "white" + } + } + GridLayout { + id: artnetBackendGrid + columns: 2 + enabled: artnetBackendCheckbox.checkState + + Text { + text: "Target IP:" + color: "white" + } + TextField { + id: artnetTargetIpInput + placeholderText: "127.0.0.1" + text: "127.0.0.1" + } + Text { + text: "Universe:" + color: "white" + } + SpinBox { + id: artnetUniverseSpin + from: 0 + to: 32767 + stepSize: 1 + editable: true + } + Text { + text: "Max FPS" + color: "white" + } + SpinBox { + id: artnetFpsSpin + from: 1 + to: 100 + stepSize: 1 + editable: true + } + } } Row { @@ -128,6 +176,7 @@ Popup { onClicked: { universeHandler.configure_universe(currentUuid, universeNameInput.text); universeHandler.configure_tcp_backend(currentUuid, tcpBackendCheckbox.checkState, tcpTargetIpInput.text, tcpPortInput.text, tcpHzSpin.value); + universeHandler.configure_artnet_backend(currentUuid, artnetBackendCheckbox.checkState, artnetTargetIpInput.text, artnetUniverseSpin.value, artnetFpsSpin.value); cleanup(); } } @@ -163,6 +212,12 @@ Popup { tcpTargetIpInput.text = tcpBackendData[1]; tcpPortInput.text = tcpBackendData[2]; tcpHzSpin.value = tcpBackendData[3]; + + let artnetBackendData = universeHandler.get_artnet_backend_configuration(currentUuid); + artnetBackendCheckbox.checkState = artnetBackendData[0]; + artnetTargetIpInput.text = artnetBackendData[1]; + artnetUniverseSpin.value = artnetBackendData[2]; + artnetFpsSpin.value = artnetBackendData[3]; } function cleanup() { @@ -173,6 +228,11 @@ Popup { tcpPortInput.text = "7500"; tcpHzSpin.value = 30 + artnetBackendCheckbox.checkState = false; + artnetTargetIpInput.text = "127.0.0.1"; + artnetUniverseSpin.value = 0; + artnetFpsSpin.value = 30; + currentUuid = ""; configureUniversePopup.close(); } diff --git a/LightDrive/universe_handler.py b/LightDrive/universe_handler.py index 34e7d9b..d707660 100644 --- a/LightDrive/universe_handler.py +++ b/LightDrive/universe_handler.py @@ -72,4 +72,31 @@ def get_tcp_backend_configuration(self, universe_uuid: str) -> list: if universe.uuid == universe_uuid: return [universe.tcp_backend.enabled, universe.tcp_backend.target_ip, universe.tcp_backend.port, universe.tcp_backend.hz] else: - return [] \ No newline at end of file + return [] + + @Slot(str, bool, str, int, int) + def configure_artnet_backend(self, universe_uuid: str, enabled: bool, target_ip: str, universe_num: int, fps: int) -> None: + if not universe_uuid: + return + for universe in self.root.workspace.universes: + if universe.uuid == universe_uuid: + universe_data = universe + break + else: + return + + universe_data.artnet_backend.enabled = enabled + universe_data.artnet_backend.target_ip = target_ip + universe_data.artnet_backend.universe = universe_num + universe_data.artnet_backend.fps = fps + self.root.output_manager.build_output_universes() + + @Slot(str, result=list) # See reason for type above + def get_artnet_backend_configuration(self, universe_uuid: str) -> list: + if not universe_uuid: + return [] + for universe in self.root.workspace.universes: + if universe.uuid == universe_uuid: + return [universe.artnet_backend.enabled, universe.artnet_backend.target_ip, universe.artnet_backend.universe, universe.artnet_backend.fps] + else: + return [] diff --git a/pyproject.toml b/pyproject.toml index 7967859..5ed88e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ classifiers = [ ] dependencies = [ "pyside6>=6.10.1", + "stupidartnet>=1.6.0", ] [project.urls] Source = "https://github.com/Nmstr/LightDrive" diff --git a/uv.lock b/uv.lock index e0211d4..932f4f9 100644 --- a/uv.lock +++ b/uv.lock @@ -8,10 +8,14 @@ version = "0.0.0.dev0" source = { virtual = "." } dependencies = [ { name = "pyside6" }, + { name = "stupidartnet" }, ] [package.metadata] -requires-dist = [{ name = "pyside6", specifier = ">=6.10.1" }] +requires-dist = [ + { name = "pyside6", specifier = ">=6.10.1" }, + { name = "stupidartnet", specifier = ">=1.6.0" }, +] [[package]] name = "pyside6" @@ -72,3 +76,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/a6/8c65ee0fa5e172ebcca03246b1bc3bd96cdaf1d60537316648536b7072a5/shiboken6-6.10.1-cp39-abi3-win_amd64.whl", hash = "sha256:c1601d3cda1fa32779b141663873741b54e797cb0328458d7466281f117b0a4e", size = 1234704, upload-time = "2025-11-20T10:08:57.417Z" }, { url = "https://files.pythonhosted.org/packages/7b/6a/c0fea2f2ac7d9d96618c98156500683a4d1f93fea0e8c5a2bc39913d7ef1/shiboken6-6.10.1-cp39-abi3-win_arm64.whl", hash = "sha256:5cf800917008587b551005a45add2d485cca66f5f7ecd5b320e9954e40448cc9", size = 1795567, upload-time = "2025-11-20T10:08:59.184Z" }, ] + +[[package]] +name = "stupidartnet" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/41/ef5ac9d70b6694c41b21d42b2ba7b73772f4713f7b7c2b3c8b5c091ffe7f/stupidartnet-1.6.0.tar.gz", hash = "sha256:0e4c0cd0b662c2663ce0397d6f990dbae91576ad7c10230863e96d8d78c21845", size = 14892, upload-time = "2025-03-12T19:59:17.598Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/af/8ec0c05142bd915e9f95b85c238c0ae4a0ff61060c29b3b92a245b78139a/stupidartnet-1.6.0-py3-none-any.whl", hash = "sha256:b9b96fe23e759966b8692d99f0b6111f2b0a1b90b18e10a7bdc8a459ca3fe7c0", size = 12668, upload-time = "2025-03-12T19:59:16.453Z" }, +]