Code Monkey home page Code Monkey logo

oscpy's Introduction

OSCPy

Coverage Status CI is done by Github Checks, see the current commit for build status.

A modern implementation of OSC for python2/3.

What is OSC.

OpenSoundControl is an UDP based network protocol, that is designed for fast dispatching of time-sensitive messages, as the name suggests, it was designed as a replacement for MIDI, but applies well to other situations. The protocol is simple to use, OSC addresses look like http URLs, and accept various basic types, such as string, float, int, etc. You can think of it basically as an http POST, with less overhead.

You can learn more about OSC on OpenSoundControl.org

Goals

  • python2.7/3.6+ compatibility (can be relaxed more on the python3 side if needed, but nothing before 2.7 will be supported)
  • fast
  • easy to use
  • robust (returns meaningful errors in case of malformed messages, always do the right thing on correct messages, and by default intercept+log the exceptions raised by callbacks)
  • separation of concerns (message parsing vs communication)
  • sync and async compatibility (threads, asyncio, trio…)
  • clean and easy to read code

Features

  • serialize and parse OSC data types/Messages/Bundles
  • a thread based udp server to open sockets (INET or UNIX) and bind callbacks on osc addresses on them
  • a simple client

Install

pip install oscpy

Usage

Server (thread)

from oscpy.server import OSCThreadServer
from time import sleep

def callback(*values):
    print("got values: {}".format(values))

osc = OSCThreadServer()  # See sources for all the arguments

# You can also use an \*nix socket path here
sock = osc.listen(address='0.0.0.0', port=8000, default=True)
osc.bind(b'/address', callback)
sleep(1000)
osc.stop()  # Stop the default socket

osc.stop_all()  # Stop all sockets

# Here the server is still alive, one might call osc.listen() again

osc.terminate_server()  # Request the handler thread to stop looping

osc.join_server()  # Wait for the handler thread to finish pending tasks and exit

or you can use the decorator API.

Server (thread)

from oscpy.server import OSCThreadServer
from time import sleep

osc = OSCThreadServer()
sock = osc.listen(address='0.0.0.0', port=8000, default=True)

@osc.address(b'/address')
def callback(*values):
    print("got values: {}".format(values))

sleep(1000)
osc.stop()

Servers are also client, in the sense they can send messages and answer to messages from other servers

from oscpy.server import OSCThreadServer
from time import sleep

osc_1 = OSCThreadServer()
osc_1.listen(default=True)

@osc_1.address(b'/ping')
def ping(*values):
    print("ping called")
    if True in values:
        cont.append(True)
    else:
        osc_1.answer(b'/pong')

osc_2 = OSCThreadServer()
osc_2.listen(default=True)

@osc_2.address(b'/pong')
def pong(*values):
    print("pong called")
    osc_2.answer(b'/ping', [True])

osc_2.send_message(b'/ping', [], *osc_1.getaddress())

timeout = time() + 1
while not cont:
    if time() > timeout:
        raise OSError('timeout while waiting for success message.')

Server (async) (TODO!)

from oscpy.server import OSCThreadServer

with OSCAsyncServer(port=8000) as OSC:
    for address, values in OSC.listen():
       if address == b'/example':
            print("got {} on /example".format(values))
       else:
            print("unknown address {}".format(address))

Client

from oscpy.client import OSCClient

address = "127.0.0.1"
port = 8000

osc = OSCClient(address, port)
for i in range(10):
    osc.send_message(b'/ping', [i])

Unicode

By default, the server and client take bytes (encoded strings), not unicode strings, for osc addresses as well as osc strings. However, you can pass an encoding parameter to have your strings automatically encoded and decoded by them, so your callbacks will get unicode strings (unicode in python2, str in python3).

osc = OSCThreadServer(encoding='utf8')
osc.listen(default=True)

values = []

@osc.address(u'/encoded')
def encoded(*val):
    for v in val:
        assert not isinstance(v, bytes)
    values.append(val)

send_message(
    u'/encoded',
    [u'hello world', u'ééééé ààààà'],
    *osc.getaddress(), encoding='utf8')

(u literals added here for clarity).

CLI

OSCPy provides an "oscli" util, to help with debugging:

  • oscli dump to listen for messages and dump them
  • oscli send to send messages or bundles to a server

See oscli -h for more information.

GOTCHAS

  • None values are not allowed in serialization
  • Unix-type sockets must not already exist when you listen() on them

TODO

  • real support for timetag (currently only supports optionally dropping late bundles, not delaying those with timetags in the future)
  • support for additional argument types
  • an asyncio-oriented server implementation
  • examples & documentation

Contributing

Check out our contribution guide and feel free to improve OSCPy.

License

OSCPy is released under the terms of the MIT License. Please see the LICENSE.txt file.

oscpy's People

Contributors

andremiras avatar chihayak avatar cjrchang avatar doug-hoffman avatar felixdollack avatar levaitamas avatar p0lygun avatar pakal avatar tshirtman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oscpy's Issues

Android - iOS: get_sender() not called from a callback in file

Describe the bug
__file__ != filename, because .py complied to .pyc
It should be:

if function == '_listen' and re.search(r'oscpy[\\/]server\.py', filename):
    break

To Reproduce

osc = OSCThreadServer()
sock = osc.listen(address='0.0.0.0', port=8000, default=True)

@osc.address(b'/ping')
def _identify_me(values):
    print(osc.get_sender())

Expected behavior
Print sender

Logs/output

05-13 09:44:16.672 13763 13905 I python  : [INFO   ] [osc         ]
05-13 09:44:16.672 13763 13905 I python  : __file__: ./_applibs/oscpy/server.pyc
05-13 09:44:16.672 13763 13905 I python  : filename: /home/user/.buildozer/android/app/_applibs/oscpy/server.py
05-13 09:44:16.673 13763 13905 I python  : [ERROR  ] [Catch Exception]
05-13 09:44:16.673 13763 13905 I python  : Traceback (most recent call last):
05-13 09:44:16.673 13763 13905 I python  :   File "/home/user/.buildozer/android/app/block/socketp2p.py", line 27, in f
05-13 09:44:16.673 13763 13905 I python  :   File "/home/user/.buildozer/android/app/block/socketp2p.py", line 101, in _identify_me
05-13 09:44:16.673 13763 13905 I python  :   File "/home/user/.buildozer/android/app/_applibs/oscpy/server.py", line 447, in get_sender
05-13 09:44:16.673 13763 13905 I python  : RuntimeError: get_sender() not called from a callback

Platform (please complete the following information):

  • OS: android, iOS
  • Python version: 3.7
  • release or git branch/commit: master f013d0e

Out Of Topic:
Can you add this to method OSCThreadServer.unbind:

if isinstance(address, UNICODE) and self.encoding:
      address = address.encode(
          self.encoding, errors=self.encoding_errors)

Add option to demonstrate persistent service

I suggest that the example have an option to show running a service as a persistent one so it doesn't stop on app close.

It could be a check box that says "run as persistent service"

I've tried to make it an auto restart service with this line in start_service function:

service.mService.setAutoRestartService(True)

but no luck.

