Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with Raspberry Pico PI - Server mode #12

Open
jkozniewski opened this issue Jun 9, 2024 · 4 comments
Open

Compatibility with Raspberry Pico PI - Server mode #12

jkozniewski opened this issue Jun 9, 2024 · 4 comments

Comments

@jkozniewski
Copy link

Hi !

I was able to run the Client code without any issues (so far) on Raspberry Pico PI W but have no luck with the Server code since it seems to require fflib which in turn uses ffi package and I can't find any implementation for Pico (it seems it's only supported in unix port ?). Is there any workaround, is the iff really necessary to provide OSC Server on microcontroller hardware ?

Kind of surprised that it seems it's the only OSC lib for micropython given the versatility and widespread use in makers community (especially in context of DIY music controllers) so hope some robust implementation of both Server and Client on micropython boards would be possible.

@SpotlightKid
Copy link
Owner

SpotlightKid commented Jun 9, 2024

ffi is only need by the tools/async_server.py and tools/minimal_server.py 👍

You can either:

  1. Implement your own server on top of uosc/server.py.
  2. Compile tools/async_server.py and tools/minimal_server.py with -O2, so that __debug__ is False.
  3. Remove the import from uosc.compat.socketutil in tools/async_server.py and tools/minimal_server.py and the calls to get_hostport(), which are only used in debug logging calls, IIRC.

@jkozniewski
Copy link
Author

Ok, great thanks - though curious why there is Client class and no Server class ?

@SpotlightKid
Copy link
Owner

SpotlightKid commented Jun 11, 2024

Short answer: because it isn't really needed and everyone's requirements are different.

Long answer:

The two main tasks for a server are a) to open a communication channel (e.g. an UDP socket) and listen on it and b) to dispatch incoming client messages to handler functions. This can be as minimal as in uosc/tools/minimal_server.py. In this case it doesn't really have any state that needs to be accessible / changed from the outside, so it doesn't warrant creating a class, IMHO.

To handle clients efficiently, the server should employ some kind of parallel request handling. The two main ways to achieve that on MicroPython are asyncio or threads, with the latter not available on every MicroPython port or platform. The right choice also depends on the structure of your application and the frequency and size of client requests you expect and how long you want to handle them.

IMHO answering these kind of design questions is out of scope for the core of OSC handling, so uosc gives you the building blocks to implement your own server, but not more. Plus two working examples how to implement one.

You need to implement your own server or adapt the given examples if:

  • the two given implementations are not suitable for your platform.
  • you want to use another communication channel than UDP.
  • you want to use threads for parallelism.
  • you don't like the given implementations.

@jkozniewski
Copy link
Author

jkozniewski commented Jun 11, 2024

Ok, thanks for swift and thorough reply.
I've made a leaner version of minimal_server.py example suitable to run on microcontroller removing all unix related functionalities, hope that in case someone like me would get confused with all additional imports etc. this example would provide a clearer view of what's really a bare minimum in such case:

"""A minimal, blocking OSC UDP server."""

import socket

from uosc.server import handle_osc

DEFAULT_ADDRESS = '0.0.0.0'
DEFAULT_PORT = 9001
MAX_DGRAM_SIZE = 1472


def run_server(saddr, port, handler=handle_osc):

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    ai = socket.getaddrinfo(saddr, port)[0]
    sock.bind(ai[-1])
    print("Listening for OSC messages on:", saddr, port)

    try:
        while True:
            data, caddr = sock.recvfrom(MAX_DGRAM_SIZE)
            handler(data, caddr)
    finally:
        sock.close()
        print("Bye!")

try:
    run_server(DEFAULT_ADDRESS, DEFAULT_PORT)
except KeyboardInterrupt:
    pass

Tested and it works on Pico PI :)

Obviously the way to go would be using async version running on separate thread (2nd core of RP) to separate time critical code.

My question now is - is it possible to have Client and Server both running simultaneously on the same thread (presumably using asyncio) so I can send and receive OSC messages at the same time ?
Any tips on how to approach this would be great !
Just for the context - I'll have multiple picos connected to main PC to which they will be sending/receiving OSC messages.

If I get such setup working I intend to share the example code so others can use it as a blueprint for such (kind of likely) scenario :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants