input-remapper icon indicating copy to clipboard operation
input-remapper copied to clipboard

Led test

Open cybermaus opened this issue 2 years ago • 18 comments

see https://github.com/sezanzeb/key-mapper/issues/160

cybermaus avatar Aug 19 '21 06:08 cybermaus

some difficulties that is the magic of asyncio

asyncio is indeed a bit more difficult than for example async/await in javascript. In javascript there is always one single event loop present, and in python you have to create them yourself first.

Just in case you haven't used async/await before, a brief introduction:

Each time you await something, asyncio will continue at a different place in your code that has previously stopped at an await. Await is kindof like saying "while we wait for input, lets do something else". Its just another method of concurrency.

sezanzeb avatar Aug 19 '21 18:08 sezanzeb

Ok, I believe this is complete.

I foresee the possibility to send to other devices (like in your earlier question: if I program a button on "Brand XYZ mouse" I may want to set a led or beep "Brand UVW keyboard", so the device parameter can also be something else then a bool, like a search string. But for now, it will only work if "Brand XYZ mouse" has its own LED's

Eventually, it should even be extended to EV_FF force feedback, for which this device specification could really be important. but I believe that is a bit more complex then merely sending events. If it is as simple as sending event triplets, it may already work when on the same device.

Let me know. If you accept the PR, I will also work on the documentation, and maybe even add some test cases.

cybermaus avatar Aug 20 '21 16:08 cybermaus

and maybe even add some test cases.

we will absolutely do that, I'll help you out on that and point you to where to do that later

Eventually, it should even be extended to EV_FF force feedback

please explain what EV_FF does

sezanzeb avatar Aug 21 '21 09:08 sezanzeb

In test_macros.py there is

    def test_event_1(self):
        macro = parse('e(EV_KEY, KEY_A, 1)', self.mapping)
        a_code = system_mapping.get('a')
        self.assertSetEqual(macro.get_capabilities()[EV_KEY], {a_code})
        self.assertSetEqual(macro.get_capabilities()[EV_REL], set())

        self.loop.run_until_complete(macro.run(self.handler))
        self.assertListEqual(self.result, [(EV_KEY, a_code, 1)])
        self.assertEqual(len(macro.child_macros), 0)

You could start with this as a template. self.handler needs to be modified so that the assertListEqual can check events written back to the device.

same for the led(...) macro function

sezanzeb avatar Aug 21 '21 09:08 sezanzeb

In order to test device_write you could write one in test_keycode_mapper.py since it is in that class.

maybe something like

def test_device_write(self):
    context = Context(...)
    
    # note that there is a fake `class InputDevice` in `test.py` that is patched into evdev via `patch_evdev`.
    # It probably has to be extended with a `write` function.
    # to have each inputDevice get different capabilities, take a look at the `fixtures` in `test.py`
    context.sources = [evdev.InputDevice(...), ...]
    
    keycode_mapper = KeycodeMapper(...)
    keycode_mapper.device_write(...)
    # assert stuff, maybe the patched `InputDevice` can keep track of written events,
    # just like the `write_history` of the `UInput` patch.

You can run tests via python3 tests/test.py test_keycode_mapper

sezanzeb avatar Aug 21 '21 09:08 sezanzeb

FF stands for force feedback. Not a gamer myself, but I believe it is where the game controller vibrates in your hand when you take a hit.

I suspect soon there will be a user that complains that his vibration does not work anymore when key-mapper sits in the middle. Then again, I suspect hard-core gamers turn this off anyway, at it distracts and shaves milliseconds of their response times.

Anyway, I do not know how to use or test it, but I foresee if ever that is implemented, it will also pass through this todevice() function.

cybermaus avatar Aug 21 '21 12:08 cybermaus

I should ignore the errors that the python3 tests/test.py test_keycode_mapper already give before I modify anything?

======================================================================
ERROR: test_macro_writes_to_context_uinput (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 509, in test_macro_writes_to_context_uinput
    keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
  File "/usr/lib/python3/dist-packages/keymapper/injection/keycode_mapper.py", line 441, in handle_keycode
    macro.press_key()
AttributeError: 'NoneType' object has no attribute 'press_key'

======================================================================
ERROR: test_macro_writes_to_context_uinput (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 83, in tearDown
    if macro.is_holding():
AttributeError: 'NoneType' object has no attribute 'is_holding'

======================================================================
ERROR: test_not_forward (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 83, in tearDown
    if macro.is_holding():
AttributeError: 'NoneType' object has no attribute 'is_holding'

======================================================================
ERROR: test_release_joystick_button (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 83, in tearDown
    if macro.is_holding():
AttributeError: 'NoneType' object has no attribute 'is_holding'

======================================================================
ERROR: test_subsets (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 83, in tearDown
    if macro.is_holding():
AttributeError: 'NoneType' object has no attribute 'is_holding'

======================================================================
ERROR: test_two_d_pad_macros (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 83, in tearDown
    if macro.is_holding():
AttributeError: 'NoneType' object has no attribute 'is_holding'

======================================================================
ERROR: test_wheel_combination_release_failure (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 83, in tearDown
    if macro.is_holding():
AttributeError: 'NoneType' object has no attribute 'is_holding'

======================================================================
FAIL: test_not_forward (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 202, in test_not_forward
    self.assertEqual(len(unreleased), 1)
AssertionError: 2 != 1

======================================================================
FAIL: test_wheel_combination_release_failure (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 1293, in test_wheel_combination_release_failure
    self.assertEqual(uinput_write_history[0].t, btn_down)
AssertionError: Tuples differ: (1, 73, 1) != (1, 276, 1)

First differing element 1:
73
276

- (1, 73, 1)
?      ^

+ (1, 276, 1)
?     + ^


----------------------------------------------------------------------
Ran 22 tests in 5.540s

FAILED (failures=2, errors=7)

cybermaus avatar Aug 21 '21 17:08 cybermaus

Please run python3 tests/test.py test_keycode_mapper.TestKeycodeMapper.test_macro_writes_to_context_uinput and share the complete log. They pass on my computer, I'll see if I can fix them for yours

sezanzeb avatar Aug 22 '21 09:08 sezanzeb

Ok. Just for sake to correctness, I first moved to the main branch rather then the LED branch, but it did not matter much Please note this is running on a Raspberry Pi. Also, all function I use seem to work.

pi@RasPi4SSD:~/key-mapper $ python3 tests/test.py test_keycode_mapper.TestKeycodeMapper.test_macro_writes_to_context_uinput
1363 0.02780 DEBUG groups.py:328: Discovering device paths
1363 0.02898 SPAM groups.py:362: Found "1_1_1_usb-0000:03:00.0-0", "/dev/input/event1", "Foo Device", type: keyboard
1363 0.02934 SPAM groups.py:362: Found "1_1_1_usb-0000:03:00.0-1", "/dev/input/event11", "Foo Device foo", type: mouse
1363 0.03054 SPAM groups.py:362: Found "1_1_1_usb-0000:03:00.0-1", "/dev/input/event10", "Foo Device", type: keyboard
1363 0.03084 SPAM groups.py:362: Found "1_1_1_usb-0000:03:00.0-1", "/dev/input/event13", "Foo Device", type: unknown
1363 0.03219 SPAM groups.py:362: Found "2_1_2_usb-0000:03:00.0-2", "/dev/input/event20", "Bar Device", type: keyboard
1363 0.03261 SPAM groups.py:362: Found "3_1_3_-", "/dev/input/event30", "gamepad", type: gamepad
1363 0.03395 SPAM groups.py:362: Found "5_1_5_key-mapper", "/dev/input/event40", "key-mapper Bar Device", type: keyboard
1363 0.03621 INFO groups.py:434: Found "Foo Device", "Foo Device 2", "Bar Device", "gamepad", "key-mapper Bar Device"
1363 0.04235 DEBUG state.py:64: Gathering available keycodes
1363 0.05295 INFO paths.py:50: Creating file "/tmp/key-mapper-test/xmodmap.json"
1363 0.05464 DEBUG state.py:85: Writing "/tmp/key-mapper-test/xmodmap.json"
1363 0.07304 SPAM pipe.py:64: Creating new pipe for "/tmp/key-mapper/results"
1363 0.07485 SPAM pipe.py:64: Creating new pipe for "/tmp/key-mapper/commands"
cleanup
1363 0.17613 DEBUG reader.py:194: Sending close msg to helper
1367 0.17998 SPAM macros.py:84: SharedDict got ('stop',)
1363 0.20993 INFO paths.py:50: Creating file "/tmp/key-mapper-test/config.json"
1363 0.21228 INFO config.py:269: Saved config to /tmp/key-mapper-test/config.json
1363 0.21301 DEBUG state.py:64: Gathering available keycodes
1363 0.22502 INFO paths.py:50: Creating file "/tmp/key-mapper-test/xmodmap.json"
1363 0.22613 DEBUG state.py:85: Writing "/tmp/key-mapper-test/xmodmap.json"
1363 0.23365 DEBUG reader.py:203: Clearing reader
1372 0.23619 SPAM macros.py:84: SharedDict got ('ping',)
1363 0.33935 DEBUG groups.py:328: Discovering device paths
1363 0.34015 SPAM groups.py:362: Found "1_1_1_usb-0000:03:00.0-0", "/dev/input/event1", "Foo Device", type: keyboard
1363 0.34051 SPAM groups.py:362: Found "1_1_1_usb-0000:03:00.0-1", "/dev/input/event11", "Foo Device foo", type: mouse
1363 0.34182 SPAM groups.py:362: Found "1_1_1_usb-0000:03:00.0-1", "/dev/input/event10", "Foo Device", type: keyboard
1363 0.34211 SPAM groups.py:362: Found "1_1_1_usb-0000:03:00.0-1", "/dev/input/event13", "Foo Device", type: unknown
1363 0.34335 SPAM groups.py:362: Found "2_1_2_usb-0000:03:00.0-2", "/dev/input/event20", "Bar Device", type: keyboard
1363 0.34371 SPAM groups.py:362: Found "3_1_3_-", "/dev/input/event30", "gamepad", type: gamepad
1363 0.34492 SPAM groups.py:362: Found "5_1_5_key-mapper", "/dev/input/event40", "key-mapper Bar Device", type: keyboard
1363 0.34750 INFO groups.py:434: Found "Foo Device", "Foo Device 2", "Bar Device", "gamepad", "key-mapper Bar Device"
test_macro_writes_to_context_uinput (testcases.test_keycode_mapper.TestKeycodeMapper) ...
1363 0.38732 SPAM macros.py:721: preparing macro k(a) for later execution
1363 0.38832 SPAM macros.py:617: calls k with ['a']
1363 0.38872 SPAM macros.py:658:   <class 'str'> a
1363 0.38898 SPAM macros.py:624: add call to k with ['a']
1363 0.38931 ERROR macros.py:727: Failed to parse macro "k(a)": KeyError('Unknown key "a"')
1363 0.39055 DEBUG macros.py:729: File "/usr/lib/python3/dist-packages/keymapper/injection/macros.py", line 724, in parse
    macro_object = _parse_recurse(macro, mapping)
  File "/usr/lib/python3/dist-packages/keymapper/injection/macros.py", line 640, in _parse_recurse
    function[0](*params)
  File "/usr/lib/python3/dist-packages/keymapper/injection/macros.py", line 371, in keycode
    raise KeyError(f'Unknown key "{symbol}"')
1363 0.39115 DEBUG context.py:96: Parsing macros
1363 0.39143 DEBUG context.py:108: No macros configured
ERROR
ERROR

======================================================================
ERROR: test_macro_writes_to_context_uinput (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 509, in test_macro_writes_to_context_uinput
    keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
  File "/usr/lib/python3/dist-packages/keymapper/injection/keycode_mapper.py", line 429, in handle_keycode
    macro.press_key()
AttributeError: 'NoneType' object has no attribute 'press_key'

======================================================================
ERROR: test_macro_writes_to_context_uinput (testcases.test_keycode_mapper.TestKeycodeMapper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pi/key-mapper/tests/testcases/test_keycode_mapper.py", line 83, in tearDown
    if macro.is_holding():
AttributeError: 'NoneType' object has no attribute 'is_holding'

----------------------------------------------------------------------
Ran 1 test in 0.010s

FAILED (errors=2)
1372 0.39833 SPAM macros.py:84: SharedDict got ('stop',)
pi@RasPi4SSD:~/key-mapper $

cybermaus avatar Aug 22 '21 10:08 cybermaus

are you running the test from a tty or ssh? the issue is probably that xmodmap -pke doesn't yield any output, but the test expects it to do

sezanzeb avatar Aug 22 '21 11:08 sezanzeb

please pull the latest main and try again

sezanzeb avatar Aug 22 '21 11:08 sezanzeb

From ssh indeed, sorry Even without your fixes, it indeed ran OK from tty

With your fixes, it runs OK from ssh as well. Is that now fixed and ssh as good to run the test as tty, or should I best always use the latter.

cybermaus avatar Aug 22 '21 12:08 cybermaus

I think ssh should be fine now too. Obviously tests that require the gui to run would still fail, but those are just in test_integration.py

sezanzeb avatar Aug 22 '21 12:08 sezanzeb

Is that now fixed and ssh as good to run the test as tty, or should I best always use the latter.

There is also test_numlock in test_injector.py which will probably fail, but anyways, for this only test_macros.py and test_keycode_mapper.py are of interest

sezanzeb avatar Aug 24 '21 06:08 sezanzeb

I just saw your request for contributor. Please understand I am also busy, but more importantly I ran into some trouble with writing the test routines. I started a few times, but got stuck on how to write a test routine for the 'todevice()' function, or rather, its underlying device_write() function.

I may need some additional hints on this.

cybermaus avatar Aug 31 '21 09:08 cybermaus

Don't worry I'm not blaming you. Your work is greatly appreciated!

where are you stuck? If you have questions you can always ask. Maintaining key-mapper and communicating with people works quite well even though my focus is currently a different one.

I think I'll dedicate one really intense week for key-mapper programming each winter/spring during semester break and see how far I can get. The really big problems have already been solved after all.

sezanzeb avatar Aug 31 '21 09:08 sezanzeb

I just merged the latest main into led_test: https://github.com/sezanzeb/key-mapper/tree/led_test_merge

There have been some conflicts because I changed the architecture around a bit, but I haven't tried if led and to_device it still works properly after the merge.

sezanzeb avatar Sep 29 '21 21:09 sezanzeb

This was closed automatically when I was tinkering around with the branches, sorry

sezanzeb avatar Nov 03 '21 17:11 sezanzeb

there will be a lot of changes once beta is merged in, because the architecture is completely different. If this is still relevant, it needs to be rewritten, sorry. Idk how easy/hard it is with the new architecture

sezanzeb avatar Oct 23 '22 17:10 sezanzeb

Ok noted. If I ever have a need to update my Pi, I guess I am in for some work. But as it stands, the Pi and mini-keyboard is working. Thanks for letting me know.

cybermaus avatar Oct 23 '22 18:10 cybermaus

PS in all this time, no one else has requested or used LED's?

cybermaus avatar Oct 23 '22 18:10 cybermaus

PS in all this time, no one else has requested or used LED's?

no

sezanzeb avatar Oct 23 '22 18:10 sezanzeb