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

while True: led.red = pot.value #76

Closed
bennuttall opened this issue Oct 17, 2015 · 14 comments
Closed

while True: led.red = pot.value #76

bennuttall opened this issue Oct 17, 2015 · 14 comments
Assignees
Milestone

Comments

@bennuttall
Copy link
Member

Would it be possible to automatically pass the value of a potentiometer directly into something like an RGBLED?

So that the following example:

led = RGBLED(2, 3, 4)
pot = MCP3008()

while True:
    led.red = pot.value

can be done automatically somehow?

i.e. When the pot value changes, the LED changes accordingly.

@waveform80
Copy link
Member

Hmmm ... it's certainly possible to come up with something, but it'll never be as simple as led.red = pot.something because led.red is expecting a single scalar value. That said ... I've just had a cunning idea involving iterators.

How about having a values property on the pot that acts as an infinite iterator (this could actually be a generic thing on several classes); as you iterate over it, it yields values from the pot. Then have a property on RGBLED that's something like ... red_source, or maybe red_values, or ... well, I haven't got any really good ideas for that name yet, but anyway. When set to something other than None the property fires up a background thread which periodically reads values off the iterator it's been given and assigns them to red (or whatever).

The nice thing about that is firstly that values is so generic. We could apply that concept to just about any class so you could have an LED's value sourced from a LightSensor, or a thermometer or anything else. The other nice thing is that iterators are trivial to make in Python so you could simply assign a list, or generator expression, or a generator function to do all sorts of things.

Huh ... just realized my idea for pulse in the other thread can be implemented as an iterator. Oh, and how this should be rate-limited ... Okay, let me hack on this for a bit.

@waveform80 waveform80 self-assigned this Oct 17, 2015
@bennuttall
Copy link
Member Author

Ooh nice idea!

@bennuttall
Copy link
Member Author

I have to say, values 0-1 for PWM and RGBLED make it so much easier to deal with. Made my examples so much simpler!

@waveform80
Copy link
Member

Have you tried it with picamera's Color class yet? It's rather nice :)

@bennuttall
Copy link
Member Author

Not yet - like the examples though. I'm surprised you had to implement that in picamera - surely it's generic enough to exist elsewhere!

@waveform80
Copy link
Member

Well, there's a built in colorsys in Python's standard library but frankly it's not very good (just a set of functions for converting between systems; no methods for comparing or modifying colors, and even the set of systems is extremely limited). I had a look at a few systems available on PyPI - most were largely unimpressive. I seem to recall there was one reasonably good one but it still lacked a good means of color manipulation and furthermore none of the PyPI ones seemed to be packaged on Raspbian/Debian which was a bit of a deal breaker.

@waveform80
Copy link
Member

Fanboi moment: OMG this is seriously cool. Right, now that's out of my system...

Current status (bear with me here): I've shifted value down to GPIODevice. At that level it's effectively an alias for is_active in as much as it returns False when the device is off, True when it's on. Descendent classes sometimes override this with more refined values. For example, LightSensor returns a value between 0.0 (dark) and 1.0 (fully lit, although in practice it'll never actually get to 1.0), and PWMOutputDevice returns a value between 0.0 and 1.0 representing the current duty cycle. It's worth keeping this separate from is_active because that always returns a bool (False/True) even in these more refined classes (typically in these, is_active just compares value to a threshold like 0.0).

The base class GPIODevice now also implements the values property. This simply returns an infinite iterator that reads from value. Even on its own this is quite useful as you can do things like:

>>> sensor = LightSensor(18)
>>> for v in sensor.values:
...     print v

And the console will spit out the values read from the sensor in real-time - nice for hardware debugging. However, here's the clever bit. OutputDevice now also implements a source property. When set to any iterable (could be a list, could be a generator function, whatever), this fires up a background thread that continually reads from that iterable and assigns to value (which is made a writeable property in this class which in turn alters the state of the object).

Still doesn't sound like much? First let's fade in a PWMLED:

>>> blue = PWMLED(16)
>>> blue.source = [i / 10000.0 for i in range(10000)]

Not impressed? Let's link the state of another LED to the state of this one, then do it again:

>>> red = PWMLED(12)
>>> red.source = blue.values
>>> blue.source = [i / 10000.0 for i in range(10000)]

Remember that all devices have values, even output ones, which enables this sort of chaining. Can we combine this with event handlers to control two LEDs from a single button without writing any functions at all? Yup:

>>> btn = Button(26)
>>> btn.when_pressed = blue.on
>>> btn.when_released = blue.off

But hang on, we don't even need to set hooks to do this. We could do it with even fewer lines of code just by chaining the state of the button to the blue LED:

>>> btn.when_pressed = None
>>> btn.when_released = None
>>> blue.source = btn.values

Still works nicely! Now, we're using PWMLEDs so how about something more variable than a button. How about a light sensor?

>>> sensor = LightSensor(18)
>>> blue.source = sensor.values

Now cover and uncover the light sensor: you should see both LEDs fade and brighten in response. You may note during all this that the CPU is pegged at 100%. This isn't a bug - it's entirely deliberate. It's because you've got background threads constantly reading and applying values. But with some trivial filters we can make things a lot more relaxed (and for that matter, intelligent). Firstly, let's make a filter that waits 0.1 seconds between reads from an iterable:

