Version

Quick search

Table Of Contents

Source code for kivy.input.providers.tuio

'''
TUIO Input Provider
===================

TUIO is the de facto standard network protocol for the transmission of
touch and fiducial information between a server and a client. To learn
more about TUIO (which is itself based on the OSC protocol), please
refer to http://tuio.org -- The specification should be of special
interest.

Configure a TUIO provider in the config.ini
-------------------------------------------

The TUIO provider can be configured in the configuration file in the
``[input]`` section::

    [input]
    # name = tuio,<ip>:<port>
    multitouchtable = tuio,192.168.0.1:3333

Configure a TUIO provider in the App
------------------------------------

You must add the provider before your application is run, like this::

    from kivy.app import App
    from kivy.config import Config

    class TestApp(App):
        def build(self):
            Config.set('input', 'multitouchscreen1', 'tuio,0.0.0.0:3333')
            # You can also add a second TUIO listener
            # Config.set('input', 'source2', 'tuio,0.0.0.0:3334')
            # Then do the usual things
            # ...
            return
'''

__all__ = ('TuioMotionEventProvider', 'Tuio2dCurMotionEvent',
           'Tuio2dObjMotionEvent')

from kivy.logger import Logger

from functools import partial
from collections import deque
from kivy.input.provider import MotionEventProvider
from kivy.input.factory import MotionEventFactory
from kivy.input.motionevent import MotionEvent
from kivy.input.shape import ShapeRect


