IR-remote with Kivy (coding question)

42 views
Skip to first unread message

Gregor Verweyen

unread,
Sep 28, 2022, 1:38:12 PM9/28/22
to Kivy development
on my Raspberry I have a IR-remote installed. If I add into the config.ini, section input the line:' remote = hidinput,/dev/input/event5' then I get (somehow to keyboard keys translated) events for the keys 'left', 'right', ' up'... but other keys, like 'KEY_OK' just deliver a log message 'unhandled code...' Trying to add such codes (just for testing) in the file hidinput.py into the 'keyboard_keys' list only works, if I map the remote code to a key which exists in Keybord.keycodes. This is acceptable for the 'KEY_OK', which can be mapped to 'enter' but there are a lot of other keys where such mapping makes the code unreadable, like 'KEY_PLAY' or 'KEY_VOLUME_UP'...
Can I extend the Keyboard.keycodes list somewhere or create my own list ?
My preferred approach would be to write a new module e.g. remoteinput.py an register it in the __INIT.py__ I tried this and it seems to work.

Gregor Verweyen

unread,
Oct 9, 2022, 3:44:02 PM10/9/22
to Kivy development
My quick &dirty solution - probably developers us this to add an official version
I created a new file remoteinput.py and added the following lines into __init__.py
    try:
        import kivy.input.providers.remoteinput
    except:
        err = 'Input: RemoteInput is not supported by your version of linux'
        Logger.exception(err)

here the remoteinput.py
<code>
# coding utf-8
'''
Native support for remotes input from the linux kernel
==================================================

Support starts from 2.6.32-ubuntu, or 2.6.34.

To configure remoteinput, add this to your configuration::

    [input]
    # devicename = remoteinput,/dev/input/eventXX
    # example with remote at Raspberry GPIO
    remote = remoteinput,/dev/input/event5

.. note::
    You must have read access to the input event.


'''
import os
from kivy.input.motionevent import MotionEvent

__all__ = ('RemoteInputMotionEventProvider', 'RemoteMotionEvent')

# late imports
Window = None
Keyboard = None


class RemoteMotionEvent(MotionEvent):
    pass



if 'KIVY_DOC' in os.environ:
    # documentation hack
    RemoteInputMotionEventProvider = None