New Windows issue (with current code including fix #48)

Hi tshirtman,

A simple stop_all() on a server is producing an error message on Win10 (not on Linux):

Traceback (most recent call last):
  File "D:\blender-2.80-windows64\2.80\python\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "D:\blender-2.80-windows64\2.80\python\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "C:\users\hebus\Application Data\Blender Foundation\Blender\2.80\scripts\addons\MOM\oscpy\server.py", line 336, in _listen
    data, sender = sender_socket.recvfrom(65535)
OSError: [WinError 10038] Windows Error 0x2736

This happens despite the recent try/catch because its an OSError and not a ConnectionResetError that the new code can intercept.
I edited manually the line 337 to catch all errors as a workaround and my code can now stop the server and reconnect successfully. You will of course determinate a proper solution.

I found this while googling on the error message:
https://stackoverflow.com/questions/35889267/an-operation-was-attempted-on-something-that-is-not-a-socket-tried-fixing-a-lot

Protect the server against exceptions in callbacks

Describe the bug
As of now, callbacks are called by the listener thread as "cb(address, *values)", without try...except protection around. So an exception in a callback will stop the server thread.

Expected behavior
Maybe do some "catch all" around the callback call, and log potential errors?
Or document that callbacks must by themselves prevent any excrption from flowing through?

Clarify blob handling.

In Python, the only semantic difference between bytes and bytearray data structures, is that the former are immutable and the latter are mutable. It is similar to the difference between tuples and lists.

However, in oscpy, bytes are treated as as an OSC-String and bytesarrays are treated as an OSC-Blob. They are treated differently as to whether they go through an encoding/decoding stage,

This distinction is not obvious nor intuitive.

Please consider treating bytes as OSC-Blobs, to make then consistent. Unicode strings should continue to be treated as strings and encoded/decoded.

Failing that please consider documenting this in the Gotcha section.

Async oscpy, question, help !

Hi, how could I implement oscpy with cooroutines using asyncio, I don't understand how to implement the below.
OSCAsyncServer exist in somewere ?

Server (async) (TODO!)

from oscpy.server import OSCThreadServer

with OSCAsyncServer(port=8000) as OSC:
for address, values in OSC.listen():
if address == b'/example':
print("got {} on /example".format(values))
else:
print("unknown address {}".format(address))

blob packing exception

Describe the bug
client send_message raise exception when sending blobs

To Reproduce
OSCClient(b'localhost', 10000).send_message(b'/my_uuid', [bytearray(range(16)),])

Expected behavior
16 bytes blob sent to server

Logs/output
Traceback (most recent call last):
File "", line 1, in
File "C:\Utils\Python3\lib\site-packages\oscpy\client.py", line 136, in send_message
stats = send_message(
File "C:\Utils\Python3\lib\site-packages\oscpy\client.py", line 63, in send_message
message, stats = format_message(
File "C:\Utils\Python3\lib\site-packages\oscpy\parser.py", line 271, in format_message
message = pack(
struct.error: pack expected 26 items for packing (got 3)

Platform (please complete the following information):

  • OS: windows 10
  • Python 3.7
  • release

Additional context
THE REASON
WRITERS = (
...
(bytearray, (b'b', b'%ib')),
mean
struct.pack(b'16b', bytearray(range(16))) # it is not valid
struct.error: pack expected 16 items for packing (got 1)

FIX
it shoulfd be:
struct.pack(b'16s', bytearray(range(16)))
so,
WRITERS = (
...
(bytearray, (b's', b'%ib')),

Blob size unpack_from with wrong unit

Describe the bug
I am sending an OSC message containing a single blob of data:
/destn/1\x00\x00\x00\x00,b\x00\x00\x00\x00\x00\x08...
[28 bytes] /destn/1 b [8]0123456789ABCDEF

I am getting this result for both OSC data send from C and using Dart.

To Reproduce
Code example showing the issue is the example code from the readme:

from oscpy.server import OSCThreadServer
from time import sleep

osc = OSCThreadServer()
sock = osc.listen(address='0.0.0.0', port=9000, default=True)

@osc.address(b'/destn/*')
def callback(*values):
    print("got values: {}".format(values))

sleep(1000)
osc.stop()

Here the sending portion in C using tinyosc compiled with gcc *.c -Werror -std=c99 -O0 -g -o sendosc Note: main.c from tinyosc has to be removed or the content be replaced with below:

#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>

#include "tinyosc.h"

int main(int argc, char *argv[]) {
  char buffer[128];
  int len = 0;
  char blob[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
  len = tosc_writeMessage(buffer, sizeof(buffer), "/destn/1", "b",
      sizeof(blob), blob);
  tosc_printOscBuffer(buffer, len);

  struct sockaddr_in client_addr;
  client_addr.sin_family = AF_INET;
  client_addr.sin_port = htons(9000);
  client_addr.sin_addr.s_addr = INADDR_ANY;
  const int fd_send = socket(AF_INET, SOCK_DGRAM, 0);
  if (fd_send < 0)
  {
    fprintf(stderr, "Error opening socket");
    return -1;
  }
  int response = sendto(
    fd_send, (void*) &buffer[0], len, 0,
    (struct sockaddr*)&client_addr, sizeof(client_addr)
  );
  if (response < 0) {
    fprintf(stderr, "Error in sendto()\n");
    return -1;
  }
  // close the UDP socket
  close(fd_send);
  return 0;
}

Expected behavior
Read the correct size of the blob and get a binary array same way it was written.
The same message works without any issue using e.g. python-osc.
blob = b'\x01#Eg\x89\xab\xcd\xef'

Logs/output

Exception in thread Thread-1:
Traceback (most recent call last):
  File "miniconda3/envs/v/lib/python3.9/threading.py", line 973, in _bootstrap_inner
    self.run()
  File "miniconda3/envs/v/lib/python3.9/threading.py", line 910, in run
    self._target(*self._args, **self._kwargs)
  File "miniconda3/envs/v/lib/python3.9/site-packages/oscpy/server.py", line 338, in _run_listener
    self._listen()
  File "miniconda3/envs/v/lib/python3.9/site-packages/oscpy/server.py", line 386, in _listen
    for address, tags, values, offset in read_packet(
  File "miniconda3/envs/v/lib/python3.9/site-packages/oscpy/parser.py", line 409, in read_packet
    read_message(
  File "miniconda3/envs/v/lib/python3.9/site-packages/oscpy/parser.py", line 309, in read_message
    value, off = parse(
  File "miniconda3/envs/v/lib/python3.9/site-packages/oscpy/parser.py", line 206, in parse
    return parser(
  File "miniconda3/envs/v/lib/python3.9/site-packages/oscpy/parser.py", line 96, in parse_blob
    data = unpack_from('>%iQ' % length, value, offset + size)
struct.error: unpack_from requires a buffer of at least 84 bytes for unpacking 64 bytes at offset 20 (actual buffer size is 28)
^CTraceback (most recent call last):
  File "Documents/code/udp_test/osc_example.py", line 11, in <module>
    sleep(1000)
KeyboardInterrupt

Platform (please complete the following information):

  • OS: OSX 12.5.1
  • Python 3.9
  • OSCpy 0.6.0 (installed with pip)

Additional context
Add any other context about the problem here.

Bad File Description on close

Exception in thread Thread-42:
Traceback (most recent call last):
  File threading.py, line 801, in __bootstrap_inner
    self.run()
  File threading.py, line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "oscpy/server.py", line 307, in _listen
    read, write, error = select(self.sockets, [], [], self.timeout)
error: (9, 'Bad file descriptor')

When I try to close using osc.stop_all() the above error happens every time.

Tested on Python 2.7 and 3.6 resulting in the same error

Use strings for address and parameters

It's great to have an OSC library that works well and supports both Python 2 and Python 3.

One issue I have with it though, is that it doesn't seem to support string types for the address or for OSC parameters, but uses byte arrays instead.

It can get a bit tedious (and error-prone) having to convert backwards and forwards between strings and bytes all over the place (or remembering to prefix a b in front of literals), so it would be great if it accepted and returned strings instead of (or as well as) bytes.

You would have to decide on an encoding, but I think it's fine to assume utf8 (or if you want to be more flexible you can always have an optional constructor parameter for the encoding).

Unable to change port using listen method

The listen method does not seem to change the current port. Please see below for a code snippet where I try to change a port with the changePort method. If defaut=True, I get this error:

RuntimeError: Only one default socket authorized! Please set default=False to other calls to listen()

And, if defaut=False, I just stop receiving messages on the port. Is this a sort of bug, or is there a right way to change port with OSCpy?

OS: macOS 12.6.6
Python: 3.9.16
OSCpy: 0.6.0

from oscpy.server import OSCThreadServer

SERVER_ADDRESS = "127.0.0.1"
PORT = 3000
OSC_ADDRESS = b"/testAddress"

class OSCTest:
    def __init__(self):
        self.server = OSCThreadServer()
        self.socket = self.server.listen(address=SERVER_ADDRESS,
                                         port=PORT, default=True)
        self.server.bind(OSC_ADDRESS, self.handler)

    def handler(self, *data):
        print(data)

    def close(self):
        self.server.stop(self.socket)

    def changePort(self, newPort):
        """Change the server port to a newPort."""
        # Close the current port
        self.server.stop(self.socket)
        # Listen on the new port
        self.socket = self.server.listen(address=SERVER_ADDRESS,
                                         port=newPort, default=False)
        # Re-bind the server to handle incoming OSC messages
        self.server.bind(OSC_ADDRESS, self.handler)

socket.error: [Errno 98] Address already in use

Error trying to re-create listen:

osc = OSC() 
id_osc = self.osc.listen(address='127.0.0.1', port=3002, default=True)
osc.bind(b'/msgFromService', msgFromService)
osc.bind(b'/progressb', progressb)

# On exit 
osc.stop_all()

OSC server timeout

After starting an OSC server and idling, the listener times out due to recv on Python 2.7.

    #Initialise OSC server
    osc = OSCThreadServer()
    sock = osc.listen(address='0.0.0.0', port=8000, default=True)

After a minute or so:

Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/home/rc/venv-python2-ros/local/lib/python2.7/site-packages/oscpy/server.py", line 257, in _listen
    data, sender = sender_socket.recvfrom(65535)
timeout: timed out

A temporary workaround is to get the OSC server to poll itself, something along the lines of:

    while not shutdownevent.isSet():
        osc.send_message(b'/keepalive', [], '127.0.0.1', 8000)
        time.sleep(2)

Perhaps using Select() (Python 2, Python 3) would be better.

OSCThreadServer.answer presumes caller listens on sending port

OSCThreadServer.answer currently sends responses to the sender's IP address and port.
This library needs to interface with other OSC implementations and the sending port may not be user specified (i.e. usually a user specifies the target IP address and port and an incoming port).

No workarounds available at this point in time.

Perhaps re-working to specify a target response port is an option?

Listener thread died because of unable to parse a packet

Describe the bug
As the title stated, the entire listener thread terminated because parser.py raise an error.

To Reproduce
Sorry but it seems happened just once and I wasn't able to reproduce the bug yet, but read though the code I found that it is caused by the header mismatched.

Expected behavior
The packet should be droped and log a header error instead of the entire listener thread terminated?

Logs/output

 Exception in thread Thread-1:
 Traceback (most recent call last):
   File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
     self.run()
   File "/usr/lib/python3.8/threading.py", line 870, in run
     self._target(*self._args, **self._kwargs)
   File "/.venv/lib/python3.8/site-packages/oscpy/server.py", line 338, in _run_listener
     self._listen()
   File "/.venv/lib/python3.8/site-packages/oscpy/server.py", line 386, in _listen
     for address, tags, values, offset in read_packet(
   File "/.venv/lib/python3.8/site-packages/oscpy/parser.py", line 424, in read_packet
     raise ValueError('packet is not a message or a bundle')
 ValueError: packet is not a message or a bundle

Platform (please complete the following information):

  • OS: linux
  • Python version: 3.8
  • release 0.6.0

closed remote connection on windows.

Describe the bug
running tests on windows now fails, specifically, test test_server_different_port in test_server.py.

To reproduce

(.env) C:\Users\gqb\dev\oscpy>pytest -k test_server_different_port
Test session starts (platform: win32, Python 3.6.1, pytest 4.2.0, pytest-sugar 0.9.2)
rootdir: C:\Users\gqb\dev\oscpy, inifile:
plugins: sugar-0.9.2, cov-2.6.1
collecting ...

―――――――――――――――――――――――――――――――――――――――――――― test_server_different_port ――――――――――――――――――――――――――――――――――――――――――――

    def test_server_different_port():
        # used for storing values received by callback_3000
        checklist = []

        def callback_3000(*values):
            checklist.append(values[0])

        # server, will be tested:
        server_3000 = OSCThreadServer(encoding='utf8')
        sock_3000 = server_3000.listen( address='0.0.0.0', port=3000, default=True)
        server_3000.bind(b'/callback_3000', callback_3000)

        # clients sending to different ports, used to test the server:
        client_3000 = OSCClient(address='localhost', port=3000, encoding='utf8')

        # server sends message to himself, should work:
        server_3000.send_message(
            b'/callback_3000',
            ["a"],
            ip_address='localhost',
            port=3000
        )
        sleep(0.05)

        # client sends message to server, will be received properly:
        client_3000.send_message(b'/callback_3000', ["b"])
        sleep(0.05)

        # sever sends message on different port, might crash the server on windows:
        server_3000.send_message(
            b'/callback_3000',
            ["nobody is going to receive this"],
            ip_address='localhost',
            port=3001
        )
        sleep(0.05)

        # client sends message to server again. if server is dead, message
        # will not be received:
        client_3000.send_message(b'/callback_3000', ["c"])
        sleep(0.1)

        # if 'c' is missing in the received checklist, the server thread
        # crashed and could not recieve the last message from the client:
>       assert checklist == ['a', 'b', 'c']
E       AssertionError: assert ['a', 'b'] == ['a', 'b', 'c']
E         Right contains more items, first extra item: 'c'
E         Use -v to get the full diff

tests\test_server.py:927: AssertionError
----------------------------------------------- Captured stderr call -----------------------------------------------
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Users\gqb\AppData\Local\Programs\Python\Python36\lib\threading.py", line 916, in _bootstrap_inner
    self.run()
  File "C:\Users\gqb\AppData\Local\Programs\Python\Python36\lib\threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "c:\users\gqb\dev\oscpy\oscpy\server.py", line 336, in _listen
    data, sender = sender_socket.recvfrom(65535)
ConnectionResetError: [WinError 10054] Une connexion existante a dû être fermée par l’hôte distant


 tests/test_server.py ⨯                                                                               100% ██████████

Results (0.56s):
       1 failed
         - tests/test_server.py:883 test_server_different_port
      80 deselected

(.env) C:\Users\gqb\dev\oscpy>

Expected behavior
Test should pass, there is no reason that sending from client should crash the server.

Platform (please complete the following information):

  • OS: windows 10
  • Python version. 3.6.1
  • release or git branch/commit: master

Additional context
doesn't reproduce in CI, only on a real windows, it seems…

Activity and Service don't respond to messages from one another on Android

The code works on mac, but on an Android device it seems that the service and main app don't exchange (respond to) messages from one another.
Because if I don't send messages between them and just start service and push notification on a time interval it works.
Below is my code.

main.py:

from oscpy.client import OSCClient
from oscpy.server import OSCThreadServer

SERVICE_NAME = '{packagename}.Service{servicename}'.format(
    packagename='clientsandorders.com.clientsandorders',
    servicename='Taskreminder'
)

class MainApp(MDApp):
    def build(self):
        kv = Builder.load_file("main.kv")

        self.start_service()

        self.server = server = OSCThreadServer()
        server.listen(
            address=b'localhost',
            port=3002,
            default=True,
        )

        server.bind(b'/ping_response', self.ping_response)
        self.client = OSCClient(address=b'localhost', port=3000)

        return kv
    
        def ping_response(self):
             self.client.send_message(b'/push_up', [])

And here's the service code:

from time import sleep
from oscpy.server import OSCThreadServer
from oscpy.client import OSCClient
from plyer import notification
from plyer.utils import platform
from jnius import autoclass

CLIENT = OSCClient(address='localhost', port=3002)

def push_up():
    if platform == 'android':
        notification.notify(title='Test', message='This is a test message')
    else:
        print('Service works')


def ping_activity():
    CLIENT.send_message(b'/ping_response', [])


if __name__ == '__main__':
    SERVER = OSCThreadServer()
    SERVER.listen(address='localhost', port=3000, default=True)
    SERVER.bind(b'/push_up', push_up)
    PythonService = autoclass('org.kivy.android.PythonService')
    PythonService.mService.setAutoRestartService(True)

    while True:
        sleep(10)
        ping_activity()

Expected behaviour is for service to ping the main app on a specific time interval, then get a response (with some data in the future) and to push a notification with plyer.

logcat shows no errors.

Many thanks in advance!

Cannot oscli send -> oscli dump without raising exception

Describe the bug
I cannot use oscli to send and dump a package without the server raising an exception.

To Reproduce

In one shell

oscli dump

in another

oscli send 127.0.0.1

You will receive an exception

ValueError: packet is not a message or a bundle

Expected behavior
The packet should be readable

Platform (please complete the following information):

  • OS: Arch Linux Latest
  • Python 3.10.5
  • oscpy 0.6.0 from PyPI

NameError: name 'OSCThreadServer' is not defined

Using your implementation on a raspberry pi 3 using pip install and python version 2.7.13, I have this error:

osc = OSCThreadServer() NameError: name 'OSCThreadServer' is not defined

The client is working just fine BTW.
Any idea?

Delay between close() and reopening a server

Hello,

I had a bug showing only when trying to reopen a server with the same IP (or changing from 0.0.0.0 to 127.0.0.1) and same port settings after a close() function.

The error message is that the port is already in use.

Traceback (most recent call last):
  File "/home/hebus/.config/blender/2.80/scripts/addons/MOM/osc.py", line 107, in save_osc_port_in
    bpy.ops.mom.refresh_osc()
  File "/Graphisme/blender-2.80-linux-glibc217-x86_64/2.80/scripts/modules/bpy/ops.py", line 201, in __call__
    ret = op_call(self.idname_py(), None, kw)
RuntimeError: Error: Traceback (most recent call last):
  File "/home/hebus/.config/blender/2.80/scripts/addons/MOM/osc.py", line 140, in execute
    osc_server.listen(address=ip, port=port, default=True)
  File "/Graphisme/2.80_migration/MOM/oscpy/server.py", line 245, in listen
    sock.bind(addr)
OSError: [Errno 98] Address already in use

The solution I found was to introduce a delay using:
time.sleep(.01)

10ms was the minimal value to avoid the issue.

Using tag from a displax screen make listener thread crash

Describe the bug
When using a DISPLAX tag supporting TUIO on a DISPLAX screen, oscpy TUIO listener crash:

❯ oscli dump -P 3333
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/home/tito/.pyenv/versions/3.9.9/lib/python3.9/threading.py", line 973, in _bootstrap_inner
    self.run()
  File "/home/tito/.pyenv/versions/3.9.9/lib/python3.9/threading.py", line 910, in run
    self._target(*self._args, **self._kwargs)
  File "/home/tito/code/oman/.venv/lib/python3.9/site-packages/oscpy/server.py", line 338, in _run_listener
    self._listen()
  File "/home/tito/code/oman/.venv/lib/python3.9/site-packages/oscpy/server.py", line 386, in _listen
    for address, tags, values, offset in read_packet(
  File "/home/tito/code/oman/.venv/lib/python3.9/site-packages/oscpy/parser.py", line 416, in read_packet
    timetag, messages = read_bundle(
  File "/home/tito/code/oman/.venv/lib/python3.9/site-packages/oscpy/parser.py", line 390, in read_bundle
    address, tags, values, off = read_message(
  File "/home/tito/code/oman/.venv/lib/python3.9/site-packages/oscpy/parser.py", line 309, in read_message
    value, off = parse(
  File "/home/tito/code/oman/.venv/lib/python3.9/site-packages/oscpy/parser.py", line 202, in parse
    raise ValueError(
ValueError: no known parser for type hint: 116, value: b'#bundle\x00\x00\x00\x01\x86\xa5l+D\x00\x00\x008/tuio2/frm\x00\x00,itis\x00\x00\x00\x00\x08\xdf\xc2d\x01\xf1`\xd6\x04\x18\x92 \x08\x12*Ultra 023009000107\x00\x00\x00\x00\x00\x10/tuio2/alv\x00\x00,\x00\x00\x00'

To Reproduce
Code example showing the issue:

import socket

message = b'#bundle\x00\x00\x00\x01\x86\xa5l+D\x00\x00\x008/tuio2/frm\x00\x00,itis\x00\x00\x00\x00\x08\xdf\xc2d\x01\xf1`\xd6\x04\x18\x92 \x08\x12*Ultra 023009000107\x00\x00\x00\x00\x00\x10/tuio2/alv\x00\x00,\x00\x00\x00'

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(message, ("127.0.0.1", 3333))

Expected behavior
I should not expect crashing. Maybe due to type or bundle issue, will search.

Logs/output
If applicable, add screenshots to help explain your problem.

Platform (please complete the following information):

  • OS: Windows 10, Arch linux.
  • Python 3.9 + 3.11
  • oscpy 0.6.0

Server callback error handling

Is your feature request related to a problem? Please describe.
I noticed that when error happens in the OSCThreadServer callback, the thread will crash. For example, if I bind a function to the server which has two arguments, but some evil client decided to send a message with three arguments. It will causes the server to crash.

Describe the solution you'd like
Error handling in OSCThreadServer._listen function. And it is already available in commit e337169 .

Describe alternatives you've considered
Currently I am overriding the _listen function to implement my own error handling when cb happened, but it will be nice to have a release that supports this kind of feature.

An example to send and receive a bundle

It would be useful to have an example about sending/sharing a bundle with a client.
I am currently working on how to use a bundle so I can send a list of list, latest being coordinate.

Here is my current progress :

Client:

points = [[1, 2, 3], [4, 5, 6]]

bundle = []

for i, msg in `enumerate(points):
    tag = ('/' + str(i)).encode('utf-8')
    bundle.append([tag, msg])

client.send_bundle(bundle)

Server:

def on_tag(*args):
    print(args)

for i in range(10):
    tag = ('/' + str(i)).encode('utf-8')
    server.bind(tag, on_tag, get_adress=True)

Is it good so far ?
Thanks you for your feedback.

Feature request: catch none matching address

What really would be helpfull is the possibility to have a catch none matching address.
Something like:

@osc.address(None):
def catch_none_matching(values)
...

This way you can do advanced matching in the "catch_none_matching" function.

Just my two cents.
Regards, Peter

CPU usage getting high after opening/closing server many times

Hi,

This issue might be related or not to issue #43.

In my new Blender add-on project, I implemented an automatic retry function for the OSC server that fires every 500ms. It's quite handy for the user.

Lately I observed Blender taking all of my CPUs and, after investigation, it happens when the server is not able to connect (due to wrong settings) for a long time and therefore is still trying to connect again and again. I can reproduce the problem, the CPU usage rises very slowly and steadily until the app becomes unresponsive. It doesn't happen when the add-on connects successfully at the beginning and keeps the connection running, that's why I only discover the problem now.

If after all the failed attempts the server can at least connects it doesn't solve the issue, the CPU usage stays high and the only solution is to close Blender.

Here is an extract of my code:

def retry_server():
    global osc_server, osc_in_ok
    bcw = bpy.context.window_manager
    ip = bcw.mom_osc_udp_in
    port = bcw.mom_osc_port_in

    if bcw.mom_osc_in_enable is True and osc_in_ok is False:
        # try closing a previous instance
        if osc_server is not None:
            osc_server.stop_all()
            osc_server = None
            time.sleep(.01)

        # try opening
        try:
            osc_server = OSCThreadServer(default_handler=OSC_callback)
            osc_server.listen(address=ip, port=port, default=True)
            bcw.mom_osc_alert = False
            osc_in_ok = True

        except:
            bcw.mom_osc_alert = True
            osc_server = None

    if bcw.mom_osc_in_enable is False and osc_in_ok is True:
        # try closing a previous instance
        if osc_server is not None:
            osc_server.stop_all()
            osc_server = None

    return .5

As you see I tried to clean the reference "osc_server", setting it to "None", but it doesn't solve the issue.

This is with python 3.7.0 under Ubuntu 18.04 LTS and a fresh "oscpy" copy from github.

No route to host results in crash

Describe the bug
oscpy chrashes when sent to a recipient not available on the network.

To Reproduce
Produced by sending a message to a recipient not available on the network.
Logs/output
File "/usr/local/lib/python3.8/site-packages/oscpy/client.py", line 136, in send_message
stats = send_message(
File "/usr/local/lib/python3.8/site-packages/oscpy/client.py", line 68, in send_message
sock.sendto(message, address)
OSError: [Errno 65] No route to host

Platform (please complete the following information):

  • OS: MacOS 10.15.5
  • Python 3.8
  • oscpy 0.5

New release?

Looks like there's quite a lot new since 0.3.0, how about a new release?

Example code in ReadMe won't run.

The Readme contains some example code.

One example demonstrates that servers are also clients:

  • It imports time.sleep() but never uses it. Meanwhile, it uses time.time() without importing it.
  • It has a variable called cont which is never defined.
  • It does a busy wait, which is not demonstrating a good way to use the API.
  • It keeps appending True to a list (Why not just count?), but never popping it.
  • ping takes parameters, but it isn't clear what they represent.
  • pong doesn't use the *parameter. Rename it _.

One example demonstrates that async servers.

  • It imports OSCThreadServer but uses OSCAsyncServer

One example demonstrates how Unicode is used.

  • Uses OSCThreadServer without importing.
  • Uses send_message() without the expected osc. prefix.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.