Coverage for /usr/lib/python3/dist-packages/gpiozero/input_devices.py: 33%
357 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 16:40 +0100
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 16:40 +0100
1# vim: set fileencoding=utf-8:
2#
3# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
4#
5# Copyright (c) 2015-2021 Dave Jones <dave@waveform.org.uk>
6# Copyright (c) 2015-2021 Ben Nuttall <ben@bennuttall.com>
7# Copyright (c) 2020 Robert Erdin <roberte@depop.com>
8# Copyright (c) 2020 Dan Jackson <dan@djackson.org>
9# Copyright (c) 2016-2020 Andrew Scheller <github@loowis.durge.org>
10# Copyright (c) 2019 Kosovan Sofiia <sofiia.kosovan@gmail.com>
11# Copyright (c) 2018 Philippe Muller <philippe.muller@gmail.com>
12# Copyright (c) 2016 Steveis <SteveAmor@users.noreply.github.com>
13#
14# SPDX-License-Identifier: BSD-3-Clause
16from __future__ import (
17 unicode_literals,
18 print_function,
19 absolute_import,
20 division,
21)
23import warnings
24from time import sleep, time
25from threading import Event, Lock
26from itertools import tee
27try:
28 from statistics import median, mean
29except ImportError:
30 from .compat import median, mean
32from .exc import InputDeviceError, DeviceClosed, DistanceSensorNoEcho, \
33 PinInvalidState, PWMSoftwareFallback
34from .devices import GPIODevice, CompositeDevice
35from .mixins import GPIOQueue, EventsMixin, HoldMixin, event
36try:
37 from .pins.pigpio import PiGPIOFactory
38except ImportError:
39 PiGPIOFactory = None
42class InputDevice(GPIODevice):
43 """
44 Represents a generic GPIO input device.
46 This class extends :class:`GPIODevice` to add facilities common to GPIO
47 input devices. The constructor adds the optional *pull_up* parameter to
48 specify how the pin should be pulled by the internal resistors. The
49 :attr:`is_active` property is adjusted accordingly so that :data:`True`
50 still means active regardless of the *pull_up* setting.
52 :type pin: int or str
53 :param pin:
54 The GPIO pin that the device is connected to. See :ref:`pin-numbering`
55 for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
56 will be raised.
58 :type pull_up: bool or None
59 :param pull_up:
60 If :data:`True`, the pin will be pulled high with an internal resistor.
61 If :data:`False` (the default), the pin will be pulled low. If
62 :data:`None`, the pin will be floating. As gpiozero cannot
63 automatically guess the active state when not pulling the pin, the
64 *active_state* parameter must be passed.
66 :type active_state: bool or None
67 :param active_state:
68 If :data:`True`, when the hardware pin state is ``HIGH``, the software
69 pin is ``HIGH``. If :data:`False`, the input polarity is reversed: when
70 the hardware pin state is ``HIGH``, the software pin state is ``LOW``.
71 Use this parameter to set the active state of the underlying pin when
72 configuring it as not pulled (when *pull_up* is :data:`None`). When
73 *pull_up* is :data:`True` or :data:`False`, the active state is
74 automatically set to the proper value.
76 :type pin_factory: Factory or None
77 :param pin_factory:
78 See :doc:`api_pins` for more information (this is an advanced feature
79 which most users can ignore).
80 """
81 def __init__(self, pin=None, pull_up=False, active_state=None,
82 pin_factory=None):
83 super(InputDevice, self).__init__(pin, pin_factory=pin_factory)
84 try:
85 self.pin.function = 'input'
86 pull = {None: 'floating', True: 'up', False: 'down'}[pull_up]
87 if self.pin.pull != pull:
88 self.pin.pull = pull
89 except:
90 self.close()
91 raise
93 if pull_up is None: 93 ↛ 94line 93 didn't jump to line 94, because the condition on line 93 was never true
94 if active_state is None:
95 raise PinInvalidState(
96 'Pin %d is defined as floating, but "active_state" is not '
97 'defined' % self.pin.number)
98 self._active_state = bool(active_state)
99 else:
100 if active_state is not None: 100 ↛ 101line 100 didn't jump to line 101, because the condition on line 100 was never true
101 raise PinInvalidState(
102 'Pin %d is not floating, but "active_state" is not None' %
103 self.pin.number)
104 self._active_state = False if pull_up else True
105 self._inactive_state = not self._active_state
107 @property
108 def pull_up(self):
109 """
110 If :data:`True`, the device uses a pull-up resistor to set the GPIO pin
111 "high" by default.
112 """
113 pull = self.pin.pull
114 if pull == 'floating':
115 return None
116 else:
117 return pull == 'up'
119 def __repr__(self):
120 try:
121 return "<gpiozero.%s object on pin %r, pull_up=%s, is_active=%s>" % (
122 self.__class__.__name__, self.pin, self.pull_up, self.is_active)
123 except:
124 return super(InputDevice, self).__repr__()
127class DigitalInputDevice(EventsMixin, InputDevice):
128 """
129 Represents a generic input device with typical on/off behaviour.
131 This class extends :class:`InputDevice` with machinery to fire the active
132 and inactive events for devices that operate in a typical digital manner:
133 straight forward on / off states with (reasonably) clean transitions
134 between the two.
136 :type pin: int or str
137 :param pin:
138 The GPIO pin that the device is connected to. See :ref:`pin-numbering`
139 for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
140 will be raised.
142 :type pull_up: bool or None
143 :param pull_up:
144 See description under :class:`InputDevice` for more information.
146 :type active_state: bool or None
147 :param active_state:
148 See description under :class:`InputDevice` for more information.
150 :type bounce_time: float or None
151 :param bounce_time:
152 Specifies the length of time (in seconds) that the component will
153 ignore changes in state after an initial change. This defaults to
154 :data:`None` which indicates that no bounce compensation will be
155 performed.
157 :type pin_factory: Factory or None
158 :param pin_factory:
159 See :doc:`api_pins` for more information (this is an advanced feature
160 which most users can ignore).
161 """
162 def __init__(
163 self, pin=None, pull_up=False, active_state=None, bounce_time=None,
164 pin_factory=None):
165 super(DigitalInputDevice, self).__init__(
166 pin, pull_up=pull_up, active_state=active_state,
167 pin_factory=pin_factory)
168 try:
169 self.pin.bounce = bounce_time
170 self.pin.edges = 'both'
171 self.pin.when_changed = self._pin_changed
172 # Call _fire_events once to set initial state of events
173 self._fire_events(self.pin_factory.ticks(), self.is_active)
174 except:
175 self.close()
176 raise
178 def _pin_changed(self, ticks, state):
179 # XXX This is a bit of a hack; _fire_events takes *is_active* rather
180 # than *value*. Here we're assuming no-one's overridden the default
181 # implementation of *is_active*.
182 self._fire_events(ticks, bool(self._state_to_value(state)))
185class SmoothedInputDevice(EventsMixin, InputDevice):
186 """
187 Represents a generic input device which takes its value from the average of
188 a queue of historical values.
190 This class extends :class:`InputDevice` with a queue which is filled by a
191 background thread which continually polls the state of the underlying
192 device. The average (a configurable function) of the values in the queue is
193 compared to a threshold which is used to determine the state of the
194 :attr:`is_active` property.
196 .. note::
198 The background queue is not automatically started upon construction.
199 This is to allow descendents to set up additional components before the
200 queue starts reading values. Effectively this is an abstract base
201 class.
203 This class is intended for use with devices which either exhibit analog
204 behaviour (such as the charging time of a capacitor with an LDR), or those
205 which exhibit "twitchy" behaviour (such as certain motion sensors).
207 :type pin: int or str
208 :param pin:
209 The GPIO pin that the device is connected to. See :ref:`pin-numbering`
210 for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
211 will be raised.
213 :type pull_up: bool or None
214 :param pull_up:
215 See description under :class:`InputDevice` for more information.
217 :type active_state: bool or None
218 :param active_state:
219 See description under :class:`InputDevice` for more information.
221 :param float threshold:
222 The value above which the device will be considered "on".
224 :param int queue_len:
225 The length of the internal queue which is filled by the background
226 thread.
228 :param float sample_wait:
229 The length of time to wait between retrieving the state of the
230 underlying device. Defaults to 0.0 indicating that values are retrieved
231 as fast as possible.
233 :param bool partial:
234 If :data:`False` (the default), attempts to read the state of the
235 device (from the :attr:`is_active` property) will block until the queue
236 has filled. If :data:`True`, a value will be returned immediately, but
237 be aware that this value is likely to fluctuate excessively.
239 :param average:
240 The function used to average the values in the internal queue. This
241 defaults to :func:`statistics.median` which is a good selection for
242 discarding outliers from jittery sensors. The function specified must
243 accept a sequence of numbers and return a single number.
245 :type ignore: frozenset or None
246 :param ignore:
247 The set of values which the queue should ignore, if returned from
248 querying the device's value.
250 :type pin_factory: Factory or None
251 :param pin_factory:
252 See :doc:`api_pins` for more information (this is an advanced feature
253 which most users can ignore).
254 """
255 def __init__(
256 self, pin=None, pull_up=False, active_state=None, threshold=0.5,
257 queue_len=5, sample_wait=0.0, partial=False, average=median,
258 ignore=None, pin_factory=None):
259 self._queue = None
260 super(SmoothedInputDevice, self).__init__(
261 pin, pull_up=pull_up, active_state=active_state,
262 pin_factory=pin_factory)
263 try:
264 self._queue = GPIOQueue(self, queue_len, sample_wait, partial,
265 average, ignore)
266 self.threshold = float(threshold)
267 except:
268 self.close()
269 raise
271 def close(self):
272 try:
273 self._queue.stop()
274 except AttributeError:
275 # If the queue isn't initialized (it's None), or _queue hasn't been
276 # set ignore the error because we're trying to close anyway
277 pass
278 except RuntimeError:
279 # Cannot join thread before it starts; we don't care about this
280 # because we're trying to close the thread anyway
281 pass
282 self._queue = None
283 super(SmoothedInputDevice, self).close()
285 def __repr__(self):
286 try:
287 self._check_open()
288 except DeviceClosed:
289 return super(SmoothedInputDevice, self).__repr__()
290 else:
291 if self.partial or self._queue.full.is_set():
292 return super(SmoothedInputDevice, self).__repr__()
293 else:
294 return "<gpiozero.%s object on pin %r, pull_up=%s>" % (
295 self.__class__.__name__, self.pin, self.pull_up)
297 @property
298 def queue_len(self):
299 """
300 The length of the internal queue of values which is averaged to
301 determine the overall state of the device. This defaults to 5.
302 """
303 self._check_open()
304 return self._queue.queue.maxlen
306 @property
307 def partial(self):
308 """
309 If :data:`False` (the default), attempts to read the
310 :attr:`~SmoothedInputDevice.value` or
311 :attr:`~SmoothedInputDevice.is_active` properties will block until the
312 queue has filled.
313 """
314 self._check_open()
315 return self._queue.partial
317 @property
318 def value(self):
319 """
320 Returns the average of the values in the internal queue. This is
321 compared to :attr:`~SmoothedInputDevice.threshold` to determine whether
322 :attr:`is_active` is :data:`True`.
323 """
324 self._check_open()
325 return self._queue.value
327 @property
328 def threshold(self):
329 """
330 If :attr:`~SmoothedInputDevice.value` exceeds this amount, then
331 :attr:`is_active` will return :data:`True`.
332 """
333 return self._threshold
335 @threshold.setter
336 def threshold(self, value):
337 if not (0.0 < value < 1.0):
338 raise InputDeviceError(
339 'threshold must be between zero and one exclusive'
340 )
341 self._threshold = float(value)
343 @property
344 def is_active(self):
345 """
346 Returns :data:`True` if the :attr:`~SmoothedInputDevice.value`
347 currently exceeds :attr:`~SmoothedInputDevice.threshold` and
348 :data:`False` otherwise.
349 """
350 return self.value > self.threshold
353class Button(HoldMixin, DigitalInputDevice):
354 """
355 Extends :class:`DigitalInputDevice` and represents a simple push button
356 or switch.
358 Connect one side of the button to a ground pin, and the other to any GPIO
359 pin. Alternatively, connect one side of the button to the 3V3 pin, and the
360 other to any GPIO pin, then set *pull_up* to :data:`False` in the
361 :class:`Button` constructor.
363 The following example will print a line of text when the button is pushed::
365 from gpiozero import Button
367 button = Button(4)
368 button.wait_for_press()
369 print("The button was pressed!")
371 :type pin: int or str
372 :param pin:
373 The GPIO pin which the button is connected to. See :ref:`pin-numbering`
374 for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
375 will be raised.
377 :type pull_up: bool or None
378 :param pull_up:
379 If :data:`True` (the default), the GPIO pin will be pulled high by
380 default. In this case, connect the other side of the button to ground.
381 If :data:`False`, the GPIO pin will be pulled low by default. In this
382 case, connect the other side of the button to 3V3. If :data:`None`, the
383 pin will be floating, so it must be externally pulled up or down and
384 the ``active_state`` parameter must be set accordingly.
386 :type active_state: bool or None
387 :param active_state:
388 See description under :class:`InputDevice` for more information.
390 :type bounce_time: float or None
391 :param bounce_time:
392 If :data:`None` (the default), no software bounce compensation will be
393 performed. Otherwise, this is the length of time (in seconds) that the
394 component will ignore changes in state after an initial change.
396 :param float hold_time:
397 The length of time (in seconds) to wait after the button is pushed,
398 until executing the :attr:`when_held` handler. Defaults to ``1``.
400 :param bool hold_repeat:
401 If :data:`True`, the :attr:`when_held` handler will be repeatedly
402 executed as long as the device remains active, every *hold_time*
403 seconds. If :data:`False` (the default) the :attr:`when_held` handler
404 will be only be executed once per hold.
406 :type pin_factory: Factory or None
407 :param pin_factory:
408 See :doc:`api_pins` for more information (this is an advanced feature
409 which most users can ignore).
410 """
411 def __init__(
412 self, pin=None, pull_up=True, active_state=None, bounce_time=None,
413 hold_time=1, hold_repeat=False, pin_factory=None):
414 super(Button, self).__init__(
415 pin, pull_up=pull_up, active_state=active_state,
416 bounce_time=bounce_time, pin_factory=pin_factory)
417 self.hold_time = hold_time
418 self.hold_repeat = hold_repeat
420 @property
421 def value(self):
422 """
423 Returns 1 if the button is currently pressed, and 0 if it is not.
424 """
425 return super(Button, self).value
427Button.is_pressed = Button.is_active
428Button.pressed_time = Button.active_time
429Button.when_pressed = Button.when_activated
430Button.when_released = Button.when_deactivated
431Button.wait_for_press = Button.wait_for_active
432Button.wait_for_release = Button.wait_for_inactive
435class LineSensor(SmoothedInputDevice):
436 """
437 Extends :class:`SmoothedInputDevice` and represents a single pin line
438 sensor like the TCRT5000 infra-red proximity sensor found in the `CamJam #3
439 EduKit`_.
441 A typical line sensor has a small circuit board with three pins: VCC, GND,
442 and OUT. VCC should be connected to a 3V3 pin, GND to one of the ground
443 pins, and finally OUT to the GPIO specified as the value of the *pin*
444 parameter in the constructor.
446 The following code will print a line of text indicating when the sensor
447 detects a line, or stops detecting a line::
449 from gpiozero import LineSensor
450 from signal import pause
452 sensor = LineSensor(4)
453 sensor.when_line = lambda: print('Line detected')
454 sensor.when_no_line = lambda: print('No line detected')
455 pause()
457 :type pin: int or str
458 :param pin:
459 The GPIO pin which the sensor is connected to. See :ref:`pin-numbering`
460 for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
461 will be raised.
463 :type pull_up: bool or None
464 :param pull_up:
465 See description under :class:`InputDevice` for more information.
467 :type active_state: bool or None
468 :param active_state:
469 See description under :class:`InputDevice` for more information.
471 :param int queue_len:
472 The length of the queue used to store values read from the sensor. This
473 defaults to 5.
475 :param float sample_rate:
476 The number of values to read from the device (and append to the
477 internal queue) per second. Defaults to 100.
479 :param float threshold:
480 Defaults to 0.5. When the average of all values in the internal queue
481 rises above this value, the sensor will be considered "active" by the
482 :attr:`~SmoothedInputDevice.is_active` property, and all appropriate
483 events will be fired.
485 :param bool partial:
486 When :data:`False` (the default), the object will not return a value
487 for :attr:`~SmoothedInputDevice.is_active` until the internal queue has
488 filled with values. Only set this to :data:`True` if you require
489 values immediately after object construction.
491 :type pin_factory: Factory or None
492 :param pin_factory:
493 See :doc:`api_pins` for more information (this is an advanced feature
494 which most users can ignore).
496 .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035
497 """
498 def __init__(
499 self, pin=None, pull_up=False, active_state=None, queue_len=5,
500 sample_rate=100, threshold=0.5, partial=False, pin_factory=None):
501 super(LineSensor, self).__init__(
502 pin, pull_up=pull_up, active_state=active_state,
503 threshold=threshold, queue_len=queue_len,
504 sample_wait=1 / sample_rate, partial=partial,
505 pin_factory=pin_factory)
506 self._queue.start()
508 @property
509 def value(self):
510 """
511 Returns a value representing the average of the queued values. This
512 is nearer 0 for black under the sensor, and nearer 1 for white under
513 the sensor.
514 """
515 return super(LineSensor, self).value
517 @property
518 def line_detected(self):
519 return not self.is_active
521LineSensor.when_line = LineSensor.when_deactivated
522LineSensor.when_no_line = LineSensor.when_activated
523LineSensor.wait_for_line = LineSensor.wait_for_inactive
524LineSensor.wait_for_no_line = LineSensor.wait_for_active
527class MotionSensor(SmoothedInputDevice):
528 """
529 Extends :class:`SmoothedInputDevice` and represents a passive infra-red
530 (PIR) motion sensor like the sort found in the `CamJam #2 EduKit`_.
532 .. _CamJam #2 EduKit: http://camjam.me/?page_id=623
534 A typical PIR device has a small circuit board with three pins: VCC, OUT,
535 and GND. VCC should be connected to a 5V pin, GND to one of the ground
536 pins, and finally OUT to the GPIO specified as the value of the *pin*
537 parameter in the constructor.
539 The following code will print a line of text when motion is detected::
541 from gpiozero import MotionSensor
543 pir = MotionSensor(4)
544 pir.wait_for_motion()
545 print("Motion detected!")
547 :type pin: int or str
548 :param pin:
549 The GPIO pin which the sensor is connected to. See :ref:`pin-numbering`
550 for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
551 will be raised.
553 :type pull_up: bool or None
554 :param pull_up:
555 See description under :class:`InputDevice` for more information.
557 :type active_state: bool or None
558 :param active_state:
559 See description under :class:`InputDevice` for more information.
561 :param int queue_len:
562 The length of the queue used to store values read from the sensor. This
563 defaults to 1 which effectively disables the queue. If your motion
564 sensor is particularly "twitchy" you may wish to increase this value.
566 :param float sample_rate:
567 The number of values to read from the device (and append to the
568 internal queue) per second. Defaults to 10.
570 :param float threshold:
571 Defaults to 0.5. When the average of all values in the internal queue
572 rises above this value, the sensor will be considered "active" by the
573 :attr:`~SmoothedInputDevice.is_active` property, and all appropriate
574 events will be fired.
576 :param bool partial:
577 When :data:`False` (the default), the object will not return a value
578 for :attr:`~SmoothedInputDevice.is_active` until the internal queue has
579 filled with values. Only set this to :data:`True` if you require
580 values immediately after object construction.
582 :type pin_factory: Factory or None
583 :param pin_factory:
584 See :doc:`api_pins` for more information (this is an advanced feature
585 which most users can ignore).
586 """
587 def __init__(
588 self, pin=None, pull_up=False, active_state=None, queue_len=1,
589 sample_rate=10, threshold=0.5, partial=False, pin_factory=None):
590 super(MotionSensor, self).__init__(
591 pin, pull_up=pull_up, active_state=active_state,
592 threshold=threshold, queue_len=queue_len, sample_wait=1 /
593 sample_rate, partial=partial, pin_factory=pin_factory, average=mean)
594 self._queue.start()
596 @property
597 def value(self):
598 """
599 With the default *queue_len* of 1, this is effectively boolean where 0
600 means no motion detected and 1 means motion detected. If you specify
601 a *queue_len* greater than 1, this will be an averaged value where
602 values closer to 1 imply motion detection.
603 """
604 return super(MotionSensor, self).value
606MotionSensor.motion_detected = MotionSensor.is_active
607MotionSensor.when_motion = MotionSensor.when_activated
608MotionSensor.when_no_motion = MotionSensor.when_deactivated
609MotionSensor.wait_for_motion = MotionSensor.wait_for_active
610MotionSensor.wait_for_no_motion = MotionSensor.wait_for_inactive
613class LightSensor(SmoothedInputDevice):
614 """
615 Extends :class:`SmoothedInputDevice` and represents a light dependent
616 resistor (LDR).
618 Connect one leg of the LDR to the 3V3 pin; connect one leg of a 1µF
619 capacitor to a ground pin; connect the other leg of the LDR and the other
620 leg of the capacitor to the same GPIO pin. This class repeatedly discharges
621 the capacitor, then times the duration it takes to charge (which will vary
622 according to the light falling on the LDR).
624 The following code will print a line of text when light is detected::
626 from gpiozero import LightSensor
628 ldr = LightSensor(18)
629 ldr.wait_for_light()
630 print("Light detected!")
632 :type pin: int or str
633 :param pin:
634 The GPIO pin which the sensor is attached to. See :ref:`pin-numbering`
635 for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
636 will be raised.
638 :param int queue_len:
639 The length of the queue used to store values read from the circuit.
640 This defaults to 5.
642 :param float charge_time_limit:
643 If the capacitor in the circuit takes longer than this length of time
644 to charge, it is assumed to be dark. The default (0.01 seconds) is
645 appropriate for a 1µF capacitor coupled with the LDR from the
646 `CamJam #2 EduKit`_. You may need to adjust this value for different
647 valued capacitors or LDRs.
649 :param float threshold:
650 Defaults to 0.1. When the average of all values in the internal queue
651 rises above this value, the area will be considered "light", and all
652 appropriate events will be fired.
654 :param bool partial:
655 When :data:`False` (the default), the object will not return a value
656 for :attr:`~SmoothedInputDevice.is_active` until the internal queue has
657 filled with values. Only set this to :data:`True` if you require
658 values immediately after object construction.
660 :type pin_factory: Factory or None
661 :param pin_factory:
662 See :doc:`api_pins` for more information (this is an advanced feature
663 which most users can ignore).
665 .. _CamJam #2 EduKit: http://camjam.me/?page_id=623
666 """
667 def __init__(
668 self, pin=None, queue_len=5, charge_time_limit=0.01,
669 threshold=0.1, partial=False, pin_factory=None):
670 super(LightSensor, self).__init__(
671 pin, pull_up=False, threshold=threshold, queue_len=queue_len,
672 sample_wait=0.0, partial=partial, pin_factory=pin_factory)
673 try:
674 self._charge_time_limit = charge_time_limit
675 self._charge_time = None
676 self._charged = Event()
677 self.pin.edges = 'rising'
678 self.pin.bounce = None
679 self.pin.when_changed = self._cap_charged
680 self._queue.start()
681 except:
682 self.close()
683 raise
685 @property
686 def charge_time_limit(self):
687 return self._charge_time_limit
689 def _cap_charged(self, ticks, state):
690 self._charge_time = ticks
691 self._charged.set()
693 def _read(self):
694 # Drain charge from the capacitor
695 self.pin.function = 'output'
696 self.pin.state = False
697 sleep(0.1)
698 self._charge_time = None
699 self._charged.clear()
700 # Time the charging of the capacitor
701 start = self.pin_factory.ticks()
702 self.pin.function = 'input'
703 self._charged.wait(self.charge_time_limit)
704 if self._charge_time is None:
705 return 0.0
706 else:
707 return 1.0 - min(1.0,
708 (self.pin_factory.ticks_diff(self._charge_time, start) /
709 self.charge_time_limit))
711 @property
712 def value(self):
713 """
714 Returns a value between 0 (dark) and 1 (light).
715 """
716 return super(LightSensor, self).value
718LightSensor.light_detected = LightSensor.is_active
719LightSensor.when_light = LightSensor.when_activated
720LightSensor.when_dark = LightSensor.when_deactivated
721LightSensor.wait_for_light = LightSensor.wait_for_active
722LightSensor.wait_for_dark = LightSensor.wait_for_inactive
725class DistanceSensor(SmoothedInputDevice):
726 """
727 Extends :class:`SmoothedInputDevice` and represents an HC-SR04 ultrasonic
728 distance sensor, as found in the `CamJam #3 EduKit`_.
730 The distance sensor requires two GPIO pins: one for the *trigger* (marked
731 TRIG on the sensor) and another for the *echo* (marked ECHO on the sensor).
732 However, a voltage divider is required to ensure the 5V from the ECHO pin
733 doesn't damage the Pi. Wire your sensor according to the following
734 instructions:
736 1. Connect the GND pin of the sensor to a ground pin on the Pi.
738 2. Connect the TRIG pin of the sensor a GPIO pin.
740 3. Connect one end of a 330Ω resistor to the ECHO pin of the sensor.
742 4. Connect one end of a 470Ω resistor to the GND pin of the sensor.
744 5. Connect the free ends of both resistors to another GPIO pin. This forms
745 the required `voltage divider`_.
747 6. Finally, connect the VCC pin of the sensor to a 5V pin on the Pi.
749 Alternatively, the 3V3 tolerant HC-SR04P sensor (which does not require a
750 voltage divider) will work with this class.
753 .. note::
755 If you do not have the precise values of resistor specified above,
756 don't worry! What matters is the *ratio* of the resistors to each
757 other.
759 You also don't need to be absolutely precise; the `voltage divider`_
760 given above will actually output ~3V (rather than 3.3V). A simple 2:3
761 ratio will give 3.333V which implies you can take three resistors of
762 equal value, use one of them instead of the 330Ω resistor, and two of
763 them in series instead of the 470Ω resistor.
765 .. _voltage divider: https://en.wikipedia.org/wiki/Voltage_divider
767 The following code will periodically report the distance measured by the
768 sensor in cm assuming the TRIG pin is connected to GPIO17, and the ECHO
769 pin to GPIO18::
771 from gpiozero import DistanceSensor
772 from time import sleep
774 sensor = DistanceSensor(echo=18, trigger=17)
775 while True:
776 print('Distance: ', sensor.distance * 100)
777 sleep(1)
779 .. note::
781 For improved accuracy, use the pigpio pin driver rather than the default
782 RPi.GPIO driver (pigpio uses DMA sampling for much more precise edge
783 timing). This is particularly relevant if you're using Pi 1 or Pi Zero.
784 See :ref:`changing-pin-factory` for further information.
786 :type echo: int or str
787 :param echo:
788 The GPIO pin which the ECHO pin is connected to. See
789 :ref:`pin-numbering` for valid pin numbers. If this is :data:`None` a
790 :exc:`GPIODeviceError` will be raised.
792 :type trigger: int or str
793 :param trigger:
794 The GPIO pin which the TRIG pin is connected to. See
795 :ref:`pin-numbering` for valid pin numbers. If this is :data:`None` a
796 :exc:`GPIODeviceError` will be raised.
798 :param int queue_len:
799 The length of the queue used to store values read from the sensor.
800 This defaults to 9.
802 :param float max_distance:
803 The :attr:`value` attribute reports a normalized value between 0 (too
804 close to measure) and 1 (maximum distance). This parameter specifies
805 the maximum distance expected in meters. This defaults to 1.
807 :param float threshold_distance:
808 Defaults to 0.3. This is the distance (in meters) that will trigger the
809 ``in_range`` and ``out_of_range`` events when crossed.
811 :param bool partial:
812 When :data:`False` (the default), the object will not return a value
813 for :attr:`~SmoothedInputDevice.is_active` until the internal queue has
814 filled with values. Only set this to :data:`True` if you require
815 values immediately after object construction.
817 :type pin_factory: Factory or None
818 :param pin_factory:
819 See :doc:`api_pins` for more information (this is an advanced feature
820 which most users can ignore).
822 .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035
823 """
824 ECHO_LOCK = Lock()
826 def __init__(
827 self, echo=None, trigger=None, queue_len=9, max_distance=1,
828 threshold_distance=0.3, partial=False, pin_factory=None):
829 self._trigger = None
830 super(DistanceSensor, self).__init__(
831 echo, pull_up=False, queue_len=queue_len, sample_wait=0.06,
832 partial=partial, ignore=frozenset({None}), pin_factory=pin_factory
833 )
834 try:
835 if max_distance <= 0:
836 raise ValueError('invalid maximum distance (must be positive)')
837 self._max_distance = max_distance
838 self.threshold = threshold_distance / max_distance
839 self.speed_of_sound = 343.26 # m/s
840 self._trigger = GPIODevice(trigger, pin_factory=pin_factory)
841 self._echo = Event()
842 self._echo_rise = None
843 self._echo_fall = None
844 self._trigger.pin.function = 'output'
845 self._trigger.pin.state = False
846 self.pin.edges = 'both'
847 self.pin.bounce = None
848 self.pin.when_changed = self._echo_changed
849 self._queue.start()
850 except:
851 self.close()
852 raise
854 if PiGPIOFactory is None or not isinstance(self.pin_factory, PiGPIOFactory):
855 warnings.warn(PWMSoftwareFallback(
856 'For more accurate readings, use the pigpio pin factory.'
857 'See https://gpiozero.readthedocs.io/en/stable/api_input.html#distancesensor-hc-sr04 for more info'
858 ))
860 def close(self):
861 try:
862 self._trigger.close()
863 except AttributeError:
864 pass
865 self._trigger = None
866 super(DistanceSensor, self).close()
868 @property
869 def max_distance(self):
870 """
871 The maximum distance that the sensor will measure in meters. This value
872 is specified in the constructor and is used to provide the scaling for
873 the :attr:`~SmoothedInputDevice.value` attribute. When :attr:`distance`
874 is equal to :attr:`max_distance`, :attr:`~SmoothedInputDevice.value`
875 will be 1.
876 """
877 return self._max_distance
879 @max_distance.setter
880 def max_distance(self, value):
881 if value <= 0:
882 raise ValueError('invalid maximum distance (must be positive)')
883 t = self.threshold_distance
884 self._max_distance = value
885 self.threshold_distance = t
887 @property
888 def threshold_distance(self):
889 """
890 The distance, measured in meters, that will trigger the
891 :attr:`when_in_range` and :attr:`when_out_of_range` events when
892 crossed. This is simply a meter-scaled variant of the usual
893 :attr:`~SmoothedInputDevice.threshold` attribute.
894 """
895 return self.threshold * self.max_distance
897 @threshold_distance.setter
898 def threshold_distance(self, value):
899 self.threshold = value / self.max_distance
901 @property
902 def distance(self):
903 """
904 Returns the current distance measured by the sensor in meters. Note
905 that this property will have a value between 0 and
906 :attr:`max_distance`.
907 """
908 return self.value * self._max_distance
910 @property
911 def value(self):
912 """
913 Returns a value between 0, indicating the reflector is either touching
914 the sensor or is sufficiently near that the sensor can't tell the
915 difference, and 1, indicating the reflector is at or beyond the
916 specified *max_distance*.
917 """
918 return super(DistanceSensor, self).value
920 @property
921 def trigger(self):
922 """
923 Returns the :class:`Pin` that the sensor's trigger is connected to.
924 """
925 return self._trigger.pin
927 @property
928 def echo(self):
929 """
930 Returns the :class:`Pin` that the sensor's echo is connected to. This
931 is simply an alias for the usual :attr:`~GPIODevice.pin` attribute.
932 """
933 return self.pin
935 def _echo_changed(self, ticks, level):
936 if level:
937 self._echo_rise = ticks
938 else:
939 self._echo_fall = ticks
940 self._echo.set()
942 def _read(self):
943 # Wait up to 50ms for the echo pin to fall to low (the maximum echo
944 # pulse is 35ms so this gives some leeway); if it doesn't something is
945 # horribly wrong (most likely at the hardware level)
946 if self.pin.state:
947 if not self._echo.wait(0.05):
948 warnings.warn(DistanceSensorNoEcho('echo pin set high'))
949 return None
950 self._echo.clear()
951 self._echo_fall = None
952 self._echo_rise = None
953 # Obtain the class-level ECHO_LOCK to ensure multiple distance sensors
954 # don't listen for each other's "pings"
955 with DistanceSensor.ECHO_LOCK:
956 # Fire the trigger
957 self._trigger.pin.state = True
958 sleep(0.00001)
959 self._trigger.pin.state = False
960 # Wait up to 100ms for the echo pin to rise and fall (35ms is the
961 # maximum pulse time, but the pre-rise time is unspecified in the
962 # "datasheet"; 100ms seems sufficiently long to conclude something
963 # has failed)
964 if self._echo.wait(0.1):
965 if self._echo_fall is not None and self._echo_rise is not None:
966 distance = (
967 self.pin_factory.ticks_diff(
968 self._echo_fall, self._echo_rise) *
969 self.speed_of_sound / 2.0)
970 return min(1.0, distance / self._max_distance)
971 else:
972 # If we only saw the falling edge it means we missed
973 # the echo because it was too fast
974 return None
975 else:
976 # The echo pin never rose or fell; something's gone horribly
977 # wrong
978 warnings.warn(DistanceSensorNoEcho('no echo received'))
979 return None
981 @property
982 def in_range(self):
983 return not self.is_active
985DistanceSensor.when_out_of_range = DistanceSensor.when_activated
986DistanceSensor.when_in_range = DistanceSensor.when_deactivated
987DistanceSensor.wait_for_out_of_range = DistanceSensor.wait_for_active
988DistanceSensor.wait_for_in_range = DistanceSensor.wait_for_inactive
991class RotaryEncoder(EventsMixin, CompositeDevice):
992 """
993 Represents a simple two-pin incremental `rotary encoder`_ device.
995 These devices typically have three pins labelled "A", "B", and "C". Connect
996 A and B directly to two GPIO pins, and C ("common") to one of the ground
997 pins on your Pi. Then simply specify the A and B pins as the arguments when
998 constructing this classs.
1000 For example, if your encoder's A pin is connected to GPIO 21, and the B
1001 pin to GPIO 20 (and presumably the C pin to a suitable GND pin), while an
1002 LED (with a suitable 300Ω resistor) is connected to GPIO 5, the following
1003 session will result in the brightness of the LED being controlled by
1004 dialling the rotary encoder back and forth::
1006 >>> from gpiozero import RotaryEncoder
1007 >>> from gpiozero.tools import scaled_half
1008 >>> rotor = RotaryEncoder(21, 20)
1009 >>> led = PWMLED(5)
1010 >>> led.source = scaled_half(rotor.values)
1012 :type a: int or str
1013 :param a:
1014 The GPIO pin connected to the "A" output of the rotary encoder.
1016 :type b: int or str
1017 :param b:
1018 The GPIO pin connected to the "B" output of the rotary encoder.
1020 :type bounce_time: float or None
1021 :param bounce_time:
1022 If :data:`None` (the default), no software bounce compensation will be
1023 performed. Otherwise, this is the length of time (in seconds) that the
1024 component will ignore changes in state after an initial change.
1026 :type max_steps: int
1027 :param max_steps:
1028 The number of steps clockwise the encoder takes to change the
1029 :attr:`value` from 0 to 1, or counter-clockwise from 0 to -1.
1030 If this is 0, then the encoder's :attr:`value` never changes, but you
1031 can still read :attr:`steps` to determine the integer number of steps
1032 the encoder has moved clockwise or counter clockwise.
1034 :type threshold_steps: tuple of int
1035 :param threshold_steps:
1036 A (min, max) tuple of steps between which the device will be considered
1037 "active", inclusive. In other words, when :attr:`steps` is greater than
1038 or equal to the *min* value, and less than or equal the *max* value,
1039 the :attr:`active` property will be :data:`True` and the appropriate
1040 events (:attr:`when_activated`, :attr:`when_deactivated`) will be
1041 fired. Defaults to (0, 0).
1043 :type wrap: bool
1044 :param wrap:
1045 If :data:`True` and *max_steps* is non-zero, when the :attr:`steps`
1046 reaches positive or negative *max_steps* it wraps around by negation.
1047 Defaults to :data:`False`.
1049 :type pin_factory: Factory or None
1050 :param pin_factory:
1051 See :doc:`api_pins` for more information (this is an advanced feature
1052 which most users can ignore).
1054 .. _rotary encoder: https://en.wikipedia.org/wiki/Rotary_encoder
1055 """
1056 # The rotary encoder's two pins move through the following sequence when
1057 # the encoder is rotated one step clockwise:
1058 #
1059 # ────┐ ┌─────┐ ┌────────
1060 # _ │ │ │ │ counter ┌───┐
1061 # A │ │ │ │ clockwise ┌─── │ 0 │ ───┐ clockwise
1062 # └─────┘ └─────┘ (CCW) │ └───┘ │ (CW)
1063 # : : : : │ ┌───┐ ┌───┐ │
1064 # ───────┐ : ┌─────┐ : ┌───── ▾ │ 1 │ │ 2 │ ▾
1065 # _ : │ : │ : │ : │ └───┘ └───┘
1066 # B : │ : │ : │ : │ │ ┌───┐ │
1067 # : └─────┘ : └─────┘ └─── │ 3 │ ───┘
1068 # : : : : : : : : └───┘
1069 # 0 2 3 1 0 2 3 1 0
1070 #
1071 # Treating the A pin as a "high" bit, and the B pin as a "low" bit, this
1072 # means that the pins return the sequence 0, 2, 3, 1 for each step that the
1073 # encoder takes clockwise. Conversely, the pins return the sequence 0, 1,
1074 # 3, 2 for each step counter-clockwise.
1075 #
1076 # We can treat these values as edges to take in a simple state machine,
1077 # which is represented in the dictionary below:
1079 TRANSITIONS = {
1080 'idle': ['idle', 'ccw1', 'cw1', 'idle'],
1081 'ccw1': ['idle', 'ccw1', 'ccw3', 'ccw2'],
1082 'ccw2': ['idle', 'ccw1', 'ccw3', 'ccw2'],
1083 'ccw3': ['-1', 'idle', 'ccw3', 'ccw2'],
1084 'cw1': ['idle', 'cw3', 'cw1', 'cw2'],
1085 'cw2': ['idle', 'cw3', 'cw1', 'cw2'],
1086 'cw3': ['+1', 'cw3', 'idle', 'cw2'],
1087 }
1089 # The state machine here includes more than just the strictly necessary
1090 # edges; it also permits "wiggle" between intermediary states so that the
1091 # overall graph looks like this:
1092 #
1093 # ┌──────┐
1094 # │ │
1095 # ┌─────┤ idle ├────┐
1096 # │1 │ │ 2│
1097 # │ └──────┘ │
1098 # ▾ ▴ ▴ ▾
1099 # ┌────────┐ │ │ ┌───────┐
1100 # │ │ 0│ │0 │ │
1101 # ┌───┤ ccw1 ├──┤ ├──┤ cw1 ├───┐
1102 # │2 │ │ │ │ │ │ 1│
1103 # │ └─┬──────┘ │ │ └─────┬─┘ │
1104 # │ 3│ ▴ │ │ ▴ │3 │
1105 # │ ▾ │1 │ │ 2│ ▾ │
1106 # │ ┌──────┴─┐ │ │ ┌─┴─────┐ │
1107 # │ │ │ 0│ │0 │ │ │
1108 # │ │ ccw2 ├──┤ ├──┤ cw2 │ │
1109 # │ │ │ │ │ │ │ │
1110 # │ └─┬──────┘ │ │ └─────┬─┘ │
1111 # │ 2│ ▴ │ │ ▴ │1 │
1112 # │ ▾ │3 │ │ 3│ ▾ │
1113 # │ ┌──────┴─┐ │ │ ┌─┴─────┐ │
1114 # │ │ │ │ │ │ │ │
1115 # └──▸│ ccw3 │ │ │ │ cw3 │◂──┘
1116 # │ │ │ │ │ │
1117 # └───┬────┘ │ │ └───┬───┘
1118 # 0│ │ │ │0
1119 # ▾ │ │ ▾
1120 # ┌────────┐ │ │ ┌───────┐
1121 # │ │ │ │ │ │
1122 # │ -1 ├──┘ └──┤ +1 │
1123 # │ │ │ │
1124 # └────────┘ └───────┘
1125 #
1126 # Note that, once we start down the clockwise (cw) or counter-clockwise
1127 # (ccw) path, we don't allow the state to pick the alternate direction
1128 # without passing through the idle state again. This seems to work well in
1129 # practice with several encoders, even quite jiggly ones with no debounce
1130 # hardware or software
1132 def __init__(
1133 self, a, b, bounce_time=None, max_steps=16, threshold_steps=(0, 0),
1134 wrap=False, pin_factory=None):
1135 min_thresh, max_thresh = threshold_steps
1136 if max_thresh < min_thresh:
1137 raise ValueError('maximum threshold cannot be less than minimum')
1138 self._steps = 0
1139 self._max_steps = int(max_steps)
1140 self._threshold = (int(min_thresh), int(max_thresh))
1141 self._wrap = bool(wrap)
1142 self._state = 'idle'
1143 self._edge = 0
1144 self._when_rotated = None
1145 self._when_rotated_cw = None
1146 self._when_rotated_ccw = None
1147 self._rotate_event = Event()
1148 self._rotate_cw_event = Event()
1149 self._rotate_ccw_event = Event()
1150 super(RotaryEncoder, self).__init__(
1151 a=InputDevice(a, pull_up=True, pin_factory=pin_factory),
1152 b=InputDevice(b, pull_up=True, pin_factory=pin_factory),
1153 _order=('a', 'b'), pin_factory=pin_factory)
1154 self.a.pin.bounce_time = bounce_time
1155 self.b.pin.bounce_time = bounce_time
1156 self.a.pin.edges = 'both'
1157 self.b.pin.edges = 'both'
1158 self.a.pin.when_changed = self._a_changed
1159 self.b.pin.when_changed = self._b_changed
1160 # Call _fire_events once to set initial state of events
1161 self._fire_events(self.pin_factory.ticks(), self.is_active)
1163 def __repr__(self):
1164 try:
1165 self._check_open()
1166 return "<gpiozero.%s object on pins %r and %r>" % (
1167 self.__class__.__name__, self.a.pin, self.b.pin)
1168 except DeviceClosed:
1169 return super(RotaryEncoder, self).__repr__()
1171 def _a_changed(self, ticks, state):
1172 edge = (self.a._state_to_value(state) << 1) | (self._edge & 0x1)
1173 self._change_state(ticks, edge)
1175 def _b_changed(self, ticks, state):
1176 edge = (self._edge & 0x2) | self.b._state_to_value(state)
1177 self._change_state(ticks, edge)
1179 def _change_state(self, ticks, edge):
1180 self._edge = edge
1181 new_state = RotaryEncoder.TRANSITIONS[self._state][edge]
1182 if new_state == '+1':
1183 self._steps = (
1184 self._steps + 1
1185 if not self._max_steps or self._steps < self._max_steps else
1186 -self._max_steps if self._wrap else self._max_steps
1187 )
1188 self._rotate_cw_event.set()
1189 self._fire_rotated_cw()
1190 self._rotate_cw_event.clear()
1191 elif new_state == '-1':
1192 self._steps = (
1193 self._steps - 1
1194 if not self._max_steps or self._steps > -self._max_steps else
1195 self._max_steps if self._wrap else -self._max_steps
1196 )
1197 self._rotate_ccw_event.set()
1198 self._fire_rotated_ccw()
1199 self._rotate_ccw_event.clear()
1200 else:
1201 self._state = new_state
1202 return
1203 self._rotate_event.set()
1204 self._fire_rotated()
1205 self._rotate_event.clear()
1206 self._fire_events(ticks, self.is_active)
1207 self._state = 'idle'
1209 def wait_for_rotate(self, timeout=None):
1210 """
1211 Pause the script until the encoder is rotated at least one step in
1212 either direction, or the timeout is reached.
1214 :type timeout: float or None
1215 :param timeout:
1216 Number of seconds to wait before proceeding. If this is
1217 :data:`None` (the default), then wait indefinitely until the
1218 encoder is rotated.
1219 """
1220 return self._rotate_event.wait(timeout)
1222 def wait_for_rotate_clockwise(self, timeout=None):
1223 """
1224 Pause the script until the encoder is rotated at least one step
1225 clockwise, or the timeout is reached.
1227 :type timeout: float or None
1228 :param timeout:
1229 Number of seconds to wait before proceeding. If this is
1230 :data:`None` (the default), then wait indefinitely until the
1231 encoder is rotated clockwise.
1232 """
1233 return self._rotate_cw_event.wait(timeout)
1235 def wait_for_rotate_counter_clockwise(self, timeout=None):
1236 """
1237 Pause the script until the encoder is rotated at least one step
1238 counter-clockwise, or the timeout is reached.
1240 :type timeout: float or None
1241 :param timeout:
1242 Number of seconds to wait before proceeding. If this is
1243 :data:`None` (the default), then wait indefinitely until the
1244 encoder is rotated counter-clockwise.
1245 """
1246 return self._rotate_ccw_event.wait(timeout)
1248 when_rotated = event(
1249 """
1250 The function to be run when the encoder is rotated in either direction.
1252 This can be set to a function which accepts no (mandatory) parameters,
1253 or a Python function which accepts a single mandatory parameter (with
1254 as many optional parameters as you like). If the function accepts a
1255 single mandatory parameter, the device that activated will be passed
1256 as that parameter.
1258 Set this property to :data:`None` (the default) to disable the event.
1259 """)
1261 when_rotated_clockwise = event(
1262 """
1263 The function to be run when the encoder is rotated clockwise.
1265 This can be set to a function which accepts no (mandatory) parameters,
1266 or a Python function which accepts a single mandatory parameter (with
1267 as many optional parameters as you like). If the function accepts a
1268 single mandatory parameter, the device that activated will be passed
1269 as that parameter.
1271 Set this property to :data:`None` (the default) to disable the event.
1272 """)
1274 when_rotated_counter_clockwise = event(
1275 """
1276 The function to be run when the encoder is rotated counter-clockwise.
1278 This can be set to a function which accepts no (mandatory) parameters,
1279 or a Python function which accepts a single mandatory parameter (with
1280 as many optional parameters as you like). If the function accepts a
1281 single mandatory parameter, the device that activated will be passed
1282 as that parameter.
1284 Set this property to :data:`None` (the default) to disable the event.
1285 """)
1287 @property
1288 def steps(self):
1289 """
1290 The "steps" value of the encoder starts at 0. It increments by one for
1291 every step the encoder is rotated clockwise, and decrements by one for
1292 every step it is rotated counter-clockwise. The steps value is
1293 limited by :attr:`max_steps`. It will not advance beyond positive or
1294 negative :attr:`max_steps`, unless :attr:`wrap` is :data:`True` in
1295 which case it will roll around by negation. If :attr:`max_steps` is
1296 zero then steps are not limited at all, and will increase infinitely
1297 in either direction, but :attr:`value` will return a constant zero.
1299 Note that, in contrast to most other input devices, because the rotary
1300 encoder has no absolute position the :attr:`steps` attribute (and
1301 :attr:`value` by corollary) is writable.
1302 """
1303 return self._steps
1305 def _fire_rotated(self):
1306 if self.when_rotated:
1307 self.when_rotated()
1309 def _fire_rotated_cw(self):
1310 if self.when_rotated_clockwise:
1311 self.when_rotated_clockwise()
1313 def _fire_rotated_ccw(self):
1314 if self.when_rotated_counter_clockwise:
1315 self.when_rotated_counter_clockwise()
1317 @steps.setter
1318 def steps(self, value):
1319 value = int(value)
1320 if self._max_steps:
1321 value = max(-self._max_steps, min(self._max_steps, value))
1322 self._steps = value
1324 @property
1325 def value(self):
1326 """
1327 Represents the value of the rotary encoder as a value between -1 and 1.
1328 The value is calculated by dividing the value of :attr:`steps` into the
1329 range from negative :attr:`max_steps` to positive :attr:`max_steps`.
1331 Note that, in contrast to most other input devices, because the rotary
1332 encoder has no absolute position the :attr:`value` attribute is
1333 writable.
1334 """
1335 try:
1336 return self._steps / self._max_steps
1337 except ZeroDivisionError:
1338 return 0
1340 @value.setter
1341 def value(self, value):
1342 self._steps = int(max(-1, min(1, float(value))) * self._max_steps)
1344 @property
1345 def is_active(self):
1346 return self._threshold[0] <= self._steps <= self._threshold[1]
1348 @property
1349 def max_steps(self):
1350 """
1351 The number of discrete steps the rotary encoder takes to move
1352 :attr:`value` from 0 to 1 clockwise, or 0 to -1 counter-clockwise. In
1353 another sense, this is also the total number of discrete states this
1354 input can represent.
1355 """
1356 return self._max_steps
1358 @property
1359 def threshold_steps(self):
1360 """
1361 The mininum and maximum number of steps between which :attr:`is_active`
1362 will return :data:`True`. Defaults to (0, 0).
1363 """
1364 return self._threshold
1366 @property
1367 def wrap(self):
1368 """
1369 If :data:`True`, when :attr:`value` reaches its limit (-1 or 1), it
1370 "wraps around" to the opposite limit. When :data:`False`, the value
1371 (and the corresponding :attr:`steps` attribute) simply don't advance
1372 beyond their limits.
1373 """
1374 return self._wrap