# OrangePI-KY040
**High-level Python module for the KY040 rotary encoder and switch** on Raspberry Pi and similar boards that use `OPi.GPIO`
<img src="https://i.imgur.com/vgHjSoY.jpg" width="300" alt="KY-040 rotary encoder and switch">
## Features
- Increment callback
- Decrement callback
- Change callback (increment or decrement)
- Switch press callback
### Options
- Scale mode (internal counter is bound between X and Y, and is given as argument in the callback functions)
- Looped scale mode (from X to Y, then X again)
- Custom scale step
- GPIO polling (easier) or [as a device](#device-or-gpio-polling) (sturdier)
## Installation
```bash
pip install pyky040
```
<!--
## Usage
[![asciicast](https://asciinema.org/a/GVUyrqUUnZP4Sne8eEmKTWHCt.svg)](https://asciinema.org/a/GVUyrqUUnZP4Sne8eEmKTWHCt)
-->
### Basic
```python
# Import the module
from OrangePi_ky040 import pyky040
# Define your callback
def my_callback(scale_position):
print('Hello world! The scale position is {}'.format(scale_position))
# Init the encoder pins
my_encoder = pyky040.Encoder(CLK=17, DT=18, SW=26)
# Or the encoder as a device (must be installed on the system beforehand!)
# my_encoder = pyky040.Encoder(device='/dev/input/event0')
# Setup the options and callbacks (see documentation)
my_encoder.setup(scale_min=0, scale_max=100, step=1, chg_callback=my_callback)
# Launch the listener
my_encoder.watch()
# Mess with the encoder...
# > Hello world! The scale position is 1
# > Hello world! The scale position is 2
# > Hello world! The scale position is 3
# > Hello world! The scale position is 2
# > Hello world! The scale position is 1
```
### In a thread
As the `watch()` method runs an infinite polling loop, you might want to run it in a thread if you don't want to block the rest of your script, or if you have **multiple encoders** to handle.
```python
# Import the module and threading
from OrangePi_ky040 import pyky040
import threading
# Define your callback
def my_callback(scale_position):
print('Hello world! The scale position is {}'.format(scale_position))
# Init the encoder pins
my_encoder = pyky040.Encoder(CLK=17, DT=18, SW=26)
# Or the encoder as a device (must be installed on the system beforehand!)
# my_encoder = pyky040.Encoder(device='/dev/input/event0')
# Setup the options and callbacks (see documentation)
my_encoder.setup(scale_min=0, scale_max=100, step=1, chg_callback=my_callback)
# Create the thread
my_thread = threading.Thread(target=my_encoder.watch)
# Launch the thread
my_thread.start()
# Do other stuff
print('Other stuff...')
while True:
print('Looped stuff...')
sleep(1000)
# ... this is also where you can setup other encoders!
# Mess with the encoder...
# > Other stuff...
# > Looped stuff...
# > Hello world! The scale position is 1
# > Hello world! The scale position is 2
# > Hello world! The scale position is 3
# > Looped stuff...
# > Hello world! The scale position is 2
```
**Note:** The interruption of the module when running in threads is not yet handled, you might have to kill it by yourself 🔪
## Documentation
#### `Encoder(CLK=x, DT=y, SW=z)`
Initializes the module with the specified encoder pins.
- Options
- `polling_interval` Specify the pins polling interval in ms (default 1ms)
#### `Encoder(device='...')`
⚠️ Linux only
Initializes the module with the specified encoder device. [Read more](#device-or-gpio-polling)
Requirement: `pip install pyky040[device]`
#### `Encoder.setup()`
Setup the behavior of the module. All of the following keyword arguments are optional.
- Callbacks
- `inc_callback (function)` When the encoder is incremented (clockwise). Scale position as first argument.
- `dec_callback (function)` When the encoder is decremented. Scale position as first argument.
- `chg_callback (function)` When the encoder is either incremented or decremented. Scale position as first argument.
- `sw_callback (function)` When the encoder switch is pressed
- Scale mode
- `scale_min (int/float)` Scale minimum
- `scale_max (int/float)` Scale maximum
- `loop (boolean)` Loop mode (defaults to `False`)
- `step (int/float)` Scale step when incrementing or decrementing
- Options
- `sw_debounce_time (int/float)` Switch debounce time in ms (allow only one interrupt per X ms, dismiss others)
**Note:** better keep using ints and not floats for more precise results.
#### `Encoder.watch()`
Starts the listener. The pins polling interval is `1ms` by default and can be customized (see `Encoder()`).
## <a name="device-or-gpio-polling"></a>Should I use the GPIO polling or the device overlay?
The Raspberry Pi firmware allows the encoder to be set up as a device with the [`rotary-encoder` overlay](https://github.com/raspberrypi/firmware/blob/master/boot/overlays/README#L1892-L1921). It trades *the promise to catch every encoder tick* for *the ease of use* (because it needs to be installed on the host beforeheand, with root privileges).
|Approach|Plug & Play|Needs prior installation|Catches every tick|
|--------|-----------|------------------------|---------------------|
|GPIO polling|**Yes**|No|No|
|Device overlay|No|Yes|**Yes**|
### <a name="install-device"></a>How to install the encoder as a device?
Only tested on Raspbian Buster at this time.
```
# Copy this line in `/boot/config.txt` and reboot
# (replacing {CLK_PIN} and {DT_PIN} by their real values)
dtoverlay=rotary-encoder,pin_a={CLK_PIN},pin_b={DT_PIN},relative_axis=1,steps-per-period=2
```
## TROUBLESHOOTING
### Erratic behavior
It is known that some pins combinations introduce erratic behavior (interferences?). The library has been tested successfully using the following combinations (BCM numbering).
|CLK| DT| SW| Pi|Raspbian|
|---|---|---|---------|--------|
| 26| 4| 21| 3B (1.2)|Buster |
Feel free to edit the README to provide your working combinations!
If you are still experiencing issues, you might want to try to [set up the encoder as a device](#device-or-gpio-polling) instead.
## CHANGELOG
**0.1.4**
- Added `device` mode
- Added tests
**0.1.3**
- Fixed `latest_switch_call` not defined before the loop
**0.1.2**
- Changed `__init_` args to kwargs for better readability and ease of use `Encoder(CLK=x, DT=y, SW=z)`
- Added customizable debounce time (in ms) for the switch `setup(..., sw_debounce_time=300)`
- Added customizable polling interval (in ms) `Encoder(..., polling_interval=1)`
Raw data
{
"_id": null,
"home_page": "https://github.com/sonocotta/orangepi-ky040",
"name": "OrangePi.ky040",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "keyes rotary encoder switch ky040",
"author": "Andriy Malyshenko",
"author_email": "anriy@sonocotta.com",
"download_url": "https://files.pythonhosted.org/packages/75/50/b19e45ef9c6786285fc641d6ee1c384c95faa6e2a8aab7a3eb5758846a27/OrangePi.ky040-0.2.0.tar.gz",
"platform": null,
"description": "# OrangePI-KY040\n\n**High-level Python module for the KY040 rotary encoder and switch** on Raspberry Pi and similar boards that use `OPi.GPIO`\n\n<img src=\"https://i.imgur.com/vgHjSoY.jpg\" width=\"300\" alt=\"KY-040 rotary encoder and switch\">\n\n## Features\n\n- Increment callback\n- Decrement callback\n- Change callback (increment or decrement)\n- Switch press callback\n\n### Options\n\n- Scale mode (internal counter is bound between X and Y, and is given as argument in the callback functions)\n- Looped scale mode (from X to Y, then X again)\n- Custom scale step\n- GPIO polling (easier) or [as a device](#device-or-gpio-polling) (sturdier)\n\n## Installation\n\n```bash\npip install pyky040\n```\n\n<!--\n## Usage\n\n[![asciicast](https://asciinema.org/a/GVUyrqUUnZP4Sne8eEmKTWHCt.svg)](https://asciinema.org/a/GVUyrqUUnZP4Sne8eEmKTWHCt)\n-->\n\n### Basic\n\n```python\n# Import the module\nfrom OrangePi_ky040 import pyky040\n\n# Define your callback\ndef my_callback(scale_position):\n print('Hello world! The scale position is {}'.format(scale_position))\n\n# Init the encoder pins\nmy_encoder = pyky040.Encoder(CLK=17, DT=18, SW=26)\n\n# Or the encoder as a device (must be installed on the system beforehand!)\n# my_encoder = pyky040.Encoder(device='/dev/input/event0')\n\n# Setup the options and callbacks (see documentation)\nmy_encoder.setup(scale_min=0, scale_max=100, step=1, chg_callback=my_callback)\n\n# Launch the listener\nmy_encoder.watch()\n\n# Mess with the encoder...\n# > Hello world! The scale position is 1\n# > Hello world! The scale position is 2\n# > Hello world! The scale position is 3\n# > Hello world! The scale position is 2\n# > Hello world! The scale position is 1\n```\n\n### In a thread\n\nAs the `watch()` method runs an infinite polling loop, you might want to run it in a thread if you don't want to block the rest of your script, or if you have **multiple encoders** to handle.\n\n```python\n# Import the module and threading\nfrom OrangePi_ky040 import pyky040\nimport threading\n\n# Define your callback\ndef my_callback(scale_position):\n print('Hello world! The scale position is {}'.format(scale_position))\n\n# Init the encoder pins\nmy_encoder = pyky040.Encoder(CLK=17, DT=18, SW=26)\n\n# Or the encoder as a device (must be installed on the system beforehand!)\n# my_encoder = pyky040.Encoder(device='/dev/input/event0')\n\n# Setup the options and callbacks (see documentation)\nmy_encoder.setup(scale_min=0, scale_max=100, step=1, chg_callback=my_callback)\n\n# Create the thread\nmy_thread = threading.Thread(target=my_encoder.watch)\n\n# Launch the thread\nmy_thread.start()\n\n# Do other stuff\nprint('Other stuff...')\nwhile True:\n print('Looped stuff...')\n sleep(1000)\n# ... this is also where you can setup other encoders!\n\n# Mess with the encoder...\n# > Other stuff...\n# > Looped stuff...\n# > Hello world! The scale position is 1\n# > Hello world! The scale position is 2\n# > Hello world! The scale position is 3\n# > Looped stuff...\n# > Hello world! The scale position is 2\n\n```\n\n**Note:** The interruption of the module when running in threads is not yet handled, you might have to kill it by yourself \ud83d\udd2a\n\n## Documentation\n\n#### `Encoder(CLK=x, DT=y, SW=z)`\n\nInitializes the module with the specified encoder pins.\n\n- Options\n - `polling_interval` Specify the pins polling interval in ms (default 1ms)\n\n#### `Encoder(device='...')`\n\n\u26a0\ufe0f Linux only\n\nInitializes the module with the specified encoder device. [Read more](#device-or-gpio-polling)\n\nRequirement: `pip install pyky040[device]`\n\n#### `Encoder.setup()`\n\nSetup the behavior of the module. All of the following keyword arguments are optional.\n\n- Callbacks\n - `inc_callback (function)` When the encoder is incremented (clockwise). Scale position as first argument.\n - `dec_callback (function)` When the encoder is decremented. Scale position as first argument.\n - `chg_callback (function)` When the encoder is either incremented or decremented. Scale position as first argument.\n - `sw_callback (function)` When the encoder switch is pressed\n\n- Scale mode\n - `scale_min (int/float)` Scale minimum\n - `scale_max (int/float)` Scale maximum\n - `loop (boolean)` Loop mode (defaults to `False`)\n - `step (int/float)` Scale step when incrementing or decrementing\n\n- Options\n - `sw_debounce_time (int/float)` Switch debounce time in ms (allow only one interrupt per X ms, dismiss others)\n\n**Note:** better keep using ints and not floats for more precise results.\n\n#### `Encoder.watch()`\n\nStarts the listener. The pins polling interval is `1ms` by default and can be customized (see `Encoder()`).\n\n## <a name=\"device-or-gpio-polling\"></a>Should I use the GPIO polling or the device overlay?\n\nThe Raspberry Pi firmware allows the encoder to be set up as a device with the [`rotary-encoder` overlay](https://github.com/raspberrypi/firmware/blob/master/boot/overlays/README#L1892-L1921). It trades *the promise to catch every encoder tick* for *the ease of use* (because it needs to be installed on the host beforeheand, with root privileges).\n\n|Approach|Plug & Play|Needs prior installation|Catches every tick|\n|--------|-----------|------------------------|---------------------|\n|GPIO polling|**Yes**|No|No|\n|Device overlay|No|Yes|**Yes**|\n\n### <a name=\"install-device\"></a>How to install the encoder as a device?\n\nOnly tested on Raspbian Buster at this time.\n\n```\n# Copy this line in `/boot/config.txt` and reboot\n# (replacing {CLK_PIN} and {DT_PIN} by their real values)\ndtoverlay=rotary-encoder,pin_a={CLK_PIN},pin_b={DT_PIN},relative_axis=1,steps-per-period=2\n```\n\n## TROUBLESHOOTING\n\n### Erratic behavior\n\nIt is known that some pins combinations introduce erratic behavior (interferences?). The library has been tested successfully using the following combinations (BCM numbering).\n\n|CLK| DT| SW| Pi|Raspbian|\n|---|---|---|---------|--------|\n| 26| 4| 21| 3B (1.2)|Buster |\n\nFeel free to edit the README to provide your working combinations!\n\nIf you are still experiencing issues, you might want to try to [set up the encoder as a device](#device-or-gpio-polling) instead.\n\n## CHANGELOG\n\n**0.1.4**\n\n - Added `device` mode\n - Added tests\n \n**0.1.3**\n\n - Fixed `latest_switch_call` not defined before the loop\n\n**0.1.2**\n\n - Changed `__init_` args to kwargs for better readability and ease of use `Encoder(CLK=x, DT=y, SW=z)`\n - Added customizable debounce time (in ms) for the switch `setup(..., sw_debounce_time=300)`\n - Added customizable polling interval (in ms) `Encoder(..., polling_interval=1)`\n",
"bugtrack_url": null,
"license": "",
"summary": "High-level interface for the KY040 rotary encoder and switch.",
"version": "0.2.0",
"project_urls": {
"Bug Reports": "https://github.com/sonocotta/orangepi-ky040/issues",
"Homepage": "https://github.com/sonocotta/orangepi-ky040",
"Source": "https://github.com/sonocotta/orangepi-ky040"
},
"split_keywords": [
"keyes",
"rotary",
"encoder",
"switch",
"ky040"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "7550b19e45ef9c6786285fc641d6ee1c384c95faa6e2a8aab7a3eb5758846a27",
"md5": "83b7e1c06f76211f70c00c253b7721fd",
"sha256": "54b1fe29f29a17d0e70b8d2afcb9cb06e0e5fc351650cf53db2e640a11053c5d"
},
"downloads": -1,
"filename": "OrangePi.ky040-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "83b7e1c06f76211f70c00c253b7721fd",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 21264,
"upload_time": "2023-05-27T21:08:43",
"upload_time_iso_8601": "2023-05-27T21:08:43.818355Z",
"url": "https://files.pythonhosted.org/packages/75/50/b19e45ef9c6786285fc641d6ee1c384c95faa6e2a8aab7a3eb5758846a27/OrangePi.ky040-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-05-27 21:08:43",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "sonocotta",
"github_project": "orangepi-ky040",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "orangepi.ky040"
}