Skip to content

Commit

Permalink
Merge pull request #173 from bobh66/tcp
Browse files Browse the repository at this point in the history
Add tcp support
  • Loading branch information
attwad authored Aug 17, 2024
2 parents d9a29a0 + 7b5d8ea commit 70edf62
Show file tree
Hide file tree
Showing 22 changed files with 1,653 additions and 33 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ bin/

# PyBuilder
target/

# PyCharm
.idea/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

- Added TCP Client and Server support for OSC 1.0 and OSC 1.1 formats, with support for sending responses to the client
- Added response support to the existing UDP Client and Server code

## [1.8.3]

- Using trusted publisher setup to publish to pypi
Expand Down
26 changes: 24 additions & 2 deletions docs/client.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
Client
========

The client allows you to connect and send messages to an OSC server. The client class expects an :class:`OSCMessage` object, which is then sent out via UDP. Additionally, a simple client class exists that constructs the :class:`OSCMessage` object for you.
The client allows you to connect and exchange messages with an OSC server.
Client classes are available for UDP and TCP protocols.
The base client class ``send`` method expects an :class:`OSCMessage` object, which is then sent out over TCP or UDP.
Additionally, a simple client class exists that constructs the :class:`OSCMessage` object for you.

Example
See the examples folder for more use cases.

Examples
---------

.. code-block:: python
Expand All @@ -19,10 +24,27 @@ Example
client.send_message("/some/address", [1, 2., "hello"]) # Send message with int, float and string
.. code-block:: python
from pythonosc.tcp_client import SimpleTCPClient
ip = "127.0.0.1"
port = 1337
client = SimpleTCPClient(ip, port) # Create client
client.send_message("/some/address", 123) # Send float message
client.send_message("/some/address", [1, 2., "hello"]) # Send message with int, float and string
Client Module Documentation
---------------------------------

.. automodule:: pythonosc.udp_client
:special-members:
:members:
:exclude-members: __weakref__

.. automodule:: pythonosc.tcp_client
:special-members:
:members:
:exclude-members: __weakref__
12 changes: 12 additions & 0 deletions docs/dispatcher.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ The handler must have the same signature as map callbacks:
def some_callback(address: str, *osc_arguments: List[Any]) -> None:
Handler Responses
-----------------

Handler functions can return responses back to the client, when running on a server, or to the
server when running as a client. Handler functions should return one of:

* None
* An OSC address in string format
* A tuple containing a string OSC address and the associated arguments

If the handler function response is not None it will be encoded in an OSCMessage and sent to the
remote client or server.

Dispatcher Module Documentation
---------------------------------
Expand Down
7 changes: 6 additions & 1 deletion docs/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Server
=========

The server receives OSC Messages from connected clients and invoked the appropriate callback functions with the dispatcher. There are several server types available.

Server implementations are available for both UDP and TCP protocols.

Blocking Server
-----------------
Expand Down Expand Up @@ -123,6 +123,11 @@ Server Module Documentation
------------------------------

.. automodule:: pythonosc.osc_server
:special-members:
:members:
:exclude-members: __weakref__

.. automodule:: pythonosc.osc_tcp_server
:special-members:
:members:
:exclude-members: __weakref__
56 changes: 56 additions & 0 deletions examples/async_simple_tcp_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Small example Asynchronous OSC TCP client
This program listens for incoming messages in one task, and
sends 10 random values between 0.0 and 1.0 to the /filter address,
waiting for 1 seconds between each value in a second task.
"""

import argparse
import asyncio
import random
import sys

from pythonosc import tcp_client


async def get_messages(client):
async for msg in client.get_messages(60):
print(msg)


async def send_messages(client):
for x in range(10):
r = random.random()
print(f"Sending /filter {r}")
await client.send_message("/filter", r)
await asyncio.sleep(1)


async def init_main():
parser = argparse.ArgumentParser()
parser.add_argument("--ip", default="127.0.0.1", help="The ip of the OSC server")
parser.add_argument(
"--port", type=int, default=5005, help="The port the OSC server is listening on"
)
parser.add_argument(
"--mode",
default="1.1",
help="The OSC protocol version of the server (default is 1.1)",
)
args = parser.parse_args()

async with tcp_client.AsyncSimpleTCPClient(
args.ip, args.port, mode=args.mode
) as client:
async with asyncio.TaskGroup() as tg:
tg.create_task(get_messages(client))
tg.create_task(send_messages(client))


if sys.version_info >= (3, 7):
asyncio.run(init_main())
else:
# TODO(python-upgrade): drop this once 3.6 is no longer supported
event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(init_main())
event_loop.close()
51 changes: 51 additions & 0 deletions examples/async_tcp_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import argparse
import asyncio
import sys

from pythonosc.dispatcher import Dispatcher
from pythonosc.osc_tcp_server import AsyncOSCTCPServer


def filter_handler(address, *args):
print(f"{address}: {args}")


dispatcher = Dispatcher()
dispatcher.map("/filter", filter_handler)


async def loop():
"""Example main loop that only runs for 10 iterations before finishing"""
for i in range(10):
print(f"Loop {i}")
await asyncio.sleep(10)


async def init_main():
parser = argparse.ArgumentParser()
parser.add_argument("--ip", default="127.0.0.1", help="The ip of the OSC server")
parser.add_argument(
"--port", type=int, default=5005, help="The port the OSC server is listening on"
)
parser.add_argument(
"--mode",
default="1.1",
help="The OSC protocol version of the server (default is 1.1)",
)
args = parser.parse_args()

async with AsyncOSCTCPServer(
args.ip, args.port, dispatcher, mode=args.mode
) as server:
async with asyncio.TaskGroup() as tg:
tg.create_task(server.start())
tg.create_task(loop())


if sys.version_info >= (3, 7):
asyncio.run(init_main())
else:
# TODO(python-upgrade): drop this once 3.6 is no longer supported
event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(init_main())
event_loop.close()
27 changes: 27 additions & 0 deletions examples/simple_echo_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Small example OSC client
This program sends 10 random values between 0.0 and 1.0 to the /filter address,
waiting for 1 seconds between each value.
"""