>>> from time import sleep
>>> def read_slowly(iterable):
...     for i in iterable:
...         yield i
...         sleep(0.1)
...
>>> red.source = read_slowly(blue.values)
>>> blue.source = read_slowly(sensor.values)

That's better - our CPU is now nice and idle most of the time. How about we make a time delay filter:

>>> def delayed(iterable):
...     q = []
...     for i in iterable:
...         q.append(i)
...         if len(q) > 5:
...             yield q.pop(0)

Now you can wrap stuff so that the red LED will "lag" behind the changes to the blue one:

>>> red.source = delayed(read_slowly(blue.values))

Or we could make a trivial filter which inverts booleans:

>>> def inverted(iterable):
...     for i in iterable:
...         yield 1.0 - i

So now the red LED will do the opposite of the blue LED after a short delay:

>>> red.source = inverted(delayed(read_slowly(blue.values)))

Er, yes, I'll stop now. I might be having too much fun with this. I'll push it in a mo so you can have a play too!

@bennuttall
Copy link
Member Author

Dave, this is amazing!!

@waveform80
Copy link
Member

Yeah - I'm actually going off our hooks now as I think this is even better and I haven't found anything I can do with hooks that I can't with this (yet). Still, I'm not actually proposing we remove hooks or anything - they still make for really nice readable code. I'm just saying this is even nicer :)

@waveform80
Copy link
Member

Oh, just a quick warning - when I push this don't merge it just yet. I still have a few changes to do like adding values to the AnalogInputDevice class (which doesn't descend from GPIODevice and so lacks it), and for that matter I still need to test I'm doing shutdown and stuff correctly. But it's too exciting not to push now and let people play :)

@bennuttall
Copy link
Member Author

Definitely. It's two ways of thinking of it:

When the button is pressed, turn on the LED.
The LED should be on when the button is pressed.

@waveform80
Copy link
Member

Oh well, never mind about not merging :) I'll do another PR when I've got the rest finished - it's not much now.

@bennuttall
Copy link
Member Author

Oh sorry - missed that

@waveform80
Copy link
Member

Hmm, the follow-up to this is turning slightly complicated (rather than mass-duplicate the code for source and values into all the non-GPIODevice descendents that it needs to be in, I'm looking into mixin classes to reduce the duplication). Unfortunately that means I'm probably not going to finish the follow-up to this until later tonight (I've got to play baby sitter later this afternoon). That said, I know exactly how this should all look now (in concert with the tuples comment on #79 - I should've seen that earlier - it was staring me in the face when I went to implement source and values on RGBLED).

Then I've just got to document all this... !

waveform80 added a commit to waveform-computing/gpio-zero that referenced this issue Oct 21, 2015
This finishes off implementing values and source for all (current)
classes in gpiozero. I'm afraid things get rather complex in this
commit. For starters, we've now got quite a few "aggregate" classes
which necessarily don't descend from GPIODevice. To implement values and
source on these I could either repeat a helluva lot of code or ... turn
to mixin classes. Yeah, it's multiple inheritance time, baby!

Unfortunately multiple inheritance doesn't work with __slots__ but we
really ought to keep functionality that they provide us (raise
AttributeError when an unknown attribute is set). So I've implemented
this with ... erm ... metaclasses. Sorry!
waveform80 added a commit to waveform-computing/gpio-zero that referenced this issue Oct 22, 2015
This finishes off implementing values and source for all (current)
classes in gpiozero. I'm afraid things get rather complex in this
commit. For starters, we've now got quite a few "aggregate" classes
which necessarily don't descend from GPIODevice. To implement values and
source on these I could either repeat a helluva lot of code or ... turn
to mixin classes. Yeah, it's multiple inheritance time, baby!

Unfortunately multiple inheritance doesn't work with __slots__ but we
really ought to keep functionality that they provide us (raise
AttributeError when an unknown attribute is set). So I've implemented
this with ... erm ... metaclasses. Sorry!
waveform80 added a commit to waveform-computing/gpio-zero that referenced this issue Oct 22, 2015
This finishes off implementing values and source for all (current)
classes in gpiozero. I'm afraid things get rather complex in this
commit. For starters, we've now got quite a few "aggregate" classes
which necessarily don't descend from GPIODevice. To implement values and
source on these I could either repeat a helluva lot of code or ... turn
to mixin classes. Yeah, it's multiple inheritance time, baby!

Unfortunately multiple inheritance doesn't work with __slots__ but we
really ought to keep functionality that they provide us (raise
AttributeError when an unknown attribute is set). So I've implemented
this with ... erm ... metaclasses. Sorry!
waveform80 added a commit to waveform-computing/gpio-zero that referenced this issue Oct 22, 2015
This finishes off implementing values and source for all (current)
classes in gpiozero. I'm afraid things get rather complex in this
commit. For starters, we've now got quite a few "aggregate" classes
which necessarily don't descend from GPIODevice. To implement values and
source on these I could either repeat a helluva lot of code or ... turn
to mixin classes. Yeah, it's multiple inheritance time, baby!

Unfortunately multiple inheritance doesn't work with __slots__ but we
really ought to keep functionality that they provide us (raise
AttributeError when an unknown attribute is set). So I've implemented
this with ... erm ... metaclasses. Sorry!
bennuttall added a commit that referenced this issue Oct 22, 2015
@waveform80 waveform80 added this to the v0.9.0 Beta 4 milestone Oct 29, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants