Update docs and screenshots
CI / backend (pull_request) Canceled after 0s
CI / shell (pull_request) Canceled after 0s
CI / frontend (pull_request) Canceled after 0s
CI / arm64-smoke (pull_request) Canceled after 0s
CI / backend (push) Canceled after 0s
CI / shell (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / arm64-smoke (push) Canceled after 0s
CI / backend (pull_request) Canceled after 0s
CI / shell (pull_request) Canceled after 0s
CI / frontend (pull_request) Canceled after 0s
CI / arm64-smoke (pull_request) Canceled after 0s
CI / backend (push) Canceled after 0s
CI / shell (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / arm64-smoke (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import struct
|
||||
|
||||
import pytest
|
||||
from app.dmx.backends import (
|
||||
ARTNET_HEADER,
|
||||
ARTNET_OPCODE_DMX,
|
||||
ARTNET_OPCODE_POLL_REPLY,
|
||||
ARTNET_PORT,
|
||||
ArtNetDmxBackend,
|
||||
build_artnet_dmx_packet,
|
||||
parse_artnet_poll_reply,
|
||||
)
|
||||
from app.dmx.frame import DmxFrame
|
||||
|
||||
|
||||
def test_build_artnet_packet_sends_full_512_frame_to_expected_universe() -> None:
|
||||
values = [0] * 512
|
||||
values[0] = 255
|
||||
values[9] = 64
|
||||
packet = build_artnet_dmx_packet(2, values, sequence=9)
|
||||
|
||||
assert packet.startswith(ARTNET_HEADER)
|
||||
assert struct.unpack_from("<H", packet, 8)[0] == ARTNET_OPCODE_DMX
|
||||
assert packet[12] == 9
|
||||
assert packet[14] == 1
|
||||
assert packet[15] == 0
|
||||
assert struct.unpack_from(">H", packet, 16)[0] == 512
|
||||
assert len(packet) == 530
|
||||
assert packet[18] == 255
|
||||
assert packet[27] == 64
|
||||
|
||||
|
||||
def test_parse_artnet_poll_reply_extracts_node_identity() -> None:
|
||||
packet = bytearray(239)
|
||||
packet[0:8] = ARTNET_HEADER
|
||||
struct.pack_into("<H", packet, 8, ARTNET_OPCODE_POLL_REPLY)
|
||||
packet[10:14] = bytes([192, 168, 2, 55])
|
||||
packet[18] = 0
|
||||
packet[19] = 1
|
||||
packet[26 : 26 + len(b"WLED Node\x00")] = b"WLED Node\x00"
|
||||
packet[44 : 44 + len(b"Paravega Test Node\x00")] = b"Paravega Test Node\x00"
|
||||
struct.pack_into(">H", packet, 172, 4)
|
||||
packet[190] = 3
|
||||
|
||||
node = parse_artnet_poll_reply(bytes(packet))
|
||||
|
||||
assert node is not None
|
||||
assert node.ip == "192.168.2.55"
|
||||
assert node.short_name == "WLED Node"
|
||||
assert node.long_name == "Paravega Test Node"
|
||||
assert node.port_count == 4
|
||||
assert node.raw_port_address == 3
|
||||
assert node.label == "Paravega Test Node (192.168.2.55)"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_artnet_backend_sends_udp_packet_and_updates_status(monkeypatch) -> None:
|
||||
sent_packets: list[tuple[bytes, tuple[str, int]]] = []
|
||||
|
||||
class FakeSocket:
|
||||
def setsockopt(self, *_args: object) -> None:
|
||||
return
|
||||
|
||||
def sendto(self, packet: bytes, address: tuple[str, int]) -> None:
|
||||
sent_packets.append((packet, address))
|
||||
|
||||
def close(self) -> None:
|
||||
return
|
||||
|
||||
backend = ArtNetDmxBackend(
|
||||
universe=4,
|
||||
target_host="192.168.2.77",
|
||||
output_port="Paravega stue",
|
||||
)
|
||||
monkeypatch.setattr(backend, "_socket", FakeSocket())
|
||||
frame = DmxFrame(universe=4)
|
||||
frame.set_channel(1, 200, "scene")
|
||||
frame.set_channel(4, 99, "scene")
|
||||
|
||||
await backend.send_frame(frame)
|
||||
|
||||
status = backend.get_status()
|
||||
assert status.connected is True
|
||||
assert status.degraded is False
|
||||
assert status.frames_sent == 1
|
||||
assert status.send_errors == 0
|
||||
assert status.selected_universe == 4
|
||||
assert status.selected_output_port == "Paravega stue"
|
||||
assert sent_packets[0][1] == ("192.168.2.77", ARTNET_PORT)
|
||||
assert sent_packets[0][0][18] == 200
|
||||
assert sent_packets[0][0][21] == 99
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_artnet_backend_marks_degraded_on_socket_error(monkeypatch) -> None:
|
||||
class FakeSocket:
|
||||
def setsockopt(self, *_args: object) -> None:
|
||||
return
|
||||
|
||||
def sendto(self, _packet: bytes, _address: tuple[str, int]) -> None:
|
||||
raise OSError("Network unreachable")
|
||||
|
||||
def close(self) -> None:
|
||||
return
|
||||
|
||||
backend = ArtNetDmxBackend(universe=1, target_host="192.168.2.90")
|
||||
monkeypatch.setattr(backend, "_socket", FakeSocket())
|
||||
frame = DmxFrame()
|
||||
frame.set_channel(1, 255, "scene")
|
||||
|
||||
with pytest.raises(RuntimeError, match="Network unreachable"):
|
||||
await backend.send_frame(frame)
|
||||
|
||||
status = backend.get_status()
|
||||
assert status.connected is False
|
||||
assert status.degraded is True
|
||||
assert status.send_errors == 1
|
||||
assert status.last_error == "Network unreachable"
|
||||
Reference in New Issue
Block a user