mirror of
				https://github.com/KevinMidboe/python-gpiozero.git
				synced 2025-10-29 17:50:37 +00:00 
			
		
		
		
	Lots of fixes
* Move `is_active` to `GPIODevice`; it's equally applicable to inputs and outputs so there's no point having it just in inputs * Flip the pull-up status for `MotionSensor` (it was backwards leading to reversed readings from the sensor) * Add a `threshold` to `MotionSensor` (optional), and `value` (similar to `LightSensor`) * Also expose `pull_up` as a simple bool property * Rejig `LightSensor` so it also derives from `InputDevice` (it inherits enough to make it worthwhile) and so that its API is similar to `MotionSensor` (a `value` property with a `*_detected` property, and a background threaded queue which constantly monitors values)
This commit is contained in:
		| @@ -12,11 +12,17 @@ class GPIODevice(object): | ||||
|         if pin is None: | ||||
|             raise GPIODeviceError('No GPIO pin number given') | ||||
|         self._pin = pin | ||||
|         self._active_state = 1 | ||||
|         self._inactive_state = 0 | ||||
|  | ||||
|     @property | ||||
|     def pin(self): | ||||
|         return self._pin | ||||
|  | ||||
|     @property | ||||
|     def is_active(self): | ||||
|         return GPIO.input(self.pin) == self._active_state | ||||
|  | ||||
|  | ||||
| _GPIO_THREADS = set() | ||||
| def _gpio_threads_shutdown(): | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| from __future__ import division | ||||
|  | ||||
| from time import sleep | ||||
| from time import sleep, time | ||||
| from threading import Event | ||||
| from collections import deque | ||||
|  | ||||
| @@ -15,17 +15,18 @@ class InputDeviceError(GPIODeviceError): | ||||
|  | ||||
|  | ||||
| class InputDevice(GPIODevice): | ||||
|     def __init__(self, pin=None): | ||||
|     def __init__(self, pin=None, pull_up=True): | ||||
|         super(InputDevice, self).__init__(pin) | ||||
|         self._pull = GPIO.PUD_UP | ||||
|         self._edge = GPIO.FALLING | ||||
|         self._active_state = 0 | ||||
|         self._inactive_state = 1 | ||||
|         GPIO.setup(pin, GPIO.IN, self._pull) | ||||
|         self._pull_up = pull_up | ||||
|         self._edge = (GPIO.RISING, GPIO.FALLING)[pull_up] | ||||
|         if pull_up: | ||||
|             self._active_state = 0 | ||||
|             self._inactive_state = 1 | ||||
|         GPIO.setup(pin, GPIO.IN, (GPIO.PUD_DOWN, GPIO.PUD_UP)[pull_up]) | ||||
|  | ||||
|     @property | ||||
|     def is_active(self): | ||||
|         return GPIO.input(self.pin) == self._active_state | ||||
|     def pull_up(self): | ||||
|         return self._pull_up | ||||
|  | ||||
|     def wait_for_input(self): | ||||
|         GPIO.wait_for_edge(self.pin, self._edge) | ||||
| @@ -44,9 +45,14 @@ class Button(InputDevice): | ||||
|  | ||||
|  | ||||
| class MotionSensor(InputDevice): | ||||
|     def __init__(self, pin=None, queue_len=20, sample_rate=10, partial=False): | ||||
|         super(MotionSensor, self).__init__(pin) | ||||
|     def __init__( | ||||
|             self, pin=None, queue_len=5, sample_rate=10, threshold=0.5, | ||||
|             partial=False): | ||||
|         super(MotionSensor, self).__init__(pin, pull_up=False) | ||||
|         if queue_len < 1: | ||||
|             raise InputDeviceError('queue_len must be at least one') | ||||
|         self.sample_rate = sample_rate | ||||
|         self.threshold = threshold | ||||
|         self.partial = partial | ||||
|         self._queue = deque(maxlen=queue_len) | ||||
|         self._queue_full = Event() | ||||
| @@ -57,6 +63,36 @@ class MotionSensor(InputDevice): | ||||
|     def queue_len(self): | ||||
|         return self._queue.maxlen | ||||
|  | ||||
|     @property | ||||
|     def value(self): | ||||
|         if not self.partial: | ||||
|             self._queue_full.wait() | ||||
|         try: | ||||
|             return sum(self._queue) / len(self._queue) | ||||
|         except ZeroDivisionError: | ||||
|             # No data == no motion | ||||
|             return 0.0 | ||||
|  | ||||
|     @property | ||||
|     def motion_detected(self): | ||||
|         return self.value > self.threshold | ||||
|  | ||||
|     def _get_sample_rate(self): | ||||
|         return self._sample_rate | ||||
|     def _set_sample_rate(self, value): | ||||
|         if value <= 0: | ||||
|             raise InputDeviceError('sample_rate must be greater than zero') | ||||
|         self._sample_rate = value | ||||
|     sample_rate = property(_get_sample_rate, _set_sample_rate) | ||||
|  | ||||
|     def _get_threshold(self): | ||||
|         return self._threshold | ||||
|     def _set_threshold(self, value): | ||||
|         if value < 0: | ||||
|             raise InputDeviceError('threshold must be zero or more') | ||||
|         self._threshold = value | ||||
|     threshold = property(_get_threshold, _set_threshold) | ||||
|  | ||||
|     def _fill_queue(self): | ||||
|         while ( | ||||
|                 not self._queue_thread.stopping.wait(1 / self.sample_rate) and | ||||
| @@ -67,53 +103,83 @@ class MotionSensor(InputDevice): | ||||
|         while not self._queue_thread.stopping.wait(1 / self.sample_rate): | ||||
|             self._queue.append(self.is_active) | ||||
|  | ||||
|     @property | ||||
|     def motion_detected(self): | ||||
|         if not self.partial: | ||||
|             self._queue_full.wait() | ||||
|         return sum(self._queue) > (len(self._queue) / 2) | ||||
|  | ||||
|  | ||||
| class LightSensor(object): | ||||
|     def __init__(self, pin=None, darkness_level=0.01): | ||||
|         if pin is None: | ||||
|             raise InputDeviceError('No GPIO pin number given') | ||||
|  | ||||
|         self._pin = pin | ||||
|         self.darkness_level = darkness_level | ||||
| class LightSensor(InputDevice): | ||||
|     def __init__( | ||||
|             self, pin=None, queue_len=5, darkness_time=0.01, | ||||
|             threshold=0.1, partial=False): | ||||
|         super(LightSensor, self).__init__(pin, pull_up=False) | ||||
|         if queue_len < 1: | ||||
|             raise InputDeviceError('queue_len must be at least one') | ||||
|         self.darkness_time = darkness_time | ||||
|         self.threshold = threshold | ||||
|         self.partial = partial | ||||
|         self._charged = Event() | ||||
|         GPIO.add_event_detect(self.pin, GPIO.RISING, lambda channel: self._charged.set()) | ||||
|         self._queue = deque(maxlen=queue_len) | ||||
|         self._queue_full = Event() | ||||
|         self._queue_thread = GPIOThread(target=self._fill_queue) | ||||
|         self._queue_thread.start() | ||||
|  | ||||
|     @property | ||||
|     def pin(self): | ||||
|         return self._pin | ||||
|     def queue_len(self): | ||||
|         return self._queue.maxlen | ||||
|  | ||||
|     @property | ||||
|     def value(self): | ||||
|         return self._get_average_light_level(5) | ||||
|         if not self.partial: | ||||
|             self._queue_full.wait() | ||||
|         try: | ||||
|             return 1.0 - (sum(self._queue) / len(self._queue)) / self.darkness_time | ||||
|         except ZeroDivisionError: | ||||
|             # No data == no light | ||||
|             return 0.0 | ||||
|  | ||||
|     def _get_light_level(self): | ||||
|         time_taken = self._time_charging_light_capacitor() | ||||
|         value = 100 * time_taken / self.darkness_level | ||||
|         return 100 - value | ||||
|     @property | ||||
|     def light_detected(self): | ||||
|         return self.value > self.threshold | ||||
|  | ||||
|     def _time_charging_light_capacitor(self): | ||||
|     def _get_darkness_time(self): | ||||
|         return self._darkness_time | ||||
|     def _set_darkness_time(self, value): | ||||
|         if value <= 0.0: | ||||
|             raise InputDeviceError('darkness_time must be greater than zero') | ||||
|         self._darkness_time = value | ||||
|         # XXX Empty the queue and restart the thread | ||||
|     darkness_time = property(_get_darkness_time, _set_darkness_time) | ||||
|  | ||||
|     def _get_threshold(self): | ||||
|         return self._threshold | ||||
|     def _set_threshold(self, value): | ||||
|         if value < 0: | ||||
|             raise InputDeviceError('threshold must be zero or more') | ||||
|         self._threshold = value | ||||
|     threshold = property(_get_threshold, _set_threshold) | ||||
|  | ||||
|     def _fill_queue(self): | ||||
|         try: | ||||
|             while ( | ||||
|                     not self._queue_thread.stopping.is_set() and | ||||
|                     len(self._queue) < self._queue.maxlen | ||||
|                 ): | ||||
|                 self._queue.append(self._time_charging()) | ||||
|             self._queue_full.set() | ||||
|             while not self._queue_thread.stopping.is_set(): | ||||
|                 self._queue.append(self._time_charging()) | ||||
|         finally: | ||||
|             GPIO.remove_event_detect(self.pin) | ||||
|  | ||||
|     def _time_charging(self): | ||||
|         # Drain charge from the capacitor | ||||
|         GPIO.setup(self.pin, GPIO.OUT) | ||||
|         GPIO.output(self.pin, GPIO.LOW) | ||||
|         sleep(0.1) | ||||
|         # Time the charging of the capacitor | ||||
|         start = time() | ||||
|         self._charged.clear() | ||||
|         GPIO.setup(self.pin, GPIO.IN) | ||||
|         start_time = time() | ||||
|         end_time = time() | ||||
|         while ( | ||||
|             GPIO.input(self.pin) == GPIO.LOW and | ||||
|             time() - start_time < self.darkness_level | ||||
|         ): | ||||
|             end_time = time() | ||||
|         time_taken = end_time - start_time | ||||
|         return min(time_taken, self.darkness_level) | ||||
|  | ||||
|     def _get_average_light_level(self, num): | ||||
|         values = [self._get_light_level() for n in range(num)] | ||||
|         average_value = sum(values) / len(values) | ||||
|         return average_value | ||||
|         self._charged.wait(self.darkness_time) | ||||
|         return min(self.darkness_time, time() - start) | ||||
|  | ||||
|  | ||||
| class TemperatureSensor(W1ThermSensor): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user