{"action":"created","issue":{"url":"https://api.github.com/repos/RPi-Distro/python-gpiozero/issues/131","repository_url":"https://api.github.com/repos/RPi-Distro/python-gpiozero","labels_url":"https://api.github.com/repos/RPi-Distro/python-gpiozero/issues/131/labels{/name}","comments_url":"https://api.github.com/repos/RPi-Distro/python-gpiozero/issues/131/comments","events_url":"https://api.github.com/repos/RPi-Distro/python-gpiozero/issues/131/events","html_url":"https://github.com/RPi-Distro/python-gpiozero/pull/131","id":120296712,"number":131,"title":"Add SnowPi - wip, close #130","user":{"login":"bennuttall","id":1609725,"avatar_url":"https://avatars.githubusercontent.com/u/1609725?v=3","gravatar_id":"","url":"https://api.github.com/users/bennuttall","html_url":"https://github.com/bennuttall","followers_url":"https://api.github.com/users/bennuttall/followers","following_url":"https://api.github.com/users/bennuttall/following{/other_user}","gists_url":"https://api.github.com/users/bennuttall/gists{/gist_id}","starred_url":"https://api.github.com/users/bennuttall/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bennuttall/subscriptions","organizations_url":"https://api.github.com/users/bennuttall/orgs","repos_url":"https://api.github.com/users/bennuttall/repos","events_url":"https://api.github.com/users/bennuttall/events{/privacy}","received_events_url":"https://api.github.com/users/bennuttall/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":8,"created_at":"2015-12-03T23:31:42Z","updated_at":"2016-02-05T19:46:19Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/RPi-Distro/python-gpiozero/pulls/131","html_url":"https://github.com/RPi-Distro/python-gpiozero/pull/131","diff_url":"https://github.com/RPi-Distro/python-gpiozero/pull/131.diff","patch_url":"https://github.com/RPi-Distro/python-gpiozero/pull/131.patch"},"body":"Untested WIP implementation of the Ryanteck SnowPi interface\r\n\r\nMay also require `close()` method, `closed` property, `source` and `values`?"},"comment":{"url":"https://api.github.com/repos/RPi-Distro/python-gpiozero/issues/comments/180522947","html_url":"https://github.com/RPi-Distro/python-gpiozero/pull/131#issuecomment-180522947","issue_url":"https://api.github.com/repos/RPi-Distro/python-gpiozero/issues/131","id":180522947,"user":{"login":"waveform80","id":1076190,"avatar_url":"https://avatars.githubusercontent.com/u/1076190?v=3","gravatar_id":"","url":"https://api.github.com/users/waveform80","html_url":"https://github.com/waveform80","followers_url":"https://api.github.com/users/waveform80/followers","following_url":"https://api.github.com/users/waveform80/following{/other_user}","gists_url":"https://api.github.com/users/waveform80/gists{/gist_id}","starred_url":"https://api.github.com/users/waveform80/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/waveform80/subscriptions","organizations_url":"https://api.github.com/users/waveform80/orgs","repos_url":"https://api.github.com/users/waveform80/repos","events_url":"https://api.github.com/users/waveform80/events{/privacy}","received_events_url":"https://api.github.com/users/waveform80/received_events","type":"User","site_admin":false},"created_at":"2016-02-05T19:46:19Z","updated_at":"2016-02-05T19:46:19Z","body":"Very good point on the composite device not including the button. Oddly enough I was pondering this earlier (without realizing we'd already committed such an error). In my opinion a composite device composed of both input and output devices should return the state of *all* subordinate devices in its `value` property (in other words, the implementation of `TrafficLightsBuzzer.value` is currently wrong for excluding the button value from its result tuple).\r\n\r\nHowever, on the question of what to do when *setting* the `value` of a composite device composed of both input and output devices, I'm not so sure (haven't thought about it enough yet). It seems to me there's three options:\r\n\r\n* Have a different tuple type that doesn't include the input devices. Personally I find this option distasteful as it effectively means that the type of the `value` property is different between the \"get\" and \"set\" operations which rather breaks the calculated property contract. It's a bit like a property which returns an integer but only accepts a string.\r\n\r\n* Accept the same tuple type, but ignore the values for any input device elements. This is the way I'm currently leaning simply because it makes chaining composite devices nice and easy. It's a little concerning to ignore values, but I can live with it.\r\n\r\n* Accept the same tuple type, but if any value other than `None` is specified for input device elements, throw an exception. This seems the \"strictly clean\" solution, but I don't particularly like it because it requires a transform in order to chain composite devices.\r\n\r\nOpinions welcome!\r\n\r\nOn whether the number of elements must match: yes they must match as the number of elements is intrinsic to the type of the tuple. That said, just because we're using namedtuples for the result of `value` we accept non-named tuples on setting (provided they have the same number of elements) as namedtuples are just a specialization of tuple.\r\n\r\nIf the number of elements doesn't match, one can use slicing (or concatenation) in a generator transform function to compensate. Assuming a (currently) fictional `ButtonBoard`:\r\n\r\n```python\r\nfrom gpiozero import LEDBoard, ButtonBoard\r\n\r\ndef first(it, n=1):\r\n    for value in it:\r\n        yield value[:n]\r\n\r\ndef last(it, n=1):\r\n    for value in it:\r\n        yield value[-n:]\r\n\r\ndef append_ones(it, n=1):\r\n    for value in it:\r\n        yield value + ((1,) * n)\r\n\r\nbuttons = ButtonBoard(5, 6, 7, 8)\r\nleds = LEDBoard(11, 12, 13, 14)\r\n\r\n# Map all buttons to LEDs\r\nleds.source = buttons.values\r\n\r\n# Map the first two buttons to LEDs and set the rest always on\r\nleds.source = append_ones(first(buttons.values, 2), 2)\r\n\r\n# And so on...\r\n```\r\n\r\nOn whether there should be a variety of generator transforms built into the library: I'm quite torn on this question. I'd love to do it but there's two things holding me back: firstly I'm not convinced gpiozero is actually the right place for them (I'm currently working on something which might be: a library for linking arbitrary stuff like gpiozero to any other library based on a suggestion from Ben that it'd be nice to link it to picamera so you could control, e.g. the camera brightness with a pot). Secondly, it almost feels like I'd be removing a really great learning opportunity. Not that that's actually a good reason to exclude it from the library (one can always ignore a library when teaching and then reveal that what the students have written is already implemented for them).\r\n\r\nOn composites of composites: I would nest the tuples instead of flattening (arbitrary depth flattening is generally a bad idea for reasons too long to go into here). This should lead to a natural progression of names in the namedtuple classes. Consider the SnowPi example. I wouldn't bother with Ben's `SnowPiArms` class but I would keep `SnowPiArm` and simply instantiate it twice in `SnowPi`: once for left, once for right. Then `SnowPi`'s namedtuple would look something like: `(eyes, nose, left, right)`, while `SnowPiArm`'s namedtuple would be `(top, middle, bottom)` and `SnowPiEyes` would be `(left, right)`.\r\n\r\nSo, you'd access the value of the left eye as `my_snow_pi.value.eyes.left`, or the value of the middle LED on the right arm as `my_snow_pi.value.right.middle`, or get a tuple of the left arms LED values as `my_snow_pi.value.left`, and so on.\r\n\r\nFinally, would the code work identically if switched? Basically yes; it's just a question of which background reading thread gets set up first. So there's a tiny (microsecond level) difference in the start time of one thread from the other, but otherwise no difference."}}