else:
    import threading
    import collections
    import struct
    import fcntl
    from kivy.input.provider import MotionEventProvider
    from kivy.input.factory import MotionEventFactory
    from kivy.logger import Logger

    #
    # This part is taken from linux-source-2.6.32/include/linux/input.h
    #

    # value types
    EV_UP = 0x00
    EV_DN = 0x01
    EV_REP = 0x02
    # Event types
    EV_SYN = 0x00
    EV_REM = 0x04

    EVIOCGNAME = 2147501318
   
    # if a button shows the warning: 'not recognized by SDL' this is causes by the KEY_ name (e.g. KEY_SETUP) occurs because SDL tries to process IR-codes directly. The button still works here - this are the codes from the ir-keytable .toml file
    remote_buttons = {
        0x3b0c:  'KEY_POWER',
        0x3b58:  'KEY_UP',
        0x3b59:  'KEY_DOWN',
        0x3b5a:  'KEY_LEFT',
        0x3b5b:  'KEY_RIGHT',
        0x3b92:  'KEY_HOME',
        0x3b5c:  'KEY_OK',
        0x3b69:  'KEY_SEARCH',
        0x3b19:  'KEY_FAVORITES',
        0x3b2c:  'KEY_PLAYPAUSE',
        0x3b20:  'KEY_NEXT',
        0x3b83:  'KEY_BACK',
        0x3b21:  'KEY_PREVIOUS',
        0x3b0d:  'KEY_MUTE',
        0x3b31:  'KEY_STOP',
        0x3b10:  'KEY_VOLUMEUP',
        0x3b11:  'KEY_VOLUMEDOWN',
        0x3b00:  'KEY_0',
        0x3b01:  'KEY_1',
        0x3b02:  'KEY_2',
        0x3b03:  'KEY_3',
        0x3b04:  'KEY_4',
        0x3b05:  'KEY_5',
        0x3b06:  'KEY_6',
        0x3b07:  'KEY_7',
        0x3b08:  'KEY_8',
        0x3b09:  'KEY_9',
        0x3b16:  'KEY_SCROLLUP',
        0x3b17:  'KEY_SCROLLDOWN',
        0x3b94:  'KEY_LIBRARY',
        0x3b15:  'KEY_CLEAR',
        0x3b47:  'KEY_SLEEP',
        0x3b1d:  'KEY_REPEAT',
        0x3b1c:  'KEY_SHUFFLE',
        0x3b98:  'KEY_BRIGHTNESS',                # press may show warning 'not recognized by SDL' but works
        0x3b1b:  'KEY_RADIO',
        0x3b4b:  'KEY_AUX',
        0x3b1a:  'KEY_ONLINE_SERVICES',
        0x3ba5:  'KEY_SNOOZE',
        0x3be6:  'KEY_CLOCK',
        0x3b4c:  'KEY_DOCKING',
        0x3b54:  'KEY_SETUP',                     # press may show warning 'not recognized by SDL' but works
        0x3b6d:  'KEY_ALARM',
        0x3ba4:  'KEY_NOW_PLAYING',
        0x3b8c:  'KEY_CAPSLOCK',
        0x3b53:  'KEY_SOUND_MENU',
        0x3ba6:  'KEY_LIVINGSOUND',
        0x3ba7:  'KEY_FULLSOUND',
        0x3baa:  'KEY_NEUTRAL',
    }



    # sizeof(struct input_event)
    struct_input_event_sz = struct.calcsize('LLHHi')
    struct_input_absinfo_sz = struct.calcsize('iiiiii')
    sz_l = struct.calcsize('Q')

    class RemoteInputMotionEventProvider(MotionEventProvider): #HIDInput

        remote_code = 0


        def __init__(self, device, args):
            super(RemoteInputMotionEventProvider, self).__init__(device, args)
            global Window, Keyboard

            if Window is None:
                from kivy.core.window import Window
            if Keyboard is None:
                from kivy.core.window import Keyboard
                for code in remote_buttons:
                    Keyboard.keycodes.update({remote_buttons[code].lower(): code})  # add the remote buttons and codes into keycodes list

            self.input_fn = None
            self.default_ranges = dict()

            # split arguments
            args = args.split(',')
            if not args:
                Logger.error('RemoteInput: Filename missing in configuration')
                Logger.error('RemoteInput: Use /dev/input/event0 for example')
                return None

            # read filename
            self.input_fn = args[0]
            Logger.info('RemoteInput: Read event from <%s>' % self.input_fn)

            # read parameters
            for arg in args[1:]:
                if arg == '':
                    continue
                arg = arg.split('=')

                # ensure it's a key = value
                if len(arg) != 2:
                    Logger.error('RemoteInput: invalid parameter '
                                 '%s, not in key=value format.' % arg)
                    continue

                # ensure the key exist
                key, value = arg
                if key not in HIDInputMotionEventProvider.options:
                    Logger.error('RemoteInput: unknown %s option' % key)
                    continue

                # ensure the value
                try:
                    self.default_ranges[key] = int(value)
                except ValueError:
                    err = 'RemoteInput: invalid value "%s" for "%s"' % (
                        key, value)
                    Logger.error(err)
                    continue

                # all good!
                Logger.info('RemoteInput: Set custom %s to %d' % (
                    key, int(value)))


        def start(self):
            if self.input_fn is None:
                return
            self.uid = 0
            self.queue = collections.deque()
            self.dispatch_queue = []
            self.thread = threading.Thread(
                name=self.__class__.__name__,
                target=self._thread_run,
                kwargs=dict(
                    queue=self.queue,
                    input_fn=self.input_fn,
                    device=self.device))
            self.thread.daemon = True
            self.thread.start()

        def _thread_run(self, **kwargs):
            input_fn = kwargs.get('input_fn')
            queue = self.queue
            dispatch_queue = self.dispatch_queue
            device = kwargs.get('device')


            def process_as_remote(tv_sec, tv_usec, ev_type, ev_code, ev_value):

                if ev_type == EV_SYN:
                    return

                if ev_type == EV_REM:                                                         # first event delivers the ir-key-code
                    self.remote_code = ev_value
                    if ev_value not in remote_buttons:
                        Logger.warn('RemoteInput: unhandled remote code: {}'.format(ev_code))
                        return
                if not 0 <= ev_value <= 2:                                                    # we can have value 2 for press&hold button
                    return
               
                z = remote_buttons[self.remote_code]
                if z.lower() not in Keyboard.keycodes:
                    # or if it is not in this LUT
                    Logger.warn('RemoteInput: unhandled button: {}'.
                                format(z))
                    return

                keycode = Keyboard.keycodes[z.lower()]
                if ev_value == EV_DN:                                                          # button down
                    dispatch_queue.append(('key_down', (keycode, ev_code, z.lower() ,[])))
                elif ev_value == EV_UP:
                    dispatch_queue.append(('key_up', (keycode, ev_code, z.lower() ,[])))
                elif ev_value == EV_REP:                                                        # for key hold pressed (does not work - see below)
                    dispatch_queue.append(('key_down', (keycode, ev_code, z.lower() ,[])))     # repeat key_down (key-repeat throws error:
                                                                                                 # kivy._event.EventDispatcher.dispatchKeyError: 'on_key_repeat'

            # open the input
            fd = open(input_fn, 'rb')

            # get the controller name (EVIOCGNAME)
            device_name = fcntl.ioctl(fd, EVIOCGNAME + (256 << 16),
                                      " " * 256).decode().strip()
            Logger.info('RemoteInput: using <%s>' % device_name)

            # read until the end
            while fd:

                data = fd.read(struct_input_event_sz)
                if len(data) < struct_input_event_sz:
                    break
                # extract each event
                for i in range(int(len(data) / struct_input_event_sz)):
                    ev = data[i * struct_input_event_sz:]
                    # extract timeval + event infos
                    infos = struct.unpack('LLHHi', ev[:struct_input_event_sz])
                    process_as_remote(*infos)

        def update(self,dispatch_fn):
            # dispatch all events from threads
            dispatch_queue = self.dispatch_queue
            n = len(dispatch_queue)
            for name, args in dispatch_queue[:n]:
                if name == 'key_down':
                    if not Window.dispatch('on_key_down', *args):
                        Window.dispatch('on_keyboard', *args)
                elif name == 'key_up':
                    Window.dispatch('on_key_up', *args)
                elif name == 'key_repeat':              # as said above - repeat does not work, however we see multiple key_down messages (without key_up in between)
                    Window.dispatch('on_key_repeat', *args)
            del dispatch_queue[:n]

            try:
                while True:
                    event_type, touch = self.queue.popleft()
                    dispatch_fn(event_type, touch)
            except:
                pass

    MotionEventFactory.register('remoteinput', RemoteInputMotionEventProvider)
</code>

Mau Arou

unread,
Oct 12, 2022, 7:11:21 PM10/12/22
to kivy...@googlegroups.com
Thank you. ButHaveOtherProb.i try Inst kivy.....Error=fetchURL???

--
You received this message because you are subscribed to the Google Groups "Kivy development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-dev/b2585250-fd06-4950-9d4d-75b2e0ed0626n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages