-
Notifications
You must be signed in to change notification settings - Fork 489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add documentation for the Potentiometer module included with KMK #638
base: master
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# Potentiometer module | ||
Do you want to add joysticks or faders to your keyboard? | ||
|
||
The potentiometer module reads analog signals from you ADC pins and gives you a value in 7 bit base. | ||
|
||
## Enabling the extension | ||
Enabling Potentiometer gives you access to the following keycodes and can simply be | ||
added to the modules list. | ||
|
||
```python | ||
from kmk.modules.potentiometer import PotentiometerHandler | ||
potentiometer = PotentiometerHandler() | ||
|
||
keyboard.modules.append(potentiometer) | ||
``` | ||
|
||
|
||
## How to use | ||
Here is all you need to use this module in your `main.py` / `code.py` file. | ||
|
||
**1. Load the module: import the potentiometer handler and add it to keyboard modules.** | ||
|
||
```python | ||
from kmk.modules.potentiometer import PotentiometerHandler | ||
potentiometer = PotentiometerHandler() | ||
keyboard.modules.append(potentiometer) | ||
``` | ||
|
||
**2. Define the pins for each potentiometer: `pin_a` for the signal pin and `defname` for the name of the controlling defenition. If you want to invert the direction of the potentiometer, set the 3rd (optional) parameter `is_inverted` to `True`.** | ||
|
||
```python | ||
potentiometer.pins = ( | ||
(board."pin_a", "defname", "is_inverted") | ||
) | ||
``` | ||
|
||
Example: | ||
```python | ||
# Regular GPIO potentiometer | ||
potentiometer.pins = ( | ||
# regular direction potentiometer | ||
(board.A0, potentiometer_1_handler), | ||
# reversed direction potentiometer | ||
(board.A1, potentiometer_2_handler, False), | ||
) | ||
``` | ||
|
||
**3. Define the mapping of keys to be called.** | ||
|
||
here we convert the incoming base values into a value 0-127 for ease of use. | ||
|
||
This example is for a joystick mapped to WASD with a deadzone in the center. The exact deadzone values might vary depending on the potentiometers used. | ||
|
||
*Note: this uses `keyboard.add_key` and `keyboard.remove_key` which could be considered legacy.* | ||
|
||
```python | ||
def potentiometer_1_handler(state): | ||
joy1 = int((state.position / 127) * 127) | ||
if joy1 >= 0 and joy1 <= 56: | ||
keyboard.add_key(KC.S) | ||
if joy1 >= 65 and joy1 <= 127: | ||
keyboard.add_key(KC.W) | ||
if joy1 >= 57 and joy1 <= 64: | ||
keyboard.remove_key(KC.S) | ||
keyboard.remove_key(KC.W) | ||
|
||
def potentiometer_2_handler(state): | ||
joy2 = int((state.position / 127) * 127) | ||
if joy2 >= 0 and joy2 <= 58: | ||
keyboard.add_key(KC.A) | ||
if joy2 >= 67 and joy2 <= 127: | ||
keyboard.add_key(KC.D) | ||
if joy2 >= 59 and joy2 <= 66: | ||
keyboard.remove_key(KC.A) | ||
keyboard.remove_key(KC.D) | ||
``` | ||
|
||
## Other examples | ||
|
||
**Computer volume** | ||
|
||
You can use a potentiometer to control the system volume easily. Here an example from [ZFR_KBD's RP2.65-F](https://github.com/KMKfw/kmk_firmware/blob/master/user_keymaps/ZFR_KBD/RP2.65-F.py) | ||
|
||
```python | ||
def set_sys_vol(state): | ||
# convert to 0-100 | ||
new_pos = int((state.position / 127) * 64) | ||
level = level_lut[new_pos] | ||
# print(f"new vol level: {level}") | ||
# print(f"last: {keyboard.last_level}") | ||
|
||
# check if uninitialized | ||
if keyboard.last_level == -1: | ||
keyboard.last_level = level | ||
return | ||
|
||
level_diff = abs(keyboard.last_level - level) | ||
if level_diff > 0: | ||
# set volume to new level | ||
# vol_direction = "unknown" | ||
if level > keyboard.last_level: | ||
# vol_direction = "up" | ||
cmd = KC.VOLU | ||
else: | ||
# vol_direction = "down" | ||
cmd = KC.VOLD | ||
|
||
# print(f"Setting system volume {vol_direction} by {level_diff} to reach {level}") | ||
for i in range(int(level_diff / level_inc_step)): | ||
hid_report = keyboard._hid_helper.create_report([cmd]) | ||
hid_report.send() | ||
hid_report.clear_all() | ||
hid_report.send() | ||
Comment on lines
+109
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sorry, but that is not going to be an example in the official documentation.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i am not exactly sure why the hid_report was in there as this is a copied example from ZFR_KBD's RP2.65-F keymap that is included with the KMK repo. I wil have to look into how circuitpython can interface with the os if not by utilizing the hid_report. I am not that well versed in exactly this field so if you or someone else has a better example for this that would be great. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should put any kind of jank volume control examples into the "official" documentation, purely because of the third bullet point. The same "level" of volume on your keyboard will always produce different levels of volume on your computer. Terrible user experience. |
||
|
||
keyboard.last_level = level | ||
return | ||
|
||
def potentiometer_1_handler(state): | ||
set_sys_vol(state) | ||
``` | ||
|
||
**LED brightness** | ||
|
||
You can also use a potentiometer to control the LED brightness of your keyboard. Another example from [ZFR_KBD's RP2.65-F](https://github.com/KMKfw/kmk_firmware/blob/master/user_keymaps/ZFR_KBD/RP2.65-F.py) | ||
|
||
```python | ||
def get_kb_rgb_obj(keyboard): | ||
rgb = None | ||
for ext in keyboard.extensions: | ||
if type(ext) is RGB: | ||
rgb = ext | ||
break | ||
return rgb | ||
|
||
def set_led_brightness(state): | ||
rgb = get_kb_rgb_obj(keyboard) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The recommended pattern is to first assign the RGB module to a global reference, then use that reference, not look it up by type. |
||
if rgb is None: | ||
return | ||
|
||
rgb.val = int((state.position / 127) * rgb.val_limit) | ||
rgb._do_update() | ||
return | ||
|
||
def potentiometer_3_handler(state): | ||
set_led_brightness(state) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't advise to do any of that.
For one,
keyboard
may not be defined where a user defines these handlers. Second, the note to deprecation is nice, butadd_key
andremove_key
don't handle all keys correctly and should really be avoided in any code going forward.Suggestion: we get rid of the awkward (pin, callback, *)-tuple interface and replace it with
Potentiometer
instances proper, and add a user-definableon_move
method:Also, your example would trigger for every tiny movement, again and again, regardless if it passes thresholds between "key presses/releases". That might not be noticable with the example keys, but that is not true for every key and likely not the intended behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am going to rewrite this part and do some testing with my joystick board, but i believe that this should be pretty easy to accomplish. The initial example is just whatever i could come up with on the fly that would work for my use case, but as the documentation was a bit lacking and my understanding of python and circuitpython is still developing i just offered up whatever i figured out that would work.
i am very happy to learn the proper ways to do things and will work on a revision with better examples as my knowledge expands.
I hope it is not bothering you too much to help with these things as i originally started learning python specifically to develop for circuitpython, and there is some quirks, so having someone knowledgable to help my understanding is great.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's alright. I'd say the documentation should only contain information about the usage and one or two very basic examples. It's not the right place to have a wall of code.