-
Notifications
You must be signed in to change notification settings - Fork 34
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
Implement nonblocking decoder #42
Conversation
The CI failures from the linted seem related to existing code, not the changes in this PR. Will close and reopen to see if subsequent changes to the main branch resolve this. |
Sorry for not replying earlier! Please merge in the latest changes from the repo into this branch and then I'll take a look. |
@danielballan Hello, could you take a look to the following guide in order to the CI to pass the verification? |
The linter’s complaints are related to pre-existing code that I hesitate to change too deeply for fear of breaking something unknowingly. Let me see how light a touch I can take.... |
Rebased and then updated to satisfy Needs maintainer approval to run CI. I haven't yet re-tested this with hardware after the rebase and addition of |
Fixed one more complaint from pylint that for whatever reason didn't show up when I ran it locally. |
@tannewt this will be ready for your review as requested. Thanks |
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.
One super minor thing. Overall it looks really good. Thank you for the changes. It'll be really nice to have a real non-blocking API.
adafruit_irremote.py
Outdated
"Wraps the top-level function bin_data for backward-compatibility." | ||
return bin_data(pulses) | ||
|
||
def decode_bits(self, pulses): # pylint: disable=R0201 |
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.
Please use the pylint name instead of code so it's more readable.
Thanks for the review. Comment addressed. |
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.
Looks good to me! Thank you!
Updating https://github.com/adafruit/Adafruit_CircuitPython_BME280 to 2.6.5 from 2.6.4: > Moved default branch to main > Merge pull request adafruit/Adafruit_CircuitPython_BME280#52 from jposada202020/memory_otimization > Moved CI to Python 3.7 Updating https://github.com/adafruit/Adafruit_CircuitPython_HTU31D to 1.1.1 from 1.1.0: > Merge pull request adafruit/Adafruit_CircuitPython_HTU31D#6 from gmparis/main > Moved CI to Python 3.7 > Added help text and problem matcher > Added pull request template Updating https://github.com/adafruit/Adafruit_CircuitPython_ICM20X to 2.0.8 from 2.0.7: > Linted > Moved default branch to main > Moved CI to Python 3.7 > Added help text and problem matcher > Added pull request template Updating https://github.com/adafruit/Adafruit_CircuitPython_IRRemote to 4.1.0 from 4.0.6: > Moved default branch to main > Merge pull request adafruit/Adafruit_CircuitPython_IRRemote#42 from danielballan/nonblocking > Moved CI to Python 3.7 > Added help text and problem matcher > Added pull request template Updating https://github.com/adafruit/Adafruit_CircuitPython_L3GD20 to 2.3.6 from 2.3.5: > Merge pull request adafruit/Adafruit_CircuitPython_L3GD20#23 from jposada202020/correcting_measuremnt_units > Moved default branch to main > Moved CI to Python 3.7 > Added help text and problem matcher > Added pull request template Updating https://github.com/adafruit/Adafruit_CircuitPython_SGP40 to 1.1.0 from 1.0.3: > Merge pull request adafruit/Adafruit_CircuitPython_SGP40#4 from CrakeNotSnowman/voc_algorithm_dev > Moved CI to Python 3.7 > Added help text and problem matcher > Added pull request template Updating https://github.com/adafruit/Adafruit_CircuitPython_SHTC3 to 1.1.2 from 1.1.1: > Moved default branch to main > Merge pull request adafruit/Adafruit_CircuitPython_SHTC3#11 from jposada202020/master > Moved CI to Python 3.7 > Added help text and problem matcher > Added pull request template Updating https://github.com/adafruit/Adafruit_CircuitPython_SI4713 to 1.3.2 from 1.3.1: > Moved default branch to main > Merge pull request adafruit/Adafruit_CircuitPython_SI4713#20 from jposada202020/solving_station_information Updating https://github.com/adafruit/Adafruit_CircuitPython_SSD1306 to 2.11.6 from 2.11.5: > Corrected url > Merge pull request adafruit/Adafruit_CircuitPython_SSD1306#64 from jposada202020/correcting_example_link Updating https://github.com/adafruit/Adafruit_CircuitPython_Colorsys to 2.0.1 from 2.0.0: > Moved default branch to main > Merge pull request adafruit/Adafruit_CircuitPython_Colorsys#17 from GomiHgy/patch-1 > Moved CI to Python 3.7 > Added help text and problem matcher > Added pull request template Updating https://github.com/adafruit/Adafruit_CircuitPython_FunHouse to 2.1.4 from 2.1.3: > Merge pull request adafruit/Adafruit_CircuitPython_FunHouse#17 from jposada202020/adding_new_guides
Thanks for maintaining this excellent ecosystem of software and hardware. This is my first attempt to contribute to an Adafruit repository.
The Problem
As described in #32, the non-blocking code path in
GenericDecoder
is not really non-blocking. Several people have reproduced the issue: if you hold down a button on a remote control to produce a steady stream of pulses,GenericDecoder.read_pulses(..., blocking=False)
blocks indefinitely until the pulse stream stops.There is also a more subtle issue in the design. When the application is juggling multiple tasks, it's possible for more than one message to accumulate in
PulseIn
before we get around to reading it. But (if I understand correctly) the currentGenericDecoder
API has no way to return more than one code when it is read.Background
I am new to IR, but I wrote
caproto
, a beginner-friendly library that solves similar problems for EPICS, an industrial and scientific control protocol used by US Department of Enengy facilities and others. In this PR, I have tried to apply what I learned from that work about non-blocking message-parsing code, while taking as a light a touch as I can and carefully maintaining backward-compatibility with existing APIs.Proposed Solution
I cannot see how to fix the
GenericDecoder
in a backward-compatible way, so I have created a new classNonblockingGenericDecoder
. This is a usage example:Notes on the design:
decoder.read()
is a generator: it returns an iterator that you can loop over to get messages. This allows for the possibility that multiple messages accumulate between reads. When there are no messages,decoder.read()
is simply an empty iterator.PulseIn
but they happen to be part-way through a message when weread()
, those pulses are stashed internally byNonblockingGenericDecoder
and held untilread()
is called again and the message is completed.PulseIn
wouldn't necessary have to be an argument passed to__init__
. It could be passed toread()
, more akin to how it's done inGenericDecoder
. But since we need a class anyway to store the partial message state, it seems convenient to passPulseIn
in the__init__
so we only have to do it once. Also, since the decoder consumes messages fromPulseIn
, mutating it bypopleft()
-ing contents off of it, it "owns" it in a sense. Thus, having it belong toNonblockingGenericDecoder
feels cleaner to me than passing it in toread(pulses)
as a method argument.bin_data
anddecode_bits
from methods to functions so that they can be shared betweenGenericDecoder
andNonblockingGenericDecoder
. I noticed the comment that these are methods because "we may add object level config later". I suggest that config could be passed in as a parameter without making these into methods. They would only need to be methods if they need to modify instance state. I don't think that should come up, so I think it is safe to make them simple functions.classesnamedtuple
types (IRMessage
and friends) to do the job thatGenericDecoder
does with exceptions. That is, thetry
/except
control flow is replaced byif isinstance(...)
control flow. There are couple reasons for this.IRMessage
object bundling together thecode
and thepulses
in case we want to do something with the pulses. (In caproto, we get a lot of use out of similar objects which contain both human-friendly properties and the raw bytes received.)Evidence that it works smoothly
Performing the same test that I performed in #32 with this new API, messages are parsed quickly and the application loop is given frequent opportunity to do other work, as in indicated by the "Heartbeat" messages.
Open questions
GenericDecoder
. Is that necessary becauseGenericDecoder
can only return onecode
per read, and thus has to dump any pulses past the first code? Is it safe to assume that no pruning is needed in this new decoder that can yield multiple codes?read_pulses(..., blocking=False)
? Should it raise an error pointing people toGenericNonblockingDecoder
? Or issue a warning?