import argparse
import random
import time

from pythonosc import udp_client

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--ip", default="127.0.0.1", help="The ip of the OSC server")
parser.add_argument(
"--port", type=int, default=5005, help="The port the OSC server is listening on"
)
args = parser.parse_args()

client = udp_client.SimpleUDPClient(args.ip, args.port)

for x in range(10):
client.send_message("/filter", random.random())
reply = next(client.get_messages(2))
print(str(reply))
time.sleep(1)
30 changes: 30 additions & 0 deletions examples/simple_echo_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Small example OSC server
This program listens to several addresses, and prints some information about
received packets.
"""

import argparse
import math

from pythonosc.dispatcher import Dispatcher
from pythonosc import osc_server


def echo_handler(client_addr, unused_addr, args):
print(unused_addr, args)
return (unused_addr, args)


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--ip", default="127.0.0.1", help="The ip to listen on")
parser.add_argument("--port", type=int, default=5005, help="The port to listen on")
args = parser.parse_args()

dispatcher = Dispatcher()
dispatcher.set_default_handler(echo_handler, True)

server = osc_server.ThreadingOSCUDPServer((args.ip, args.port), dispatcher)
print("Serving on {}".format(server.server_address))
server.serve_forever()
35 changes: 35 additions & 0 deletions examples/simple_tcp_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Small example OSC client
This program sends 10 random values between 0.0 and 1.0 to the /filter address,
and listens for incoming messages for 1 second between each value.
"""

import argparse
import random

from pythonosc import tcp_client

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--ip", default="127.0.0.1", help="The ip of the OSC server")
parser.add_argument(
"--port", type=int, default=5005, help="The port the OSC server is listening on"
)
parser.add_argument(
"--mode",
default="1.1",
help="The OSC protocol version of the server (default is 1.1)",
)
args = parser.parse_args()

with tcp_client.SimpleTCPClient(args.ip, args.port, mode=args.mode) as client:
for x in range(10):
n = random.random()
print(f"Sending /filter {n}")
client.send_message("/filter", n)
resp = client.get_messages(1)
for r in resp:
try:
print(r)
except Exception as e:
print(f"oops {str(e)}: {r}")
46 changes: 46 additions & 0 deletions examples/simple_tcp_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Small example OSC server
This program listens to the specified address and port, and prints some information about
received packets.
"""

import argparse
import math

from pythonosc import osc_tcp_server
from pythonosc.dispatcher import Dispatcher


def print_volume_handler(unused_addr, args, volume):
print("[{0}] ~ {1}".format(args[0], volume))


def print_compute_handler(unused_addr, args, volume):
try:
print("[{0}] ~ {1}".format(args[0], args[1](volume)))
except ValueError:
pass


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--ip", default="127.0.0.1", help="The ip to listen on")
parser.add_argument("--port", type=int, default=5005, help="The port to listen on")
parser.add_argument(
"--mode",
default="1.1",
help="The OSC protocol version of the server (default is 1.1)",
)

args = parser.parse_args()

dispatcher = Dispatcher()
dispatcher.map("/filter", print)
dispatcher.map("/volume", print_volume_handler, "Volume")
dispatcher.map("/logvolume", print_compute_handler, "Log volume", math.log)

server = osc_tcp_server.ThreadingOSCTCPServer(
(args.ip, args.port), dispatcher, mode=args.mode
)
print("Serving on {}".format(server.server_address))
server.serve_forever()
Loading

0 comments on commit 70edf62

Please sign in to comment.