KMKfw / kmk_firmware

Clackety Keyboards Powered by Python
https://kmkfw.zulipchat.com
Other
1.38k stars 474 forks source link

adding pmw3360 modules #797

Open peterwhitesell opened 1 year ago

peterwhitesell commented 1 year ago

Adds pmw3360 trackball module for the pmw3360 motion sensor

PMW3360

For using PMW3360 motion sensor for pointer, scrolling and volume. The default behavior converts sensor XY movement into cursor XY movement.

from kmk.modules.PMW3360 import PMW3360
keyboard.modules.append(PMW3360(
    cs=board.GP0,
    sclk=board.GP2,
    miso=board.GP4,
    mosi=board.GP3,
    invert_x=False,
    invert_y=True,
    flip_xy=False,
    lift_config=0x04,
    on_move=lambda keyboard: None,
    scroll_layers=[1, 2],
    volume_layers=[3],
))

The firmware for this sensor has to be placed in kmk\modules\PMW3360_firmware.py

firmware = (
    b'\x01'
    b'\x04'
    ...
)

Scrolling and Volume

Scrolling and Volumne control can be enabled either in key event handlers, e.g.

...
pmw3360=PWM3360(...)
def ball_scroll_enable(key, keyboard, *args):
    pmw3360.set_scroll(True)
    return True
def ball_scroll_disable(key, keyboard, *args):
    pmw3360.set_scroll(False)
    return True
def ball_volume_enable(key, keyboard, *args):
    pmw3360.start_volume_control()
    return True
def ball_volume_disable(key, keyboard, *args):
    pmw3360.start_volume_control(False)
    return True
KC.A.before_press_handler(ball_scroll_enable)
KC.A.before_release_handler(ball_scroll_disable)
KC.B.before_press_handler(ball_volume_enable)
KC.B.before_release_handler(ball_volume_disable)

or via layers, e.g.

pmw3360=PWM3360(
    scroll_layers=[1, 2],
    volume_layers=[3]
)

Note The default Mouse device with KMK is kept minimal so it can work to support running on smaller micro controllers. To enable horizontal scrolling, support for panning (mouse wheel left/right) has to be explicitly enabled in boot.py with the bootcfg module.

Constructor parameters

Param Default Description
cs Chip Select pin
sclk SPI Clock pin
miso MISO pin
mosi MOSI pin
invert_x False Invert x axis movement
invert_y False Invert y axis movement
flip_xy False Swap X and Y axes
lift_config 0x04 Adjust for sensor distance
on_move lambda keyboard: None Add move event behavior
scroll_layers [] Movement is treated as scrolling on these layers
volume_layers [] Movement is treated as volume change on these layers
bullwinkle3000 commented 1 year ago

Really glad to see this PR and just wanted to note that I originally forked this from https://github.com/kbjunky/Dactyl5 when he was experiencing weirdness in the driver with debugging enabled. I "fixed" that by adding the same workaround as QMK uses in their pmw33xx_common.c driver. At line 203, a conditional checks for an odd state and resets burst mode which uses the recovery logic in QMK's driver.

xs5871 commented 1 year ago

I'd also like to see this merged. Skimming over the code, we may need a couple of iterations of review to get there. Let's start with linting. Run make fix-isort fix-formatting for automatic corrections and before every push make sure make test passes. At the end of the code review, we'll need documentation; you can hold off on that for now.

peterwhitesell commented 1 year ago

Hey @xs5871, I did the isort and formatting fixes and pushed. make test didn't pass for me, but they are not passing for me on master either, and it's the same 12 failures on this branch.

xs5871 commented 1 year ago

Please share the error output.

peterwhitesell commented 1 year ago

Of course! first error:

kmk_firmware $ make test
./kmk/modules/autoshift.py:33:32: W601 .has_key() is deprecated, use 'in'
make: *** [lint] Error 1

if I fix that in autoshift.py, replacing keyboard._hid_helper.has_key(KC.LSHIFT) with KC.LSHIFT in keyboard._hid_helper.has_key, I get to the test errors:

