vash3d / pigpio_encoder

Python module for the KY040 rotary encoder.
GNU General Public License v3.0
14 stars 4 forks source link

Slow response time when implementing multiple encoders #17

Open coreyblosser opened 3 years ago

coreyblosser commented 3 years ago

Hi. I'll start with I'm very new to Python so I may be overlooking a simple solution. I'm having difficulty with using threading to implement multiple rotary encoders. I'm using a Raspberry Pi4 and my implementation of threading is based on the multiple encoder example found here: https://github.com/raphaelyancey/pyKY040. I'm assuming since pigpio_encoder is similar, I can use the same method for multiple encoders.

With my current script below, the response time to encoder input is noticeably slow. It appears to not be able to keep up with the rotation of the encoder. If I comment out the lines under #Rotary one or #Rotary two, the response time is better but a bit more performance would be nice. When I use the multiple encoder example from the link above, response time is even faster. I'm assuming that may be due to it being a simpler module.

Would you have any suggestions for how I could optimize this code for faster performance? I prefer pigpio_encoder over pyKY040 because of the additional features such as long press.

Thanks

My Current Script from pigpio_encoder import pigpio_encoder import threading import time import datetime

def rotary_callback(counter): print(datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3] + " - " + "Counter value: ", counter) def sw_short(): print("Switch short press") def sw_long(): print("Switch long press")

def rotary_callback_two(counter): print("Counter2 value: ", counter) def sw_short_two(): print("Switch2 short press") def sw_long_two(): print("Switch2 long press")

Rotary one

my_rotary = pigpio_encoder.Rotary(clk=18, dt=17, sw=19) my_rotary.setup_rotary(min=0, max=100, scale=1, debounce=100, rotary_callback=rotary_callback) my_rotary.setup_switch(debounce=200, long_press=True, sw_short_callback=sw_short, sw_long_callback=sw_long) my_thread = threading.Thread(target=my_rotary.watch) my_thread.start()

Rotary two

my_rotary_two = pigpio_encoder.Rotary(clk=13, dt=12, sw=16) my_rotary_two.setup_rotary(min=0, max=100, scale=1, debounce=100, rotary_callback=rotary_callback_two) my_rotary_two.setup_switch(debounce=200, long_press=True, sw_short_callback=sw_short_two, sw_long_callback=sw_long_two) my_thread_two = threading.Thread(target=my_rotary_two.watch) my_thread_two.start()

Do other stuff

print('Other stuff...') while True: print('LoopedResponse tim stuff...') time.sleep(1000)

This is output for my current script with two encoders enabled. I'm rotating the encoder clockwise with my fingers. Other stuff... Looped stuff... 14:15:46.059 - Counter value: 1 14:15:48.166 - Counter value: 2 14:15:48.210 - Counter value: 3 14:15:48.269 - Counter value: 4 14:15:48.640 - Counter value: 5 14:15:48.782 - Counter value: 6 14:15:49.968 - Counter value: 7 14:15:49.995 - Counter value: 6 14:15:50.035 - Counter value: 7 14:15:50.462 - Counter value: 8 14:15:51.039 - Counter value: 9 14:15:51.569 - Counter value: 10 14:15:51.592 - Counter value: 11 14:15:51.615 - Counter value: 12 14:15:51.637 - Counter value: 13 14:15:51.671 - Counter value: 15 14:15:51.693 - Counter value: 16 14:15:52.189 - Counter value: 17

This is output for my current script with the second encoder commented out. The script detects more movement. Other stuff... Looped stuff... 14:16:52.406 - Counter value: 1 14:16:52.489 - Counter value: 2 14:16:52.611 - Counter value: 3 14:16:53.023 - Counter value: 4 14:16:53.049 - Counter value: 5 14:16:53.145 - Counter value: 6 14:16:53.535 - Counter value: 7 14:16:53.598 - Counter value: 8 14:16:53.668 - Counter value: 9 14:16:54.059 - Counter value: 10 14:16:54.149 - Counter value: 11 14:16:54.229 - Counter value: 12 14:16:54.640 - Counter value: 13 14:16:54.728 - Counter value: 14 14:16:54.755 - Counter value: 15 14:16:55.163 - Counter value: 16 14:16:55.235 - Counter value: 17 14:16:55.574 - Counter value: 18 14:16:55.611 - Counter value: 17 14:16:55.689 - Counter value: 18 14:16:56.038 - Counter value: 19 14:16:56.081 - Counter value: 18 14:16:56.098 - Counter value: 19 14:16:56.115 - Counter value: 20

This is output for pyky040 script I'm using (code below) two encoders enabled. It's very fast. Other stuff... Looped stuff... 14:19:19.438 - Hello world! The scale position is 1 14:19:19.468 - Hello world! The scale position is 2 14:19:19.487 - Hello world! The scale position is 3 14:19:19.503 - Hello world! The scale position is 4 14:19:19.512 - Hello world! The scale position is 5 14:19:19.525 - Hello world! The scale position is 6 14:19:19.531 - Hello world! The scale position is 7 14:19:19.541 - Hello world! The scale position is 8 14:19:19.547 - Hello world! The scale position is 9 14:19:19.557 - Hello world! The scale position is 10 14:19:19.562 - Hello world! The scale position is 11 14:19:19.571 - Hello world! The scale position is 12 14:19:19.576 - Hello world! The scale position is 13 14:19:19.586 - Hello world! The scale position is 14 14:19:19.593 - Hello world! The scale position is 15 14:19:19.603 - Hello world! The scale position is 16 14:19:19.611 - Hello world! The scale position is 17 14:19:19.626 - Hello world! The scale position is 18 14:19:20.003 - Hello world! The scale position is 19 14:19:20.023 - Hello world! The scale position is 20 14:19:20.031 - Hello world! The scale position is 21 14:19:20.043 - Hello world! The scale position is 22 14:19:20.048 - Hello world! The scale position is 23 14:19:20.057 - Hello world! The scale position is 24

My Pyky040 Script

https://pypi.org/project/pyky040/

https://github.com/raphaelyancey/pyKY040

Import the module and threading

from pyky040 import pyky040 import threading import time import datetime

def my_callback(scale_position): print(datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3] + ' - ' + 'Hello world! The scale position is {}'.format(scale_position)) def button(): print('Button pressed')

def my_callback_two(scale_position): print('Hello world! The scale position2 is {}'.format(scale_position)) def button_two(): print('Button2 pressed')

my_encoder = pyky040.Encoder(CLK=17, DT=18, SW=19) my_encoder.setup(scale_min=0, scale_max=100, step=1, chg_callback=my_callback, sw_callback=button) my_thread = threading.Thread(target=my_encoder.watch) my_thread.start()

my_encoder_two = pyky040.Encoder(CLK=12, DT=13, SW=16) my_encoder_two.setup(scale_min=0, scale_max=100, step=1, chg_callback=my_callback_two, sw_callback=button_two) my_thread_two = threading.Thread(target=my_encoder_two.watch) my_thread_two.start()

Do other stuff

print('Other stuff...') while True: print('Looped stuff...') time.sleep(1000)

volkerjaenisch commented 3 years ago

@coreyblosser Thank you for choosing pigpio_encoder! I think the problem is that you are using the watch function. This function is there only for backward compatibility but should NOT be used. The pigpio_ecoder lib is interrupt driven so you need no loop. I have not tried but I am quite sure that you even do not need two threads to operate two encoders in parallel.

So my suggestion is to drop the treads and the watch func and look what happens.

You will just need to have a single final loop for your code to wait, or a thread or so.

Cheers, Volker

volkerjaenisch commented 3 years ago

@coreyblosser I also noticed that you have set the debounce time to 100ms. This gives you a general 100ms delay. Before using debounce at all - try without. If the noise is to strong (e.g. you will see more than one step per step or missing steps) crank up the debounce time in 10ms increments till you reach a stable operation. Different encoders (or encoder models) have quite different characteristic so for each one the debounce has to be adjusted. But at first try without . I have some cheap china encoders that run really fine without debouncing.

Cheers, Volker

coreyblosser commented 3 years ago

@volkerjaenisch Thanks for your guidance. I started a new script, based on the "example using all features" found here: https://pypi.org/project/pigpio-encoder. I removed the debounce and .watch(). If understood correctly, I believe this is the direction you suggested.

The good news is the button presses for both encoders work but there is no output when turning the encoders. If I replace the while loop with my_rotary.watch(), I can see output for button presses and turning the both encoders. Am I missing a detail?

Corey

from pigpio_encoder import pigpio_encoder

def rotary_callback(counter): print("Counter value: ", counter) def sw_short(): print("Switch short press") def sw_long(): print("Switch long press")

def rotary_callback_two(counter): print("Counter value2: ", counter) def sw_short_two(): print("Switch2 short press") def sw_long_two(): print("Switch2 long press")

my_rotary = pigpio_encoder.Rotary(clk=18, dt=17, sw=19) my_rotary.setup_rotary(min=0, max=100, scale=1, rotary_callback=rotary_callback) my_rotary.setup_switch(long_press=True, sw_short_callback=sw_short, sw_long_callback=sw_long)

my_rotary_two = pigpio_encoder.Rotary(clk=13, dt=12, sw=16) my_rotary_two.setup_rotary(min=0, max=100, scale=1, rotary_callback=rotary_callback_two) my_rotary_two.setup_switch(long_press=True, sw_short_callback=sw_short_two, sw_long_callback=sw_long_two)

while(True): pass

volkerjaenisch commented 3 years ago

@coreyblosser The code looks perfect to me. What version of pigpio_encoder are you using? If you comment out the second encoder, does the first one work?

coreyblosser commented 3 years ago

@volkerjaenisch pigpiod -v shows version 79 and I'm using Python 3.7.3

When I comment out the second encoder, the behavior does not change. Short and long button presses work but no output when turning the first encoder. With the second encoder commented out, if I also comment out the while loop and uncomment the my_rotary.watch(), I see output for button presses and turning the encoder.

coreyblosser commented 3 years ago

@volkerjaenisch I notice the distribution version of pigpio_encoder I have is 0.2.2 but I see 0.2.5 on Github. Could that be significant?

volkerjaenisch commented 3 years ago

@coreyblosser

I notice the distribution version of pigpio_encoder I have is 0.2.2 but I see 0.2.5 on Github. Could that be significant?

Therefore my question for the version. Please use the version from GH. I will deploy the 0.2.5 version as Python package on PyPI in the next hours

With the second encoder commented out, if I also comment out the while loop and uncomment the my_rotary.watch(), I see output for button presses and turning the encoder.

This is really strange. If you please have a look at .watch() :

@staticmethod def watch(): """ A simple convenience function to have a waiting loop """ while True: time.sleep(10)

It is no more than a loop. But the problem may be that your loop does eat up all CPU since it has no wait in it.

Can you please give your loop a wait and test again. Mainwhile I do the deployment to PyPI.

Cheers, Volker

volkerjaenisch commented 3 years ago

Current Version 0.2.4 is now available on PyPI.

coreyblosser commented 3 years ago

@volkerjaenisch I was able to install the 0.2.4 version. All works with both encoders. This is great! Thanks for all of your help!