Skip to content
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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions docs/en/potentiometer.md
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)
```
Comment on lines +54 to +76
Copy link
Collaborator

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, but add_key and remove_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-definable on_move method:

class PotAB(Potentiometer):
    def on_move(self, keyboard, module, position, direction):
        if ...:
            keyboard.resume_process_key(module, key=KC.A, is_pressed=True)
        elif ...:
            keyboard.resume_process_key(module, key=KC.B, is_pressed=False)
        # ... etc.

potentiometer = PotentiometerHandler(
    potentiometer = (PotAB(board.A1, inverted=False),)
)

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.

Copy link
Author

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.

Copy link
Collaborator

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.


## 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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's so much wrong with this:

  • The hid_report should never ever be interfaced with user code.
  • And that's not even a good way to use it. You're throwing away any keys that are currently pressed.
  • This does not take into account that there's no feedback about the actual volume.
    This is garantueed to generate unecessary bug reports and user frustration.

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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)
```