"""UDP Clients for sending OSC messages to an OSC server""" import sys if sys.version_info > (3, 5): from collections.abc import Iterable else: from collections import Iterable import socket from typing import Generator, Union from pythonosc.dispatcher import Dispatcher from pythonosc.osc_bundle import OscBundle from pythonosc.osc_message import OscMessage from pythonosc.osc_message_builder import ArgValue, OscMessageBuilder class UDPClient(object): """OSC client to send :class:`OscMessage` or :class:`OscBundle` via UDP""" def __init__( self, address: str, port: int, allow_broadcast: bool = False, family: socket.AddressFamily = socket.AF_UNSPEC, ) -> None: """Initialize client As this is UDP it will not actually make any attempt to connect to the given server at ip:port until the send() method is called. Args: address: IP address of server port: Port of server allow_broadcast: Allow for broadcast transmissions family: address family parameter (passed to socket.getaddrinfo) """ for addr in socket.getaddrinfo( address, port, type=socket.SOCK_DGRAM, family=family ): af, socktype, protocol, canonname, sa = addr try: self._sock = socket.socket(af, socktype) except OSError: continue break self._sock.setblocking(False) if allow_broadcast: self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) self._address = address self._port = port def send(self, content: Union[OscMessage, OscBundle]) -> None: """Sends an :class:`OscMessage` or :class:`OscBundle` via UDP Args: content: Message or bundle to be sent """ self._sock.sendto(content.dgram, (self._address, self._port)) def receive(self, timeout: int = 30) -> bytes: """Wait :int:`timeout` seconds for a message an return the raw bytes Args: timeout: Number of seconds to wait for a message """ self._sock.settimeout(timeout) try: return self._sock.recv(4096) except TimeoutError: return b"" class SimpleUDPClient(UDPClient): """Simple OSC client that automatically builds :class:`OscMessage` from arguments""" def send_message(self, address: str, value: ArgValue) -> None: """Build :class:`OscMessage` from arguments and send to server Args: address: OSC address the message shall go to value: One or more arguments to be added to the message """ builder = OscMessageBuilder(address=address) values: ArgValue if value is None: pass elif not isinstance(value, Iterable) or isinstance(value, (str, bytes)): builder.add_arg(value) else: for val in value: builder.add_arg(val) msg = builder.build() self.send(msg) def get_messages(self, timeout: int = 30) -> Generator: """Wait :int:`timeout` seconds for a message from the server and convert it to a :class:`OscMessage` Args: timeout: Time in seconds to wait for a message """ msg = self.receive(timeout) while msg: yield OscMessage(msg) msg = self.receive(timeout) class DispatchClient(SimpleUDPClient): """OSC Client that includes a :class:`Dispatcher` for handling responses and other messages from the server""" dispatcher = Dispatcher() def handle_messages(self, timeout: int = 30) -> None: """Wait :int:`timeout` seconds for a message from the server and process each message with the registered handlers. Continue until a timeout occurs. Args: timeout: Time in seconds to wait for a message """ msg = self.receive(timeout) while msg: self.dispatcher.call_handlers_for_packet(msg, (self._address, self._port)) msg = self.receive(timeout)