[docs]class TuioMotionEventProvider(MotionEventProvider): '''The TUIO provider listens to a socket and handles some of the incoming OSC messages: * /tuio/2Dcur * /tuio/2Dobj You can easily extend the provider to handle new TUIO paths like so:: # Create a class to handle the new TUIO type/path # Replace NEWPATH with the pathname you want to handle class TuioNEWPATHMotionEvent(MotionEvent): def depack(self, args): # In this method, implement 'unpacking' for the received # arguments. you basically translate from TUIO args to Kivy # MotionEvent variables. If all you receive are x and y # values, you can do it like this: if len(args) == 2: self.sx, self.sy = args self.profile = ('pos', ) self.sy = 1 - self.sy super().depack(args) # Register it with the TUIO MotionEvent provider. # You obviously need to replace the PATH placeholders appropriately. TuioMotionEventProvider.register('/tuio/PATH', TuioNEWPATHMotionEvent) .. note:: The class name is of no technical importance. Your class will be associated with the path that you pass to the ``register()`` function. To keep things simple, you should name your class after the path that it handles, though. ''' __handlers__ = {} def __init__(self, device, args): super().__init__(device, args) args = args.split(',') if len(args) == 0: Logger.error('Tuio: Invalid configuration for TUIO provider') Logger.error('Tuio: Format must be ip:port (eg. 127.0.0.1:3333)') err = 'Tuio: Current configuration is <%s>' % (str(','.join(args))) Logger.error(err) return ipport = args[0].split(':') if len(ipport) != 2: Logger.error('Tuio: Invalid configuration for TUIO provider') Logger.error('Tuio: Format must be ip:port (eg. 127.0.0.1:3333)') err = 'Tuio: Current configuration is <%s>' % (str(','.join(args))) Logger.error(err) return self.ip, self.port = args[0].split(':') self.port = int(self.port) self.handlers = {} self.oscid = None self.tuio_event_q = deque() self.touches = {}
[docs] @staticmethod def register(oscpath, classname): '''Register a new path to handle in TUIO provider''' TuioMotionEventProvider.__handlers__[oscpath] = classname
[docs] @staticmethod def unregister(oscpath, classname): '''Unregister a path to stop handling it in the TUIO provider''' if oscpath in TuioMotionEventProvider.__handlers__: del TuioMotionEventProvider.__handlers__[oscpath]
[docs] @staticmethod def create(oscpath, **kwargs): '''Create a touch event from a TUIO path''' if oscpath not in TuioMotionEventProvider.__handlers__: raise Exception('Unknown %s touch path' % oscpath) return TuioMotionEventProvider.__handlers__[oscpath](**kwargs)
[docs] def start(self): '''Start the TUIO provider''' try: from oscpy.server import OSCThreadServer except ImportError: Logger.info( 'Please install the oscpy python module to use the TUIO ' 'provider.' ) raise self.oscid = osc = OSCThreadServer() osc.listen(self.ip, self.port, default=True) for oscpath in TuioMotionEventProvider.__handlers__: self.touches[oscpath] = {} osc.bind(oscpath, partial(self._osc_tuio_cb, oscpath))
[docs] def stop(self): '''Stop the TUIO provider''' self.oscid.stop_all()
[docs] def update(self, dispatch_fn): '''Update the TUIO provider (pop events from the queue)''' # read the Queue with event while True: try: value = self.tuio_event_q.pop() except IndexError: # queue is empty, we're done for now return self._update(dispatch_fn, value)
def _osc_tuio_cb(self, oscpath, address, *args): self.tuio_event_q.appendleft([oscpath, address, args]) def _update(self, dispatch_fn, value): oscpath, command, args = value # verify commands if command not in [b'alive', b'set']: return # move or create a new touch if command == b'set': id = args[0] if id not in self.touches[oscpath]: # new touch touch = TuioMotionEventProvider.__handlers__[oscpath]( self.device, id, args[1:]) self.touches[oscpath][id] = touch dispatch_fn('begin', touch) else: # update a current touch touch = self.touches[oscpath][id] touch.move(args[1:]) dispatch_fn('update', touch) # alive event, check for deleted touch if command == b'alive': alives = args to_delete = [] for id in self.touches[oscpath]: if id not in alives: # touch up touch = self.touches[oscpath][id] if touch not in to_delete: to_delete.append(touch) for touch in to_delete: dispatch_fn('end', touch) del self.touches[oscpath][touch.id]
class TuioMotionEvent(MotionEvent): '''Abstraction for TUIO touches/fiducials. Depending on the tracking software you use (e.g. Movid, CCV, etc.) and its TUIO implementation, the TuioMotionEvent object can support multiple profiles such as: * Fiducial ID: profile name 'markerid', attribute ``.fid`` * Position: profile name 'pos', attributes ``.x``, ``.y`` * Angle: profile name 'angle', attribute ``.a`` * Velocity vector: profile name 'mov', attributes ``.X``, ``.Y`` * Rotation velocity: profile name 'rot', attribute ``.A`` * Motion acceleration: profile name 'motacc', attribute ``.m`` * Rotation acceleration: profile name 'rotacc', attribute ``.r`` ''' __attrs__ = ('a', 'b', 'c', 'X', 'Y', 'Z', 'A', 'B', 'C', 'm', 'r') def __init__(self, *args, **kwargs): kwargs.setdefault('is_touch', True) kwargs.setdefault('type_id', 'touch') super().__init__(*args, **kwargs) # Default argument for TUIO touches self.a = 0.0 self.b = 0.0 self.c = 0.0 self.X = 0.0 self.Y = 0.0 self.Z = 0.0 self.A = 0.0 self.B = 0.0 self.C = 0.0 self.m = 0.0 self.r = 0.0 angle = property(lambda self: self.a) mot_accel = property(lambda self: self.m) rot_accel = property(lambda self: self.r) xmot = property(lambda self: self.X) ymot = property(lambda self: self.Y) zmot = property(lambda self: self.Z)
[docs]class Tuio2dCurMotionEvent(TuioMotionEvent): '''A 2dCur TUIO touch.'''
[docs] def depack(self, args): if len(args) < 5: self.sx, self.sy = list(map(float, args[0:2])) self.profile = ('pos', ) elif len(args) == 5: self.sx, self.sy, self.X, self.Y, self.m = list(map(float, args[0:5])) self.Y = -self.Y self.profile = ('pos', 'mov', 'motacc') else: self.sx, self.sy, self.X, self.Y = list(map(float, args[0:4])) self.m, width, height = list(map(float, args[4:7])) self.Y = -self.Y self.profile = ('pos', 'mov', 'motacc', 'shape') if self.shape is None: self.shape = ShapeRect() self.shape.width = width self.shape.height = height self.sy = 1 - self.sy super().depack(args)
[docs]class Tuio2dObjMotionEvent(TuioMotionEvent): '''A 2dObj TUIO object. '''
[docs] def depack(self, args): if len(args) < 5: self.sx, self.sy = args[0:2] self.profile = ('pos', ) elif len(args) == 9: self.fid, self.sx, self.sy, self.a, self.X, self.Y = args[:6] self.A, self.m, self.r = args[6:9] self.Y = -self.Y self.profile = ('markerid', 'pos', 'angle', 'mov', 'rot', 'motacc', 'rotacc') else: self.fid, self.sx, self.sy, self.a, self.X, self.Y = args[:6] self.A, self.m, self.r, width, height = args[6:11] self.Y = -self.Y self.profile = ('markerid', 'pos', 'angle', 'mov', 'rot', 'rotacc', 'acc', 'shape') if self.shape is None: self.shape = ShapeRect() self.shape.width = width self.shape.height = height self.sy = 1 - self.sy super().depack(args)
class Tuio2dBlbMotionEvent(TuioMotionEvent): '''A 2dBlb TUIO object. # FIXME 3d shape are not supported /tuio/2Dobj set s i x y a X Y A m r /tuio/2Dblb set s x y a w h f X Y A m r ''' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.profile = ('pos', 'angle', 'mov', 'rot', 'rotacc', 'acc', 'shape') def depack(self, args): self.sx, self.sy, self.a, self.X, self.Y, sw, sh, sd, \ self.A, self.m, self.r = args self.Y = -self.Y if self.shape is None: self.shape = ShapeRect() self.shape.width = sw self.shape.height = sh self.sy = 1 - self.sy super().depack(args) # registers TuioMotionEventProvider.register(b'/tuio/2Dcur', Tuio2dCurMotionEvent) TuioMotionEventProvider.register(b'/tuio/2Dobj', Tuio2dObjMotionEvent) TuioMotionEventProvider.register(b'/tuio/2Dblb', Tuio2dBlbMotionEvent) MotionEventFactory.register('tuio', TuioMotionEventProvider)