From acf4cc6dd5abbb2cbc2d5f39f4c15b1372d2f2c6 Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Sat, 7 Jun 2025 16:11:40 +0200 Subject: [PATCH] feat: implement simulation mode for DHT22 sensor and add dummy sensor for testing --- src/esp_sensors/dht22.py | 42 ++++++++++++++++----------------- src/esp_sensors/dummy_sensor.py | 37 +++++++++++++++++++++++++++++ src/esp_sensors/humidity.py | 6 ++--- src/esp_sensors/mqtt.py | 1 - src/esp_sensors/temperature.py | 9 ++----- src/main.py | 42 --------------------------------- tests/test_config_update.py | 1 + 7 files changed, 63 insertions(+), 75 deletions(-) create mode 100644 src/esp_sensors/dummy_sensor.py diff --git a/src/esp_sensors/dht22.py b/src/esp_sensors/dht22.py index 042aa84..bf34210 100644 --- a/src/esp_sensors/dht22.py +++ b/src/esp_sensors/dht22.py @@ -7,14 +7,11 @@ try: from machine import Pin SIMULATION = False -except ImportError: - import random - - SIMULATION = True +except ModuleNotFoundError: + SIMULATION = True # We're in a test environment from .temperature import TemperatureSensor from .humidity import HumiditySensor -from .config import get_sensor_config class DHT22Sensor(TemperatureSensor, HumiditySensor): @@ -68,6 +65,9 @@ class DHT22Sensor(TemperatureSensor, HumiditySensor): pin1 = Pin(self.pin) self._sensor = dht.DHT22(pin1) print(f"DHT22 sensor initialized on pin {pin1}") + else: + print("Initializing DHT22 sensor in simulation mode...") + self._sensor = None def apply_parameters(self, interval, name, pin, sensor_config): # Get main parameters from config if not provided @@ -107,7 +107,7 @@ class DHT22Sensor(TemperatureSensor, HumiditySensor): self._last_reading = round(temp, 1) # Also read humidity while we're at it - self._last_humidity = round(self._sensor.humidity(), 1) + self._last_humidity = self._sensor.humidity() except Exception as e: print(f"Error reading DHT22 sensor: {e}") # Return last reading if available, otherwise default value @@ -116,7 +116,7 @@ class DHT22Sensor(TemperatureSensor, HumiditySensor): if self._last_humidity is None: self._last_humidity = 0.0 - return self._last_reading + return self._last_reading def read(self) -> float: """ @@ -134,20 +134,20 @@ class DHT22Sensor(TemperatureSensor, HumiditySensor): Returns: The humidity reading as a float (percentage) """ - # If we haven't read yet, read only humidity - if self._last_humidity is None: - if SIMULATION: - # Use parent class simulation - return super().read_humidity() - else: - # Actual hardware reading - try: - self._sensor.measure() - self._last_humidity = round(self._sensor.humidity(), 1) - except Exception as e: - print(f"Error reading DHT22 humidity: {e}") - # Return default value if no previous reading - self._last_humidity = 0.0 + + if SIMULATION: + # Use parent class simulation + return super().read_humidity() + else: + # Actual hardware reading + try: + self._sensor.measure() + self._last_humidity = self._sensor.humidity() + except Exception as e: + print(f"Error reading DHT22 humidity: {e}") + # Return default value if no previous reading + self._last_humidity = 0.0 + return self._last_humidity def get_metadata(self): diff --git a/src/esp_sensors/dummy_sensor.py b/src/esp_sensors/dummy_sensor.py new file mode 100644 index 0000000..c6be907 --- /dev/null +++ b/src/esp_sensors/dummy_sensor.py @@ -0,0 +1,37 @@ +try: + import machine + + SIMULATION = False +except ModuleNotFoundError: + SIMULATION = True # We're in a test environment + import random # For generating random values in tests + + +def read_dummy(name: str, unit: str) -> float: + """ + Dummy function to simulate reading a sensor value. + + Args: + name: The name of the sensor (e.g., "temperature", "humidity") + unit: The unit of the data to generate (one of ['F', 'C', '%']) + + Returns: + A simulated sensor reading as a float + """ + + if SIMULATION: + # Simulation mode - generate random values for testing + if unit == "F": + # Simulate a temperature reading in Fahrenheit + return round(random.uniform(59.0, 86.0), 1) + elif unit == "%": + # Simulate a humidity reading in percentage + return round(random.uniform(30.0, 90.0), 1) + elif unit == "C": + return round(random.uniform(15.0, 30.0), 1) + else: + raise ValueError(f"Unsupported unit for dummy sensor: {unit}") + else: + # This method should be overridden by subclasses to implement + # actual temperature reading from hardware + raise NotImplementedError(f"Subclasses must implement read_{name}()") diff --git a/src/esp_sensors/humidity.py b/src/esp_sensors/humidity.py index fcc67c7..2355cca 100644 --- a/src/esp_sensors/humidity.py +++ b/src/esp_sensors/humidity.py @@ -2,7 +2,7 @@ Humidity sensor module for ESP-based sensors. """ -import random +from .dummy_sensor import read_dummy # Dummy sensor for simulation purposes from .sensor import Sensor from .config import get_sensor_config @@ -41,9 +41,7 @@ class HumiditySensor(Sensor): Returns: The humidity reading as a float (percentage) """ - # This is a simulation for testing purposes - # In a real implementation, this would read from the actual sensor - self._last_humidity = round(random.uniform(30.0, 90.0), 1) + self._last_humidity = read_dummy("humidity", unit="%") return self._last_humidity def get_metadata(self): diff --git a/src/esp_sensors/mqtt.py b/src/esp_sensors/mqtt.py index 69bf418..25ff275 100644 --- a/src/esp_sensors/mqtt.py +++ b/src/esp_sensors/mqtt.py @@ -2,7 +2,6 @@ MQTT module for ESP sensors. This module provides functionality to connect to an MQTT broker and publish sensor data. -It supports both real hardware and simulation mode. This module uses the MQTTClient class from mqtt_client.py for the core MQTT implementation. """ diff --git a/src/esp_sensors/temperature.py b/src/esp_sensors/temperature.py index 0160a34..b12de1a 100644 --- a/src/esp_sensors/temperature.py +++ b/src/esp_sensors/temperature.py @@ -2,7 +2,7 @@ Temperature sensor module for ESP-based sensors. """ -import random +from .dummy_sensor import read_dummy # Dummy sensor for simulation purposes from .sensor import Sensor from .config import get_sensor_config @@ -50,12 +50,7 @@ class TemperatureSensor(Sensor): Returns: The temperature reading as a float """ - # This is a simulation for testing purposes - # In a real implementation, this would read from the actual sensor - if self.unit == "C": - self._last_reading = round(random.uniform(15.0, 30.0), 1) - else: - self._last_reading = round(random.uniform(59.0, 86.0), 1) + self._last_reading = read_dummy("temperature", unit=self.unit) return self._last_reading def read(self) -> float: diff --git a/src/main.py b/src/main.py index 66516cc..8402cbb 100644 --- a/src/main.py +++ b/src/main.py @@ -26,48 +26,6 @@ from machine import Pin, deepsleep import esp32 -def simulate_button_press(timeout=None): - """ - Simulate a button press in simulation mode. - - Args: - timeout: Time in seconds to wait for input before returning. - If None, wait indefinitely. - - Returns: - True if button was pressed or timeout occurred, False to exit - """ - import select - import sys - - if timeout is not None: - print( - f"\nPress Enter to simulate a button press (or 'q' to quit, Ctrl+C to exit)..." - f"\nWill automatically continue in {timeout} seconds..." - ) - else: - print( - "\nPress Enter to simulate a button press (or 'q' to quit, Ctrl+C to exit)..." - ) - - try: - # Set up select to monitor stdin with timeout - if timeout is not None: - rlist, _, _ = select.select([sys.stdin], [], [], timeout) - if not rlist: - # Timeout occurred, no input - print("Timeout reached, continuing automatically...") - return True - - # If we get here, either there was input or timeout was None - user_input = input() - if user_input.lower() == "q": - return False - return True - except KeyboardInterrupt: - return False - - def main(): """ Main function to demonstrate button-triggered sensor display with MQTT publishing. diff --git a/tests/test_config_update.py b/tests/test_config_update.py index f911373..db673c0 100644 --- a/tests/test_config_update.py +++ b/tests/test_config_update.py @@ -27,6 +27,7 @@ def mqtt_config(): "publish_interval": 30, "ssl": False, "keepalive": 60, + "config_wait_time": 5.0, "use_esp32_client": True, }