Hundemeier / sacn

A simple ANSI E1.31 (aka sACN) module for python.
MIT License
45 stars 20 forks source link

Receive all priorities in callback, not just the highest #50

Open andrewyager opened 1 month ago

andrewyager commented 1 month ago

I have an application where it would be helpful to receive all priorities, rather than just the highest priority packets in the callback; because I want to be able to make some alternate decisions about what to do with packets based on the incoming priority and/or source name.

Would it be possible to add a flag to the handler that impacts the code at https://github.com/Hundemeier/sacn/blob/1bc53abc677c97ba4f8a40e13b341de49ffd18c4/sacn/receiving/receiver_handler.py#L55 to allow all priorities to be passed on?

Perhaps a more elegant solution might be also to define a handler that could pass for every packet, and a different handler that only passes on the highest priority packets (as is the behaviour now)?

Hundemeier commented 1 month ago

It would be possible to add a trigger (like universe_all_priorities) that could accept a callback. However, the whole purpose of this library is that the library users do not need to implement all the complicated sequence number and priority handling themselves. Therefore I'm hesitant to add this feature, even though it would not be too complicated to add.

If you really want to have low level access, you could take advantage of the internal components of this library, by implementing your own ReceiverSocketListener:

from sacn.messages.data_packet import DataPacket, calculate_multicast_addr
from sacn.receiving.receiver_socket_base import ReceiverSocketBase, ReceiverSocketListener
from sacn.receiving.receiver_socket_udp import ReceiverSocketUDP

class MyReceiverHandler(ReceiverSocketListener):
    def __init__(self, bind_address: str = '0.0.0.0', bind_port: int = 5568):
        self.socket: ReceiverSocketBase = ReceiverSocketUDP(self, bind_address, bind_port)

    def on_data(self, data: bytes, current_time: float) -> None:
        try:
            packet = DataPacket.make_data_packet(data)
        except TypeError:  # try to make a DataPacket. If it fails just ignore it
            return
        # Do something with the DataPacket 'packet'.
        # The "on_data" method is called for each received UDP packet, so this equals "raw" data.
        print(packet)

    def on_periodic_callback(self, current_time: float) -> None:
        # ignore for now
        pass

    def join_multicast(self, universe: int) -> None:
        self.socket.join_multicast(calculate_multicast_addr(universe))

    def leave_multicast(self, universe: int) -> None:
        self.socket.leave_multicast(calculate_multicast_addr(universe))

    def start(self) -> None:
        self.stop()  # stop an existing thread
        self.socket.start()

    def stop(self) -> None:
        self.socket.stop()

and use it like this:

receiver = MyReceiverHandler()
receiver.start()  # start the receiving thread
receiver.join_multicast(1)

time.sleep(15)  # receive for 15 seconds

receiver.leave_multicast(1)
receiver.stop()
andrewyager commented 1 month ago

I'm happy with this approach. Thanks!