SpotlightKid / micropython-osc

A minimal OSC client and server library for MicroPython.
MIT License
63 stars 11 forks source link

Compatibility with Raspberry Pico PI - Server mode #12

Open jkozniewski opened 3 weeks ago

jkozniewski commented 3 weeks ago

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 commented 3 weeks ago

ffi is only need by the tools/async_server.py and tools/minimal_server.py :+1:

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 commented 3 weeks ago

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

SpotlightKid commented 3 weeks ago

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:

jkozniewski commented 3 weeks ago

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 :)