From c5e02c1d73bd62385c78d14ba01bd6e5bc55a991 Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Wed, 7 May 2025 22:07:25 +0200 Subject: [PATCH] refactor config logic --- docs/dht22_sensor.md | 34 +++++++-------- examples/button_triggered_display.py | 41 ++++++++++-------- examples/dht22_example.py | 9 ++-- examples/oled_display_example.py | 28 ++++++------ src/esp_sensors/config.py | 52 ++++++++-------------- src/esp_sensors/dht22.py | 65 ++++++++++++++++++---------- src/esp_sensors/humidity.py | 18 +++++--- src/esp_sensors/oled_display.py | 32 ++++++++++---- src/esp_sensors/sensor.py | 35 ++++++++------- src/esp_sensors/temperature.py | 18 +++++--- src/main.py | 33 ++++++++------ tests/test_config.py | 65 +++++++++++----------------- tests/test_dht22_sensor.py | 34 ++++++--------- tests/test_oled_display.py | 19 ++++---- tests/test_temperature_sensor.py | 1 + 15 files changed, 259 insertions(+), 225 deletions(-) diff --git a/docs/dht22_sensor.md b/docs/dht22_sensor.md index 401f9af..15e2aba 100644 --- a/docs/dht22_sensor.md +++ b/docs/dht22_sensor.md @@ -62,28 +62,28 @@ import time # Initialize with custom parameters sensor = DHT22Sensor( - name="outdoor", - pin=5, - interval=30, # Read every 30 seconds - unit="F" # Use Fahrenheit + name="outdoor", + pin=5, + interval=30, # Read every 30 seconds + temperature_unit="F" # Use Fahrenheit ) # Continuous reading try: - while True: - temp = sensor.read() - humidity = sensor.read_humidity() - - # Get metadata - metadata = sensor.get_metadata() - - print(f"Sensor: {metadata['name']}") - print(f"Temperature: {temp}°F ({sensor.to_celsius()}°C)") - print(f"Humidity: {humidity}%") - - time.sleep(metadata['interval']) + while True: + temp = sensor.read() + humidity = sensor.read_humidity() + + # Get metadata + metadata = sensor.get_metadata() + + print(f"Sensor: {metadata['name']}") + print(f"Temperature: {temp}°F ({sensor.to_celsius()}°C)") + print(f"Humidity: {humidity}%") + + time.sleep(metadata['interval']) except KeyboardInterrupt: - print("Monitoring stopped") + print("Monitoring stopped") ``` ## API Reference diff --git a/examples/button_triggered_display.py b/examples/button_triggered_display.py index d1fa62f..c7cbbfc 100644 --- a/examples/button_triggered_display.py +++ b/examples/button_triggered_display.py @@ -7,21 +7,28 @@ This example demonstrates how to: 3. Wake up and read sensor data when the button is pressed 4. Display the data on an OLED screen """ + import time import sys import os # Add the src directory to the Python path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from src.esp_sensors.oled_display import OLEDDisplay from src.esp_sensors.dht22 import DHT22Sensor -from src.esp_sensors.config import load_config, get_sensor_config, get_display_config, get_button_config +from src.esp_sensors.config import ( + load_config, + get_sensor_config, + get_display_config, + get_button_config, +) # Import hardware-specific modules if available (for ESP32/ESP8266) try: from machine import Pin, deepsleep import esp32 + SIMULATION = False except ImportError: # Simulation mode for development on non-ESP hardware @@ -31,10 +38,12 @@ except ImportError: def simulate_button_press(): """Simulate a button press in simulation mode.""" - print("\nPress Enter to simulate a button press (or 'q' to quit, Ctrl+C to exit)...") + print( + "\nPress Enter to simulate a button press (or 'q' to quit, Ctrl+C to exit)..." + ) try: user_input = input() - if user_input.lower() == 'q': + if user_input.lower() == "q": return False return True except KeyboardInterrupt: @@ -49,16 +58,14 @@ def main(): config = load_config() # Initialize a DHT22 sensor using configuration - dht_sensor = DHT22Sensor( - config=config # Pass the loaded config - ) + dht_sensor = DHT22Sensor(sensor_config=config) # Pass the loaded config print(f"Initialized DHT22 sensor: {dht_sensor.name}, pin: {dht_sensor.pin}") # Initialize an OLED display using configuration - display = OLEDDisplay( - config=config # Pass the loaded config + display = OLEDDisplay(config=config) # Pass the loaded config + print( + f"Initialized OLED display: {display.name}, size: {display.width}x{display.height}" ) - print(f"Initialized OLED display: {display.name}, size: {display.width}x{display.height}") # Set up button using configuration button_config = get_button_config("main_button", config) @@ -88,7 +95,9 @@ def main(): # Go to light sleep mode to save power # Wake up on pin change (button press) print("Entering light sleep mode...") - esp32.wake_on_ext0(pin=button, level=0) # Wake on button press (low) + esp32.wake_on_ext0( + pin=button, level=0 + ) # Wake on button press (low) esp32.light_sleep() # Light sleep preserves RAM but saves power # When we get here, the button was pressed @@ -105,13 +114,9 @@ def main(): name_str = f"Sensor: {dht_sensor.name}" # Display values - display.display_values([ - name_str, - temp_str, - hum_str, - time_str, - "Press button again" - ]) + display.display_values( + [name_str, temp_str, hum_str, time_str, "Press button again"] + ) # Print to console print(f"Updated display with: {temp_str}, {hum_str}") diff --git a/examples/dht22_example.py b/examples/dht22_example.py index cc54d78..dbdbf03 100644 --- a/examples/dht22_example.py +++ b/examples/dht22_example.py @@ -9,15 +9,16 @@ Usage: - Ensure config.json is properly set up with DHT22 sensor configuration - The script will read temperature and humidity at the specified interval """ + import time import sys import os # Add the src directory to the Python path if needed -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) # Check if running on MicroPython -if sys.implementation.name == 'micropython': +if sys.implementation.name == "micropython": from src.esp_sensors.dht22 import DHT22Sensor from src.esp_sensors.config import load_config, get_sensor_config @@ -31,7 +32,7 @@ if sys.implementation.name == 'micropython': print(f"Sensor pin: {dht_config.get('pin')}") # Initialize the sensor using configuration - sensor = DHT22Sensor(config=config) + sensor = DHT22Sensor(sensor_config=config) print("Starting sensor readings. Press Ctrl+C to stop.") @@ -79,7 +80,7 @@ else: print(f"Sensor pin: {dht_config.get('pin')}") # Initialize the sensor using configuration - sensor = DHT22Sensor(config=config) + sensor = DHT22Sensor(sensor_config=config) print("Starting simulated sensor readings. Press Ctrl+C to stop.") diff --git a/examples/oled_display_example.py b/examples/oled_display_example.py index 7510747..f5571c0 100644 --- a/examples/oled_display_example.py +++ b/examples/oled_display_example.py @@ -2,12 +2,13 @@ Example usage of the OLED display with temperature and humidity sensors. This example demonstrates how to use the configuration system to initialize sensors and displays. """ + import time import sys import os # Add the src directory to the Python path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from src.esp_sensors.oled_display import OLEDDisplay from src.esp_sensors.dht22 import DHT22Sensor @@ -31,10 +32,12 @@ def main(): # Initialize a DHT22 sensor with configuration dht_sensor = DHT22Sensor( - sensor_type="dht22", # This will load config for this sensor type - config=config # Pass the loaded config + # This will load config for this sensor type + sensor_config=config, # Pass the loaded config + ) + print( + f"Created DHT22 sensor: {dht_sensor.name}, pin: {dht_sensor.pin}, interval: {dht_sensor.interval}s" ) - print(f"Created DHT22 sensor: {dht_sensor.name}, pin: {dht_sensor.pin}, interval: {dht_sensor.interval}s") # Method 2: Initialize with some parameters from config, others specified directly print("\nMethod 2: Override some config parameters") @@ -44,11 +47,12 @@ def main(): # These parameters will override the config values name="Custom Display", interval=1, # Update every second - # Other parameters will be loaded from config - config=config + config=config, + ) + print( + f"Created OLED display: {display.name}, size: {display.width}x{display.height}, interval: {display.interval}s" ) - print(f"Created OLED display: {display.name}, size: {display.width}x{display.height}, interval: {display.interval}s") # Display initialization message display.clear() @@ -72,13 +76,9 @@ def main(): name_str = f"Sensor: {dht_sensor.name}" # Display values - display.display_values([ - name_str, - temp_str, - hum_str, - time_str, - f"Demo ({i+1}/5)" - ]) + display.display_values( + [name_str, temp_str, hum_str, time_str, f"Demo ({i+1}/5)"] + ) # Print to console in simulation mode print(f"Updated display with: {temp_str}, {hum_str}") diff --git a/src/esp_sensors/config.py b/src/esp_sensors/config.py index e50fb35..7adece1 100644 --- a/src/esp_sensors/config.py +++ b/src/esp_sensors/config.py @@ -5,6 +5,7 @@ This module provides functionality to load and save configuration settings from/to a file, making it easy to change parameters like pins, display resolution, sensor names, and intervals without modifying the code. """ + import json import os from typing import Dict, Any, Optional, Union, List @@ -15,28 +16,12 @@ DEFAULT_CONFIG_PATH = "config.json" # Default configuration values DEFAULT_CONFIG = { "sensors": { - "temperature": { - "name": "Temperature Sensor", - "pin": 4, - "interval": 60, - "unit": "C" - }, - "humidity": { - "name": "Humidity Sensor", - "pin": 4, - "interval": 60 - }, "dht22": { "name": "DHT22 Sensor", "pin": 4, "interval": 60, - "temperature": { - "name": "DHT22 Temperature", - "unit": "C" - }, - "humidity": { - "name": "DHT22 Humidity" - } + "temperature": {"name": "DHT22 Temperature", "unit": "C"}, + "humidity": {"name": "DHT22 Humidity"}, } }, "displays": { @@ -47,15 +32,10 @@ DEFAULT_CONFIG = { "width": 128, "height": 64, "address": "0x3C", - "interval": 1 + "interval": 1, } }, - "buttons": { - "main_button": { - "pin": 0, - "pull_up": True - } - } + "buttons": {"main_button": {"pin": 0, "pull_up": True}}, } @@ -73,11 +53,13 @@ def load_config(config_path: str = DEFAULT_CONFIG_PATH) -> Dict[str, Any]: """ try: if os.path.exists(config_path): - with open(config_path, 'r') as f: + with open(config_path, "r") as f: config = json.load(f) return config else: - print(f"Configuration file {config_path} not found. Using default configuration.") + print( + f"Configuration file {config_path} not found. Using default configuration." + ) return DEFAULT_CONFIG except Exception as e: print(f"Error loading configuration: {e}. Using default configuration.") @@ -96,7 +78,7 @@ def save_config(config: Dict[str, Any], config_path: str = DEFAULT_CONFIG_PATH) True if the configuration was saved successfully, False otherwise """ try: - with open(config_path, 'w') as f: + with open(config_path, "w") as f: json.dump(config, f, indent=4) return True except Exception as e: @@ -104,7 +86,9 @@ def save_config(config: Dict[str, Any], config_path: str = DEFAULT_CONFIG_PATH) return False -def get_sensor_config(sensor_type: str, config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: +def get_sensor_config( + sensor_type: str, config: Optional[Dict[str, Any]] = None +) -> Dict[str, Any]: """ Get configuration for a specific sensor type. @@ -126,7 +110,9 @@ def get_sensor_config(sensor_type: str, config: Optional[Dict[str, Any]] = None) return sensor_config -def get_display_config(display_type: str, config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: +def get_display_config( + display_type: str, config: Optional[Dict[str, Any]] = None +) -> Dict[str, Any]: """ Get configuration for a specific display type. @@ -148,7 +134,9 @@ def get_display_config(display_type: str, config: Optional[Dict[str, Any]] = Non return display_config -def get_button_config(button_name: str = "main_button", config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: +def get_button_config( + button_name: str = "main_button", config: Optional[Dict[str, Any]] = None +) -> Dict[str, Any]: """ Get configuration for a specific button. @@ -170,8 +158,6 @@ def get_button_config(button_name: str = "main_button", config: Optional[Dict[st return button_config - - def create_default_config(config_path: str = DEFAULT_CONFIG_PATH) -> bool: """ Create a default configuration file. diff --git a/src/esp_sensors/dht22.py b/src/esp_sensors/dht22.py index 13aefe6..77b3946 100644 --- a/src/esp_sensors/dht22.py +++ b/src/esp_sensors/dht22.py @@ -1,14 +1,18 @@ """ DHT22 temperature and humidity sensor module for ESP32. """ + import time from typing import Dict, Any, Optional + try: import dht from machine import Pin + SIMULATION = False except ImportError: import random + SIMULATION = True from .temperature import TemperatureSensor @@ -19,8 +23,14 @@ from .config import get_sensor_config class DHT22Sensor(TemperatureSensor, HumiditySensor): """DHT22 temperature and humidity sensor implementation.""" - def __init__(self, name: str = None, pin: int = None, interval: int = None, - unit: str = None, sensor_type: str = "dht22", config: Dict[str, Any] = None): + def __init__( + self, + name: str = None, + pin: int = None, + interval: int = None, + temperature_unit: str = None, + sensor_config: Dict[str, Any] = None, + ): """ Initialize a new DHT22 sensor. @@ -28,38 +38,47 @@ class DHT22Sensor(TemperatureSensor, HumiditySensor): name: The name of the sensor (if None, loaded from config) pin: The GPIO pin number the sensor is connected to (if None, loaded from config) interval: Reading interval in seconds (if None, loaded from config) - unit: Temperature unit, either "C" or "F" (if None, loaded from config) - sensor_type: Type of the sensor for loading config (default: 'dht22') - config: Configuration dictionary (if provided, used instead of loading from file) + temperature_unit: Temperature unit, either "C" or "F" (if None, loaded from config) + sensor_config: Sensor-Configuration dictionary (if provided, used instead of loading from file) """ - # Load configuration - sensor_config = get_sensor_config(sensor_type, config) + if sensor_config is None: + sensor_config = {} - # Get main parameters from config if not provided - self.name = name if name is not None else sensor_config.get('name', 'DHT22 Sensor') - self.pin = pin if pin is not None else sensor_config.get('pin', 0) - self.interval = interval if interval is not None else sensor_config.get('interval', 60) + self.apply_parameters(interval, name, pin, sensor_config) - # Get temperature configuration - temp_config = sensor_config.get('temperature', {}) - temp_name = temp_config.get('name', self.name + ' Temperature') - temp_unit = unit if unit is not None else temp_config.get('unit', 'C') - - # Get humidity configuration - humidity_config = sensor_config.get('humidity', {}) - humidity_name = humidity_config.get('name', self.name + ' Humidity') + # Get sensor configurations + temp_config = sensor_config.get("temperature", {}) + humidity_config = sensor_config.get("humidity", {}) # Initialize both parent classes - TemperatureSensor.__init__(self, name=temp_name, pin=self.pin, interval=self.interval, unit=temp_unit) - HumiditySensor.__init__(self, name=humidity_name, pin=self.pin, interval=self.interval) + TemperatureSensor.__init__( + self, + pin=self.pin, + interval=self.interval, + sensor_config=temp_config, + unit=temperature_unit, + ) + HumiditySensor.__init__( + self, sensor_config=humidity_config, pin=self.pin, interval=self.interval + ) - # Store the original sensor name (it gets overwritten by HumiditySensor.__init__) - self.name = name if name is not None else sensor_config.get('name', 'DHT22 Sensor') + # Re-apply parameters to ensure they are not overridden by parent classes + self.apply_parameters(interval, name, pin, sensor_config) # Initialize the sensor if not in simulation mode if not SIMULATION: self._sensor = dht.DHT22(Pin(pin)) + def apply_parameters(self, interval, name, pin, sensor_config): + # Get main parameters from config if not provided + self.name = ( + name if name is not None else sensor_config.get("name", "DHT22 Sensor") + ) + self.pin = pin if pin is not None else sensor_config.get("pin", 0) + self.interval = ( + interval if interval is not None else sensor_config.get("interval", 60) + ) + def read_temperature(self) -> float: """ Read the current temperature. diff --git a/src/esp_sensors/humidity.py b/src/esp_sensors/humidity.py index a032166..713ab05 100644 --- a/src/esp_sensors/humidity.py +++ b/src/esp_sensors/humidity.py @@ -1,6 +1,7 @@ """ Humidity sensor module for ESP-based sensors. """ + import random from typing import Dict, Any, Optional from .sensor import Sensor @@ -10,8 +11,14 @@ from .config import get_sensor_config class HumiditySensor(Sensor): """Humidity sensor implementation.""" - def __init__(self, name: str = None, pin: int = None, interval: int = None, - sensor_type: str = None, config: Dict[str, Any] = None, **kwargs): + def __init__( + self, + name: str = None, + pin: int = None, + interval: int = None, + sensor_config: Dict[str, Any] = None, + **kwargs, + ): """ Initialize a new humidity sensor. @@ -19,12 +26,13 @@ class HumiditySensor(Sensor): name: The name of the sensor (if None, loaded from config) pin: The GPIO pin number the sensor is connected to (if None, loaded from config) interval: Reading interval in seconds (if None, loaded from config) - sensor_type: Type of the sensor for loading config (e.g., 'humidity') - config: Configuration dictionary (if provided, used instead of loading from file) + sensor_config: Sensor-Configuration dictionary (if provided, used instead of loading from file) **kwargs: Additional keyword arguments to pass to the parent class """ + if sensor_config is None: + sensor_config = {} # Initialize base class with sensor_type for configuration loading - super().__init__(name, pin, interval, sensor_type=sensor_type or "humidity", config=config) + super().__init__(name, pin, interval, sensor_config=sensor_config) self._last_humidity = None def read_humidity(self) -> float: diff --git a/src/esp_sensors/oled_display.py b/src/esp_sensors/oled_display.py index 21205c3..a6f26ac 100644 --- a/src/esp_sensors/oled_display.py +++ b/src/esp_sensors/oled_display.py @@ -1,11 +1,14 @@ """ OLED display module for ESP32 using SSD1306 controller. """ + import time from typing import Dict, Any, Optional + try: from machine import Pin, I2C import ssd1306 + SIMULATION = False except ImportError: SIMULATION = True @@ -17,9 +20,17 @@ from .config import get_display_config class OLEDDisplay(Sensor): """SSD1306 OLED display implementation.""" - def __init__(self, name: str = None, scl_pin: int = None, sda_pin: int = None, - width: int = None, height: int = None, address: str = None, - interval: int = None, config: Dict[str, Any] = None): + def __init__( + self, + name: str = None, + scl_pin: int = None, + sda_pin: int = None, + width: int = None, + height: int = None, + address: int | str = None, + interval: int = None, + display_config: Dict[str, Any] = None, + ): """ Initialize a new OLED display. @@ -31,21 +42,26 @@ class OLEDDisplay(Sensor): height: Display height in pixels (if None, loaded from config) address: I2C address of the display (if None, loaded from config) interval: Refresh interval in seconds (if None, loaded from config) - config: Configuration dictionary (if provided, used instead of loading from file) + display_config: Configuration dictionary """ - # Load configuration if needed - display_config = get_display_config("oled", config) + + if display_config is None: + display_config = {} # Get parameters from config if not provided name = name if name is not None else display_config.get("name", "OLED Display") sda_pin = sda_pin if sda_pin is not None else display_config.get("sda_pin", 21) - interval = interval if interval is not None else display_config.get("interval", 60) + interval = ( + interval if interval is not None else display_config.get("interval", 60) + ) # Initialize base class with the pin parameter super().__init__(name=name, pin=sda_pin, interval=interval) # Set display-specific parameters - self.scl_pin = scl_pin if scl_pin is not None else display_config.get("scl_pin", 22) + self.scl_pin = ( + scl_pin if scl_pin is not None else display_config.get("scl_pin", 22) + ) self.sda_pin = sda_pin # Already set above self.width = width if width is not None else display_config.get("width", 128) self.height = height if height is not None else display_config.get("height", 64) diff --git a/src/esp_sensors/sensor.py b/src/esp_sensors/sensor.py index ee28be4..9e72e92 100644 --- a/src/esp_sensors/sensor.py +++ b/src/esp_sensors/sensor.py @@ -1,6 +1,7 @@ """ Base sensor module for ESP-based sensors. """ + from typing import Dict, Any, Optional from .config import get_sensor_config @@ -8,8 +9,13 @@ from .config import get_sensor_config class Sensor: """Base class for all sensors.""" - def __init__(self, name: str = None, pin: int = None, interval: int = None, - sensor_type: str = None, config: Dict[str, Any] = None): + def __init__( + self, + name: str = None, + pin: int = None, + interval: int = None, + sensor_config: Dict[str, Any] = None, + ): """ Initialize a new sensor. @@ -17,22 +23,19 @@ class Sensor: name: The name of the sensor (if None, loaded from config) pin: The GPIO pin number the sensor is connected to (if None, loaded from config) interval: Reading interval in seconds (if None, loaded from config) - sensor_type: Type of the sensor for loading config (e.g., 'temperature') - config: Configuration dictionary (if provided, used instead of loading from file) + sensor_config: Sensor-Configuration dictionary (if provided, used instead of loading from file) """ - # Load configuration if sensor_type is provided - if sensor_type: - sensor_config = get_sensor_config(sensor_type, config) - # Use provided values or fall back to config values - self.name = name if name is not None else sensor_config.get('name', 'Unnamed Sensor') - self.pin = pin if pin is not None else sensor_config.get('pin', 0) - self.interval = interval if interval is not None else sensor_config.get('interval', 60) - else: - # Use provided values or defaults - self.name = name if name is not None else 'Unnamed Sensor' - self.pin = pin if pin is not None else 0 - self.interval = interval if interval is not None else 60 + if sensor_config is None: + sensor_config = {} + + self.name = ( + name if name is not None else sensor_config.get("name", "Unnamed Sensor") + ) + self.pin = pin if pin is not None else sensor_config.get("pin", 0) + self.interval = ( + interval if interval is not None else sensor_config.get("interval", 60) + ) self._last_reading: Optional[float] = None diff --git a/src/esp_sensors/temperature.py b/src/esp_sensors/temperature.py index df405fe..8fd7797 100644 --- a/src/esp_sensors/temperature.py +++ b/src/esp_sensors/temperature.py @@ -1,6 +1,7 @@ """ Temperature sensor module for ESP-based sensors. """ + import random from typing import Dict, Any, Optional from .sensor import Sensor @@ -10,8 +11,14 @@ from .config import get_sensor_config class TemperatureSensor(Sensor): """Temperature sensor implementation.""" - def __init__(self, name: str = None, pin: int = None, interval: int = None, - unit: str = None, config: Dict[str, Any] = None): + def __init__( + self, + name: str = None, + pin: int = None, + interval: int = None, + unit: str = None, + sensor_config: Dict[str, Any] = None, + ): """ Initialize a new temperature sensor. @@ -20,14 +27,15 @@ class TemperatureSensor(Sensor): pin: The GPIO pin number the sensor is connected to (if None, loaded from config) interval: Reading interval in seconds (if None, loaded from config) unit: Temperature unit, either "C" or "F" (if None, loaded from config) - config: Configuration dictionary (if provided, used instead of loading from file) + sensor_config: Sensor-Configuration dictionary (if provided, used instead of loading from file) """ + if sensor_config is None: + sensor_config = {} # Initialize base class with sensor_type for configuration loading - super().__init__(name, pin, interval, sensor_type="temperature", config=config) + super().__init__(name, pin, interval, sensor_config=sensor_config) # Load configuration if not provided in parameters if unit is None: - sensor_config = get_sensor_config("temperature", config) unit = sensor_config.get("unit", "C") # Validate unit diff --git a/src/main.py b/src/main.py index d606ad0..0c0a338 100644 --- a/src/main.py +++ b/src/main.py @@ -7,17 +7,24 @@ This program: 3. Wakes up and reads sensor data when the button is pressed 4. Displays the data on an OLED screen """ + import time import sys from esp_sensors.oled_display import OLEDDisplay from esp_sensors.dht22 import DHT22Sensor -from esp_sensors.config import load_config, get_button_config +from esp_sensors.config import ( + load_config, + get_button_config, + get_sensor_config, + get_display_config, +) # Import hardware-specific modules if available (for ESP32/ESP8266) try: from machine import Pin, deepsleep import esp32 + SIMULATION = False except ImportError: # Simulation mode for development on non-ESP hardware @@ -27,10 +34,12 @@ except ImportError: def simulate_button_press(): """Simulate a button press in simulation mode.""" - print("\nPress Enter to simulate a button press (or 'q' to quit, Ctrl+C to exit)...") + print( + "\nPress Enter to simulate a button press (or 'q' to quit, Ctrl+C to exit)..." + ) try: user_input = input() - if user_input.lower() == 'q': + if user_input.lower() == "q": return False return True except KeyboardInterrupt: @@ -47,12 +56,12 @@ def main(): # Initialize a DHT22 sensor using configuration dht_sensor = DHT22Sensor( - config=config # Pass the loaded config + sensor_config=get_sensor_config("dht22", config) # Pass the loaded config ) # Initialize an OLED display using configuration display = OLEDDisplay( - config=config # Pass the loaded config + display_config=get_display_config("oled", config) # Pass the loaded config ) # Set up button using configuration @@ -80,7 +89,9 @@ def main(): # Go to light sleep mode to save power # Wake up on pin change (button press) print("Entering light sleep mode...") - esp32.wake_on_ext0(pin=button, level=0) # Wake on button press (low) + esp32.wake_on_ext0( + pin=button, level=0 + ) # Wake on button press (low) esp32.light_sleep() # Light sleep preserves RAM but saves power # When we get here, the button was pressed @@ -97,13 +108,9 @@ def main(): name_str = f"Sensor: {dht_sensor.name}" # Display values - display.display_values([ - name_str, - temp_str, - hum_str, - time_str, - "Press button again" - ]) + display.display_values( + [name_str, temp_str, hum_str, time_str, "Press button again"] + ) # Print to console print(f"Updated display with: {temp_str}, {hum_str}") diff --git a/tests/test_config.py b/tests/test_config.py index 26f5310..24ec7f8 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,17 +1,18 @@ """ Tests for the configuration module. """ + import os import json import tempfile import pytest from src.esp_sensors.config import ( - load_config, - save_config, - get_sensor_config, + load_config, + save_config, + get_sensor_config, get_display_config, create_default_config, - DEFAULT_CONFIG + DEFAULT_CONFIG, ) @@ -19,7 +20,7 @@ def test_load_default_config(): """Test that default configuration is loaded when file doesn't exist.""" # Use a non-existent file path config = load_config("non_existent_file.json") - + # Check that the default configuration was loaded assert config == DEFAULT_CONFIG assert "sensors" in config @@ -31,32 +32,28 @@ def test_save_and_load_config(): # Create a temporary file with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as temp_file: temp_path = temp_file.name - + try: # Create a test configuration test_config = { "sensors": { - "test_sensor": { - "name": "Test Sensor", - "pin": 10, - "interval": 30 - } + "test_sensor": {"name": "Test Sensor", "pin": 10, "interval": 30} } } - + # Save the configuration result = save_config(test_config, temp_path) assert result is True - + # Load the configuration loaded_config = load_config(temp_path) - + # Check that the loaded configuration matches the saved one assert loaded_config == test_config assert loaded_config["sensors"]["test_sensor"]["name"] == "Test Sensor" assert loaded_config["sensors"]["test_sensor"]["pin"] == 10 assert loaded_config["sensors"]["test_sensor"]["interval"] == 30 - + finally: # Clean up the temporary file if os.path.exists(temp_path): @@ -67,21 +64,15 @@ def test_get_sensor_config(): """Test getting sensor configuration.""" # Create a test configuration test_config = { - "sensors": { - "test_sensor": { - "name": "Test Sensor", - "pin": 10, - "interval": 30 - } - } + "sensors": {"test_sensor": {"name": "Test Sensor", "pin": 10, "interval": 30}} } - + # Get configuration for an existing sensor sensor_config = get_sensor_config("test_sensor", test_config) assert sensor_config["name"] == "Test Sensor" assert sensor_config["pin"] == 10 assert sensor_config["interval"] == 30 - + # Get configuration for a non-existent sensor (should return default or empty dict) non_existent_config = get_sensor_config("non_existent", test_config) assert isinstance(non_existent_config, dict) @@ -92,20 +83,16 @@ def test_get_display_config(): # Create a test configuration test_config = { "displays": { - "test_display": { - "name": "Test Display", - "width": 64, - "height": 32 - } + "test_display": {"name": "Test Display", "width": 64, "height": 32} } } - + # Get configuration for an existing display display_config = get_display_config("test_display", test_config) assert display_config["name"] == "Test Display" assert display_config["width"] == 64 assert display_config["height"] == 32 - + # Get configuration for a non-existent display (should return default or empty dict) non_existent_config = get_display_config("non_existent", test_config) assert isinstance(non_existent_config, dict) @@ -116,25 +103,25 @@ def test_create_default_config(): # Create a temporary file path with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as temp_file: temp_path = temp_file.name - + try: # Remove the file (we just want the path) os.unlink(temp_path) - + # Create the default configuration result = create_default_config(temp_path) assert result is True - + # Check that the file exists assert os.path.exists(temp_path) - + # Load the configuration and check it matches the default - with open(temp_path, 'r') as f: + with open(temp_path, "r") as f: config = json.load(f) - + assert config == DEFAULT_CONFIG - + finally: # Clean up the temporary file if os.path.exists(temp_path): - os.unlink(temp_path) \ No newline at end of file + os.unlink(temp_path) diff --git a/tests/test_dht22_sensor.py b/tests/test_dht22_sensor.py index 0058a29..6e77531 100644 --- a/tests/test_dht22_sensor.py +++ b/tests/test_dht22_sensor.py @@ -1,6 +1,7 @@ """ Tests for the DHT22 sensor module. """ + import pytest from src.esp_sensors.dht22 import DHT22Sensor from src.esp_sensors.config import get_sensor_config @@ -9,7 +10,7 @@ from src.esp_sensors.config import get_sensor_config def test_dht22_sensor_initialization(): """Test that a DHT22 sensor can be initialized with valid parameters.""" # Test direct parameter initialization - sensor = DHT22Sensor(name="test_sensor", pin=5, interval=30, unit="C") + sensor = DHT22Sensor(name="test_sensor", pin=5, interval=30, temperature_unit="C") assert sensor.name == "test_sensor" assert sensor.pin == 5 assert sensor.interval == 30 @@ -18,23 +19,14 @@ def test_dht22_sensor_initialization(): # Test initialization with custom config test_config = { - "sensors": { - "dht22": { - "name": "config_sensor", - "pin": 6, - "interval": 40, - "temperature": { - "name": "Config Temperature", - "unit": "F" - }, - "humidity": { - "name": "Config Humidity" - } - } - } + "name": "config_sensor", + "pin": 6, + "interval": 40, + "temperature": {"name": "Config Temperature", "unit": "F"}, + "humidity": {"name": "Config Humidity"}, } - config_sensor = DHT22Sensor(config=test_config) + config_sensor = DHT22Sensor(sensor_config=test_config) assert config_sensor.name == "config_sensor" assert config_sensor.pin == 6 assert config_sensor.interval == 40 @@ -45,7 +37,7 @@ def test_dht22_sensor_initialization(): def test_dht22_sensor_invalid_unit(): """Test that initializing with an invalid unit raises a ValueError.""" with pytest.raises(ValueError): - DHT22Sensor(name="test_sensor", pin=5, interval=30, unit="K") + DHT22Sensor(name="test_sensor", pin=5, interval=30, temperature_unit="K") def test_dht22_sensor_read_temperature(): @@ -68,7 +60,7 @@ def test_dht22_sensor_read_humidity(): def test_dht22_sensor_fahrenheit(): """Test that a sensor initialized with Fahrenheit returns appropriate values.""" - sensor = DHT22Sensor(name="test_sensor", pin=5, unit="F") + sensor = DHT22Sensor(name="test_sensor", pin=5, temperature_unit="F") reading = sensor.read() assert isinstance(reading, float) # For Fahrenheit, the reading should be between 59.0 and 86.0 @@ -78,13 +70,13 @@ def test_dht22_sensor_fahrenheit(): def test_dht22_temperature_conversion(): """Test temperature conversion methods.""" # Test Celsius to Fahrenheit - c_sensor = DHT22Sensor(name="celsius_sensor", pin=5, unit="C") + c_sensor = DHT22Sensor(name="celsius_sensor", pin=5, temperature_unit="C") c_sensor._last_reading = 20.0 # Manually set for testing f_value = c_sensor.to_fahrenheit() assert f_value == 68.0 # 20°C = 68°F # Test Fahrenheit to Celsius - f_sensor = DHT22Sensor(name="fahrenheit_sensor", pin=5, unit="F") + f_sensor = DHT22Sensor(name="fahrenheit_sensor", pin=5, temperature_unit="F") f_sensor._last_reading = 68.0 # Manually set for testing c_value = f_sensor.to_celsius() assert c_value == 20.0 # 68°F = 20°C @@ -92,7 +84,7 @@ def test_dht22_temperature_conversion(): def test_dht22_metadata(): """Test that metadata includes the temperature unit, humidity, and type.""" - sensor = DHT22Sensor(name="test_sensor", pin=5, unit="C") + sensor = DHT22Sensor(name="test_sensor", pin=5, temperature_unit="C") metadata = sensor.get_metadata() assert metadata["name"] == "test_sensor" assert metadata["pin"] == 5 diff --git a/tests/test_oled_display.py b/tests/test_oled_display.py index 16b766b..5473c74 100644 --- a/tests/test_oled_display.py +++ b/tests/test_oled_display.py @@ -1,6 +1,7 @@ """ Tests for the OLED display module. """ + import pytest from src.esp_sensors.oled_display import OLEDDisplay @@ -15,20 +16,20 @@ def test_oled_display_initialization(): assert display.width == 128 assert display.height == 64 assert display.address == 0x3C - assert display.interval == 1 # Default interval is now 1 in the configuration + assert display.interval == 60 assert display._values == [] def test_oled_display_custom_parameters(): """Test that an OLED display can be initialized with custom parameters.""" display = OLEDDisplay( - "custom_display", - scl_pin=22, - sda_pin=21, - width=64, - height=32, - address=0x3D, - interval=30 + "custom_display", + scl_pin=22, + sda_pin=21, + width=64, + height=32, + address=0x3D, + interval=30, ) assert display.name == "custom_display" assert display.scl_pin == 22 @@ -57,7 +58,7 @@ def test_oled_display_metadata(): assert metadata["width"] == 128 assert metadata["height"] == 64 assert metadata["address"] == 0x3C - assert metadata["interval"] == 1 # Default interval is now 1 in the configuration + assert metadata["interval"] == 60 assert metadata["type"] == "SSD1306" assert metadata["values_count"] == 0 diff --git a/tests/test_temperature_sensor.py b/tests/test_temperature_sensor.py index 5cbf74b..6ed4446 100644 --- a/tests/test_temperature_sensor.py +++ b/tests/test_temperature_sensor.py @@ -1,6 +1,7 @@ """ Tests for the temperature sensor module. """ + import pytest from src.esp_sensors.temperature import TemperatureSensor