kmk_firmware $ make test
EEEEEEEEEEEE
======================================================================
ERROR: tests.test_autoshift (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_autoshift
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_autoshift.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_capsword (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_capsword
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_capsword.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_combos (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_combos
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_combos.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_holdtap (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_holdtap
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_holdtap.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_kmk_extension_stringy_keymaps (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_kmk_extension_stringy_keymaps
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_kmk_extension_stringy_keymaps.py", line 3, in <module>
    from kmk.extensions.stringy_keymaps import StringyKeymaps
  File "/Users/peter/Dev/kmk_firmware/kmk/extensions/stringy_keymaps.py", line 2, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_kmk_keyboard (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_kmk_keyboard
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_kmk_keyboard.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_kmk_keys (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_kmk_keys
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_kmk_keys.py", line 3, in <module>
    from kmk.keys import KC, Key, ModifierKey, make_key
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_layers (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_layers
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_layers.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_oneshot (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_oneshot
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_oneshot.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_sticky_mod (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_sticky_mod
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_sticky_mod.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_string_substitution (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_string_substitution
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_string_substitution.py", line 3, in <module>
    from kmk.keys import ALL_ALPHAS, ALL_NUMBERS, KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

======================================================================
ERROR: tests.test_tapdance (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_tapdance
Traceback (most recent call last):
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Users/peter/.asdf/installs/python/3.6.15/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/peter/Dev/kmk_firmware/tests/test_tapdance.py", line 3, in <module>
    from kmk.keys import KC
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 495, in <module>
    class Key:
  File "/Users/peter/Dev/kmk_firmware/kmk/keys.py", line 508, in Key
    meta: object = object(),
TypeError: 'type' object is not subscriptable

----------------------------------------------------------------------
Ran 12 tests in 0.001s

FAILED (errors=12)
make: *** [unit-tests] Error 1

Do you not see these symptoms? maybe my dev env isn't set up correctly. I'm using python 3.6.15:

kmk_firmware $ pipenv run python --version
Python 3.6.15
xs5871 commented 1 year ago

3.6 is ancient, try a current version.

peterwhitesell commented 1 year ago

3.6 is ancient, try a current version.

Indeed it is. Saw it in the docs somewhere. I also tried 3.9, as seen in the dockerfile, with the same results. What version do you recommend?

xs5871 commented 1 year ago

It worked just before you tried to use it (see #801). My guess is flake8 very recently dropped python 3.9 support

kbjunky commented 1 year ago

Thanks for fixing my code and happy to see this merged into KMK.

xs5871 commented 1 year ago

Any updates on making the linter happy with the updated library version requirements?

peterwhitesell commented 1 year ago

hey @xs5871, make fix-isort and make fix-formatting are passing for me now (🥳). I see make test is still failing, with actual test failures now, though they are failing on master as well. they are AssertionError: reports don't match up failures, and they seem flakey. here's an example failure with debug_enabled turned on for the tests that were failing:

peterwhitesell commented 1 year ago
..........match: 2 combo, within timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 3 combo, within timeout, shuffled
assert mods: 0 == 0 keys: {'Y'} == {'Y'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo + overlap, after timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'C'} == {'C'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo + overlap, interleaved, after timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: {'C', 'X'} == {'C', 'X'} matching?: True
assert mods: 0 == 0 keys: {'C'} == {'C'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo hold + other, interleaved, after timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: {'X', 'E'} == {'X', 'E'} matching?: True
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo hold + overlap, interleaved, after timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: {'C', 'X'} == {'C', 'X'} matching?: True
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: other + 2 combo, after timeout
assert mods: 0 == 0 keys: {'E'} == {'E'} matching?: True
assert mods: 0 == 0 keys: {'X', 'E'} == {'X', 'E'} matching?: True
assert mods: 0 == 0 keys: {'E'} == {'E'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo + other, after timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: {'X', 'E'} == {'X', 'E'} matching?: True
assert mods: 0 == 0 keys: {'E'} == {'E'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo, partial release and repeat
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo, partial release and repeat
assert mods: 0 == 0 keys: {'Y'} == {'Y'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'Y'} == {'Y'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo + 2 combo, after timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: {'X', 'Z'} == {'X', 'Z'} matching?: True
assert mods: 0 == 0 keys: {'Z'} == {'Z'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 combo hold + 2 combo, after timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: {'X', 'Z'} == {'X', 'Z'} matching?: True
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: partial combo, after timeout
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: partial combo, repeated
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: partial combo, repeated
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: 3 combo after timout
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: {'A', 'C'} == {'A', 'C'} matching?: True
assert mods: 0 == 0 keys: {'A', 'B', 'C'} == {'A', 'B', 'C'} matching?: True
assert mods: 0 == 0 keys: {'A', 'C'} == {'A', 'C'} matching?: True
assert mods: 0 == 0 keys: {'C'} == {'C'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: other + 2 combo within timeout
assert mods: 0 == 0 keys: {'E'} == {'E'} matching?: True
assert mods: 0 == 0 keys: {'A', 'E'} == {'A', 'E'} matching?: True
assert mods: 0 == 0 keys: {'A', 'B', 'E'} == {'A', 'B', 'E'} matching?: True
assert mods: 0 == 0 keys: {'A', 'E'} == {'A', 'E'} matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: 2 combo + other within timeout
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: {'A', 'B'} == {'A', 'B'} matching?: True
assert mods: 0 == 0 keys: {'A', 'B', 'E'} == {'A', 'B', 'E'} matching?: True
assert mods: 0 == 0 keys: {'A', 'E'} == {'A', 'E'} matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: 2 combo after timeout
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: Combo + other, within timeout
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: {'A', 'B'} == {'A', 'B'} matching?: True
assert mods: 0 == 0 keys: {'A', 'B', 'E'} == {'A', 'B', 'E'} matching?: True
assert mods: 0 == 0 keys: {'B', 'E'} == {'B', 'E'} matching?: True
assert mods: 0 == 0 keys: {'E'} == {'E'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: Combo + other, within timeout
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: {'A', 'E'} == {'A', 'E'} matching?: True
assert mods: 0 == 0 keys: {'A', 'B', 'E'} == {'A', 'B', 'E'} matching?: True
assert mods: 0 == 0 keys: {'B', 'E'} == {'B', 'E'} matching?: True
assert mods: 0 == 0 keys: {'E'} == {'E'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: Combo containing layer switch, within timeout
assert mods: 0 == 0 keys: {'Z'} == {'Z'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match: Combo containing layer switch + other, within timeout
assert mods: 0 == 0 keys: {'1'} == {'1'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: Other pressed and released before combo, delay after other press but within the combo timeout
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'Z'} == {'Z'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: Other pressed and released before combo, delay after other release but within the combo timeout
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'Z'} == {'Z'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: Other pressed and released before combo, delay after other pressed but within the combo timeout, other is part of another combo
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'Z'} == {'Z'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: Other pressed and released before combo, delay after other release but within the combo timeout, other is part of another combo
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'Z'} == {'Z'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
.match: leader sequence, within timeout
assert mods: 0 == 0 keys: {'V'} == {'V'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 sequence, within timeout
assert mods: 0 == 0 keys: {'X'} == {'X'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 2 sequence, within long timeout
assert mods: 0 == 0 keys: {'Z'} == {'Z'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match: 3 sequence, within timeout
assert mods: 0 == 0 keys: {'1'} == {'Y'} matching?: False
assert mods: 0 == 0 keys: set() == set() matching?: False
assert mods: 0 == None keys: {'2'} == {None} matching?: False
assert mods: 0 == None keys: set() == {None} matching?: False
assert mods: 0 == None keys: {'HASH'} == {None} matching?: False
assert mods: 0 == None keys: set() == {None} matching?: False
FF.................................................multi-character key, single-character value
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
multi-character value, single-character key
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
shifted alphanumeric or symbol in key and/or value
assert mods: 2 == 2 keys: set() == set() matching?: True
assert mods: 2 == 2 keys: {'2'} == {'2'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
backspace is only tapped as many times as necessary to delete the difference between the key and value
assert mods: 0 == 0 keys: {'D'} == {'D'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'C'} == {'C'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'C'} == {'C'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
the presence of non-shift modifiers prevents a multi-character match
assert mods: 1 == 1 keys: set() == set() matching?: True
assert mods: 1 == 1 keys: {'A'} == {'A'} matching?: True
assert mods: 1 == 1 keys: set() == set() matching?: True
assert mods: 1 == 1 keys: {'A'} == {'A'} matching?: True
assert mods: 1 == 1 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
the presence of non-shift modifiers prevents a single-character match
assert mods: 1 == 1 keys: set() == set() matching?: True
assert mods: 1 == 1 keys: {'B'} == {'B'} matching?: True
assert mods: 1 == 1 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
the presence of non-shift modifiers resets current potential matches
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 1 == 1 keys: set() == set() matching?: True
assert mods: 1 == 1 keys: {'A'} == {'A'} matching?: True
assert mods: 1 == 1 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match found and replaced when there are preceding characters
assert mods: 0 == 0 keys: {'C'} == {'C'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
match found and replaced when there are trailing characters, and the trailing characters are sent
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'C'} == {'C'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
no match
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'1'} == {'1'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
multiple backspaces
assert mods: 0 == 0 keys: {'D'} == {'D'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'D'} == {'D'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'D'} == {'D'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'D'} == {'D'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'BACKSPACE'} == {'BACKSPACE'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'B'} == {'B'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: 0 == 0 keys: {'A'} == {'A'} matching?: True
assert mods: 0 == 0 keys: set() == set() matching?: True
assert mods: None == 0 keys: {None} == {'B'} matching?: False
assert mods: None == 0 keys: {None} == set() matching?: False
F..........
======================================================================
FAIL: test_sequence (tests.test_combos.TestCombo.test_sequence)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/peter/Dev/kmk_firmware/tests/test_combos.py", line 464, in test_sequence
    keyboard.test(
  File "/Users/peter/.asdf/installs/python/3.11.3/lib/python3.11/unittest/mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/peter/Dev/kmk_firmware/tests/keyboard_test.py", line 131, in test
    assert matching, "reports don't match up"
AssertionError: reports don't match up

======================================================================
FAIL: test_holdtap (tests.test_holdtap.TestHoldTap.test_holdtap)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/peter/Dev/kmk_firmware/tests/test_holdtap.py", line 98, in test_holdtap
    keyboard.test(
  File "/Users/peter/.asdf/installs/python/3.11.3/lib/python3.11/unittest/mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/peter/Dev/kmk_firmware/tests/keyboard_test.py", line 131, in test
    assert matching, "reports don't match up"
AssertionError: reports don't match up

======================================================================
FAIL: test_keyboard_events_are_correct (tests.test_string_substitution.TestStringSubstitution.test_keyboard_events_are_correct)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/peter/Dev/kmk_firmware/tests/test_string_substitution.py", line 184, in test_keyboard_events_are_correct
    self.keyboard.test(
  File "/Users/peter/.asdf/installs/python/3.11.3/lib/python3.11/unittest/mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/peter/Dev/kmk_firmware/tests/keyboard_test.py", line 131, in test
    assert matching, "reports don't match up"
AssertionError: reports don't match up

----------------------------------------------------------------------
Ran 73 tests in 4.599s

FAILED (failures=3)
make: *** [unit-tests] Error 1
peterwhitesell commented 1 year ago

the test failures are unrelated to this changeset (as they fail on master as well), but just being diligent here, running make test for each commit

peterwhitesell commented 1 year ago

hey @xs5871, I was planning to start writing documentation for the module. Is there anything major interface-wise that would be good to address before that? I believe the linting is all good at this point

xs5871 commented 1 year ago

Unfortunately, yes, there's quite a bit that can be improved, code quality wise. Since this is a big chunk of a module, it'll take me a while to go through all of it. I'm positive we can work through it together.

peterwhitesell commented 1 year ago

@xs5871 totally understand it'll take a while to review 😃 just making sure I'm not holding up the review on my end. for full transparency I copied this code from bullwinkle3000's fork of kbjunky's original code, and then hacked it a bit myself for my needs, but I think I understand it well enough at this point to address any feedback.

I'll look forward to your feedbacks as you have a chance to review. not sure if you like to do comprehensive or iterative reviews, but I'm happy to address points along the way if that's helpful

peterwhitesell commented 1 year ago

I went ahead and pushed up some initial docs for the module as well

peterwhitesell commented 1 year ago

hey @xs5871 thank you for the review and feedback. It may take me some time to address, but just writing now to acknowledge receipt and thank you for the time

xs5871 commented 1 year ago

It's my fault this took so long. A bunch of my reviews got stuck for some reason and I only noticed yesterday. I actually wrote this over a month ago... apologies.

lucacri commented 3 months ago

Is there any update on this? I would like to test it and debug it. Can I just use this module or is it now too obsolete?

peterwhitesell commented 3 months ago

Hi @lucacri, sorry I haven't attended to this PR for so long. I've changed tactics a bit, and haven't had a chance to new PR for it. What I do now is I use the whimsee/CircuitPython_PMW3360 library, with a much thinner kmk module built on that. If you want to try it out, it's pretty simple and you're more than welcome to open a PR for it if you get to that before I do.

Steps to use it are as follows, assuming you already have kmk setup on your board:

  1. install whimsee/CircuitPython_PMW3360 (either follow instructions in their readme, or just copy pmw3360.py from the root of their repo into <your board>/lib/pmw3360.py
  2. copy kmk/modules/pmw3660.py from my branch into <your board>/kmk/modules/pmw3660.py
  3. configure your board with the module:
    from kmk.modules.pmw3360 import PMW3360
    ...
    keyboard.modules.append(PMW3360(
    sck_pin, mosi_pin, miso_pin, cs_pin, # assuming you have defined these variables
    cpi=800,
    scroll_layers=[2, 4],
    ))