refactor config logic

This commit is contained in:
OMGeeky
2025-05-07 22:07:25 +02:00
parent 0fb0566adc
commit c5e02c1d73
15 changed files with 259 additions and 225 deletions

View File

@@ -62,28 +62,28 @@ import time
# Initialize with custom parameters # Initialize with custom parameters
sensor = DHT22Sensor( sensor = DHT22Sensor(
name="outdoor", name="outdoor",
pin=5, pin=5,
interval=30, # Read every 30 seconds interval=30, # Read every 30 seconds
unit="F" # Use Fahrenheit temperature_unit="F" # Use Fahrenheit
) )
# Continuous reading # Continuous reading
try: try:
while True: while True:
temp = sensor.read() temp = sensor.read()
humidity = sensor.read_humidity() humidity = sensor.read_humidity()
# Get metadata # Get metadata
metadata = sensor.get_metadata() metadata = sensor.get_metadata()
print(f"Sensor: {metadata['name']}") print(f"Sensor: {metadata['name']}")
print(f"Temperature: {temp}°F ({sensor.to_celsius()}°C)") print(f"Temperature: {temp}°F ({sensor.to_celsius()}°C)")
print(f"Humidity: {humidity}%") print(f"Humidity: {humidity}%")
time.sleep(metadata['interval']) time.sleep(metadata['interval'])
except KeyboardInterrupt: except KeyboardInterrupt:
print("Monitoring stopped") print("Monitoring stopped")
``` ```
## API Reference ## API Reference

View File

@@ -7,21 +7,28 @@ This example demonstrates how to:
3. Wake up and read sensor data when the button is pressed 3. Wake up and read sensor data when the button is pressed
4. Display the data on an OLED screen 4. Display the data on an OLED screen
""" """
import time import time
import sys import sys
import os import os
# Add the src directory to the Python path # 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.oled_display import OLEDDisplay
from src.esp_sensors.dht22 import DHT22Sensor 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) # Import hardware-specific modules if available (for ESP32/ESP8266)
try: try:
from machine import Pin, deepsleep from machine import Pin, deepsleep
import esp32 import esp32
SIMULATION = False SIMULATION = False
except ImportError: except ImportError:
# Simulation mode for development on non-ESP hardware # Simulation mode for development on non-ESP hardware
@@ -31,10 +38,12 @@ except ImportError:
def simulate_button_press(): def simulate_button_press():
"""Simulate a button press in simulation mode.""" """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: try:
user_input = input() user_input = input()
if user_input.lower() == 'q': if user_input.lower() == "q":
return False return False
return True return True
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -49,16 +58,14 @@ def main():
config = load_config() config = load_config()
# Initialize a DHT22 sensor using configuration # Initialize a DHT22 sensor using configuration
dht_sensor = DHT22Sensor( dht_sensor = DHT22Sensor(sensor_config=config) # Pass the loaded config
config=config # Pass the loaded config
)
print(f"Initialized DHT22 sensor: {dht_sensor.name}, pin: {dht_sensor.pin}") print(f"Initialized DHT22 sensor: {dht_sensor.name}, pin: {dht_sensor.pin}")
# Initialize an OLED display using configuration # Initialize an OLED display using configuration
display = OLEDDisplay( display = OLEDDisplay(config=config) # Pass the loaded config
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 # Set up button using configuration
button_config = get_button_config("main_button", config) button_config = get_button_config("main_button", config)
@@ -88,7 +95,9 @@ def main():
# Go to light sleep mode to save power # Go to light sleep mode to save power
# Wake up on pin change (button press) # Wake up on pin change (button press)
print("Entering light sleep mode...") 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 esp32.light_sleep() # Light sleep preserves RAM but saves power
# When we get here, the button was pressed # When we get here, the button was pressed
@@ -105,13 +114,9 @@ def main():
name_str = f"Sensor: {dht_sensor.name}" name_str = f"Sensor: {dht_sensor.name}"
# Display values # Display values
display.display_values([ display.display_values(
name_str, [name_str, temp_str, hum_str, time_str, "Press button again"]
temp_str, )
hum_str,
time_str,
"Press button again"
])
# Print to console # Print to console
print(f"Updated display with: {temp_str}, {hum_str}") print(f"Updated display with: {temp_str}, {hum_str}")

View File

@@ -9,15 +9,16 @@ Usage:
- Ensure config.json is properly set up with DHT22 sensor configuration - Ensure config.json is properly set up with DHT22 sensor configuration
- The script will read temperature and humidity at the specified interval - The script will read temperature and humidity at the specified interval
""" """
import time import time
import sys import sys
import os import os
# Add the src directory to the Python path if needed # 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 # 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.dht22 import DHT22Sensor
from src.esp_sensors.config import load_config, get_sensor_config 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')}") print(f"Sensor pin: {dht_config.get('pin')}")
# Initialize the sensor using configuration # Initialize the sensor using configuration
sensor = DHT22Sensor(config=config) sensor = DHT22Sensor(sensor_config=config)
print("Starting sensor readings. Press Ctrl+C to stop.") print("Starting sensor readings. Press Ctrl+C to stop.")
@@ -79,7 +80,7 @@ else:
print(f"Sensor pin: {dht_config.get('pin')}") print(f"Sensor pin: {dht_config.get('pin')}")
# Initialize the sensor using configuration # Initialize the sensor using configuration
sensor = DHT22Sensor(config=config) sensor = DHT22Sensor(sensor_config=config)
print("Starting simulated sensor readings. Press Ctrl+C to stop.") print("Starting simulated sensor readings. Press Ctrl+C to stop.")

View File

@@ -2,12 +2,13 @@
Example usage of the OLED display with temperature and humidity sensors. 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. This example demonstrates how to use the configuration system to initialize sensors and displays.
""" """
import time import time
import sys import sys
import os import os
# Add the src directory to the Python path # 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.oled_display import OLEDDisplay
from src.esp_sensors.dht22 import DHT22Sensor from src.esp_sensors.dht22 import DHT22Sensor
@@ -31,10 +32,12 @@ def main():
# Initialize a DHT22 sensor with configuration # Initialize a DHT22 sensor with configuration
dht_sensor = DHT22Sensor( dht_sensor = DHT22Sensor(
sensor_type="dht22", # This will load config for this sensor type # This will load config for this sensor type
config=config # Pass the loaded config 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 # Method 2: Initialize with some parameters from config, others specified directly
print("\nMethod 2: Override some config parameters") print("\nMethod 2: Override some config parameters")
@@ -44,11 +47,12 @@ def main():
# These parameters will override the config values # These parameters will override the config values
name="Custom Display", name="Custom Display",
interval=1, # Update every second interval=1, # Update every second
# Other parameters will be loaded from config # 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 initialization message
display.clear() display.clear()
@@ -72,13 +76,9 @@ def main():
name_str = f"Sensor: {dht_sensor.name}" name_str = f"Sensor: {dht_sensor.name}"
# Display values # Display values
display.display_values([ display.display_values(
name_str, [name_str, temp_str, hum_str, time_str, f"Demo ({i+1}/5)"]
temp_str, )
hum_str,
time_str,
f"Demo ({i+1}/5)"
])
# Print to console in simulation mode # Print to console in simulation mode
print(f"Updated display with: {temp_str}, {hum_str}") print(f"Updated display with: {temp_str}, {hum_str}")

View File

@@ -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, from/to a file, making it easy to change parameters like pins, display resolution,
sensor names, and intervals without modifying the code. sensor names, and intervals without modifying the code.
""" """
import json import json
import os import os
from typing import Dict, Any, Optional, Union, List from typing import Dict, Any, Optional, Union, List
@@ -15,28 +16,12 @@ DEFAULT_CONFIG_PATH = "config.json"
# Default configuration values # Default configuration values
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"sensors": { "sensors": {
"temperature": {
"name": "Temperature Sensor",
"pin": 4,
"interval": 60,
"unit": "C"
},
"humidity": {
"name": "Humidity Sensor",
"pin": 4,
"interval": 60
},
"dht22": { "dht22": {
"name": "DHT22 Sensor", "name": "DHT22 Sensor",
"pin": 4, "pin": 4,
"interval": 60, "interval": 60,
"temperature": { "temperature": {"name": "DHT22 Temperature", "unit": "C"},
"name": "DHT22 Temperature", "humidity": {"name": "DHT22 Humidity"},
"unit": "C"
},
"humidity": {
"name": "DHT22 Humidity"
}
} }
}, },
"displays": { "displays": {
@@ -47,15 +32,10 @@ DEFAULT_CONFIG = {
"width": 128, "width": 128,
"height": 64, "height": 64,
"address": "0x3C", "address": "0x3C",
"interval": 1 "interval": 1,
} }
}, },
"buttons": { "buttons": {"main_button": {"pin": 0, "pull_up": True}},
"main_button": {
"pin": 0,
"pull_up": True
}
}
} }
@@ -73,11 +53,13 @@ def load_config(config_path: str = DEFAULT_CONFIG_PATH) -> Dict[str, Any]:
""" """
try: try:
if os.path.exists(config_path): if os.path.exists(config_path):
with open(config_path, 'r') as f: with open(config_path, "r") as f:
config = json.load(f) config = json.load(f)
return config return config
else: 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 return DEFAULT_CONFIG
except Exception as e: except Exception as e:
print(f"Error loading configuration: {e}. Using default configuration.") 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 True if the configuration was saved successfully, False otherwise
""" """
try: try:
with open(config_path, 'w') as f: with open(config_path, "w") as f:
json.dump(config, f, indent=4) json.dump(config, f, indent=4)
return True return True
except Exception as e: except Exception as e:
@@ -104,7 +86,9 @@ def save_config(config: Dict[str, Any], config_path: str = DEFAULT_CONFIG_PATH)
return False 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. 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 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. 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 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. 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 return button_config
def create_default_config(config_path: str = DEFAULT_CONFIG_PATH) -> bool: def create_default_config(config_path: str = DEFAULT_CONFIG_PATH) -> bool:
""" """
Create a default configuration file. Create a default configuration file.

View File

@@ -1,14 +1,18 @@
""" """
DHT22 temperature and humidity sensor module for ESP32. DHT22 temperature and humidity sensor module for ESP32.
""" """
import time import time
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
try: try:
import dht import dht
from machine import Pin from machine import Pin
SIMULATION = False SIMULATION = False
except ImportError: except ImportError:
import random import random
SIMULATION = True SIMULATION = True
from .temperature import TemperatureSensor from .temperature import TemperatureSensor
@@ -19,8 +23,14 @@ from .config import get_sensor_config
class DHT22Sensor(TemperatureSensor, HumiditySensor): class DHT22Sensor(TemperatureSensor, HumiditySensor):
"""DHT22 temperature and humidity sensor implementation.""" """DHT22 temperature and humidity sensor implementation."""
def __init__(self, name: str = None, pin: int = None, interval: int = None, def __init__(
unit: str = None, sensor_type: str = "dht22", config: Dict[str, Any] = None): 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. Initialize a new DHT22 sensor.
@@ -28,38 +38,47 @@ class DHT22Sensor(TemperatureSensor, HumiditySensor):
name: The name of the sensor (if None, loaded from config) 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) 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) interval: Reading interval in seconds (if None, loaded from config)
unit: Temperature unit, either "C" or "F" (if None, loaded from config) temperature_unit: Temperature unit, either "C" or "F" (if None, loaded from config)
sensor_type: Type of the sensor for loading config (default: 'dht22') sensor_config: Sensor-Configuration dictionary (if provided, used instead of loading from file)
config: Configuration dictionary (if provided, used instead of loading from file)
""" """
# Load configuration if sensor_config is None:
sensor_config = get_sensor_config(sensor_type, config) sensor_config = {}
# Get main parameters from config if not provided self.apply_parameters(interval, name, pin, sensor_config)
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)
# Get temperature configuration # Get sensor configurations
temp_config = sensor_config.get('temperature', {}) temp_config = sensor_config.get("temperature", {})
temp_name = temp_config.get('name', self.name + ' Temperature') humidity_config = sensor_config.get("humidity", {})
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')
# Initialize both parent classes # Initialize both parent classes
TemperatureSensor.__init__(self, name=temp_name, pin=self.pin, interval=self.interval, unit=temp_unit) TemperatureSensor.__init__(
HumiditySensor.__init__(self, name=humidity_name, pin=self.pin, interval=self.interval) 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__) # Re-apply parameters to ensure they are not overridden by parent classes
self.name = name if name is not None else sensor_config.get('name', 'DHT22 Sensor') self.apply_parameters(interval, name, pin, sensor_config)
# Initialize the sensor if not in simulation mode # Initialize the sensor if not in simulation mode
if not SIMULATION: if not SIMULATION:
self._sensor = dht.DHT22(Pin(pin)) 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: def read_temperature(self) -> float:
""" """
Read the current temperature. Read the current temperature.

View File

@@ -1,6 +1,7 @@
""" """
Humidity sensor module for ESP-based sensors. Humidity sensor module for ESP-based sensors.
""" """
import random import random
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from .sensor import Sensor from .sensor import Sensor
@@ -10,8 +11,14 @@ from .config import get_sensor_config
class HumiditySensor(Sensor): class HumiditySensor(Sensor):
"""Humidity sensor implementation.""" """Humidity sensor implementation."""
def __init__(self, name: str = None, pin: int = None, interval: int = None, def __init__(
sensor_type: str = None, config: Dict[str, Any] = None, **kwargs): self,
name: str = None,
pin: int = None,
interval: int = None,
sensor_config: Dict[str, Any] = None,
**kwargs,
):
""" """
Initialize a new humidity sensor. Initialize a new humidity sensor.
@@ -19,12 +26,13 @@ class HumiditySensor(Sensor):
name: The name of the sensor (if None, loaded from config) 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) 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) interval: Reading interval in seconds (if None, loaded from config)
sensor_type: Type of the sensor for loading config (e.g., 'humidity') sensor_config: Sensor-Configuration dictionary (if provided, used instead of loading from file)
config: Configuration dictionary (if provided, used instead of loading from file)
**kwargs: Additional keyword arguments to pass to the parent class **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 # 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 self._last_humidity = None
def read_humidity(self) -> float: def read_humidity(self) -> float:

View File

@@ -1,11 +1,14 @@
""" """
OLED display module for ESP32 using SSD1306 controller. OLED display module for ESP32 using SSD1306 controller.
""" """
import time import time
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
try: try:
from machine import Pin, I2C from machine import Pin, I2C
import ssd1306 import ssd1306
SIMULATION = False SIMULATION = False
except ImportError: except ImportError:
SIMULATION = True SIMULATION = True
@@ -17,9 +20,17 @@ from .config import get_display_config
class OLEDDisplay(Sensor): class OLEDDisplay(Sensor):
"""SSD1306 OLED display implementation.""" """SSD1306 OLED display implementation."""
def __init__(self, name: str = None, scl_pin: int = None, sda_pin: int = None, def __init__(
width: int = None, height: int = None, address: str = None, self,
interval: int = None, config: Dict[str, Any] = None): 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. Initialize a new OLED display.
@@ -31,21 +42,26 @@ class OLEDDisplay(Sensor):
height: Display height in pixels (if None, loaded from config) height: Display height in pixels (if None, loaded from config)
address: I2C address of the display (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) 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 # Get parameters from config if not provided
name = name if name is not None else display_config.get("name", "OLED Display") 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) 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 # Initialize base class with the pin parameter
super().__init__(name=name, pin=sda_pin, interval=interval) super().__init__(name=name, pin=sda_pin, interval=interval)
# Set display-specific parameters # 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.sda_pin = sda_pin # Already set above
self.width = width if width is not None else display_config.get("width", 128) 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) self.height = height if height is not None else display_config.get("height", 64)

View File

@@ -1,6 +1,7 @@
""" """
Base sensor module for ESP-based sensors. Base sensor module for ESP-based sensors.
""" """
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from .config import get_sensor_config from .config import get_sensor_config
@@ -8,8 +9,13 @@ from .config import get_sensor_config
class Sensor: class Sensor:
"""Base class for all sensors.""" """Base class for all sensors."""
def __init__(self, name: str = None, pin: int = None, interval: int = None, def __init__(
sensor_type: str = None, config: Dict[str, Any] = None): self,
name: str = None,
pin: int = None,
interval: int = None,
sensor_config: Dict[str, Any] = None,
):
""" """
Initialize a new sensor. Initialize a new sensor.
@@ -17,22 +23,19 @@ class Sensor:
name: The name of the sensor (if None, loaded from config) 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) 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) interval: Reading interval in seconds (if None, loaded from config)
sensor_type: Type of the sensor for loading config (e.g., 'temperature') sensor_config: Sensor-Configuration dictionary (if provided, used instead of loading from file)
config: 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 if sensor_config is None:
self.name = name if name is not None else sensor_config.get('name', 'Unnamed Sensor') sensor_config = {}
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.name = (
else: name if name is not None else sensor_config.get("name", "Unnamed Sensor")
# 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 sensor_config.get("pin", 0)
self.pin = pin if pin is not None else 0 self.interval = (
self.interval = interval if interval is not None else 60 interval if interval is not None else sensor_config.get("interval", 60)
)
self._last_reading: Optional[float] = None self._last_reading: Optional[float] = None

View File

@@ -1,6 +1,7 @@
""" """
Temperature sensor module for ESP-based sensors. Temperature sensor module for ESP-based sensors.
""" """
import random import random
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from .sensor import Sensor from .sensor import Sensor
@@ -10,8 +11,14 @@ from .config import get_sensor_config
class TemperatureSensor(Sensor): class TemperatureSensor(Sensor):
"""Temperature sensor implementation.""" """Temperature sensor implementation."""
def __init__(self, name: str = None, pin: int = None, interval: int = None, def __init__(
unit: str = None, config: Dict[str, Any] = None): self,
name: str = None,
pin: int = None,
interval: int = None,
unit: str = None,
sensor_config: Dict[str, Any] = None,
):
""" """
Initialize a new temperature sensor. 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) 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) interval: Reading interval in seconds (if None, loaded from config)
unit: Temperature unit, either "C" or "F" (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 # 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 # Load configuration if not provided in parameters
if unit is None: if unit is None:
sensor_config = get_sensor_config("temperature", config)
unit = sensor_config.get("unit", "C") unit = sensor_config.get("unit", "C")
# Validate unit # Validate unit

View File

@@ -7,17 +7,24 @@ This program:
3. Wakes up and reads sensor data when the button is pressed 3. Wakes up and reads sensor data when the button is pressed
4. Displays the data on an OLED screen 4. Displays the data on an OLED screen
""" """
import time import time
import sys import sys
from esp_sensors.oled_display import OLEDDisplay from esp_sensors.oled_display import OLEDDisplay
from esp_sensors.dht22 import DHT22Sensor 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) # Import hardware-specific modules if available (for ESP32/ESP8266)
try: try:
from machine import Pin, deepsleep from machine import Pin, deepsleep
import esp32 import esp32
SIMULATION = False SIMULATION = False
except ImportError: except ImportError:
# Simulation mode for development on non-ESP hardware # Simulation mode for development on non-ESP hardware
@@ -27,10 +34,12 @@ except ImportError:
def simulate_button_press(): def simulate_button_press():
"""Simulate a button press in simulation mode.""" """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: try:
user_input = input() user_input = input()
if user_input.lower() == 'q': if user_input.lower() == "q":
return False return False
return True return True
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -47,12 +56,12 @@ def main():
# Initialize a DHT22 sensor using configuration # Initialize a DHT22 sensor using configuration
dht_sensor = DHT22Sensor( 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 # Initialize an OLED display using configuration
display = OLEDDisplay( display = OLEDDisplay(
config=config # Pass the loaded config display_config=get_display_config("oled", config) # Pass the loaded config
) )
# Set up button using configuration # Set up button using configuration
@@ -80,7 +89,9 @@ def main():
# Go to light sleep mode to save power # Go to light sleep mode to save power
# Wake up on pin change (button press) # Wake up on pin change (button press)
print("Entering light sleep mode...") 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 esp32.light_sleep() # Light sleep preserves RAM but saves power
# When we get here, the button was pressed # When we get here, the button was pressed
@@ -97,13 +108,9 @@ def main():
name_str = f"Sensor: {dht_sensor.name}" name_str = f"Sensor: {dht_sensor.name}"
# Display values # Display values
display.display_values([ display.display_values(
name_str, [name_str, temp_str, hum_str, time_str, "Press button again"]
temp_str, )
hum_str,
time_str,
"Press button again"
])
# Print to console # Print to console
print(f"Updated display with: {temp_str}, {hum_str}") print(f"Updated display with: {temp_str}, {hum_str}")

View File

@@ -1,6 +1,7 @@
""" """
Tests for the configuration module. Tests for the configuration module.
""" """
import os import os
import json import json
import tempfile import tempfile
@@ -11,7 +12,7 @@ from src.esp_sensors.config import (
get_sensor_config, get_sensor_config,
get_display_config, get_display_config,
create_default_config, create_default_config,
DEFAULT_CONFIG DEFAULT_CONFIG,
) )
@@ -36,11 +37,7 @@ def test_save_and_load_config():
# Create a test configuration # Create a test configuration
test_config = { test_config = {
"sensors": { "sensors": {
"test_sensor": { "test_sensor": {"name": "Test Sensor", "pin": 10, "interval": 30}
"name": "Test Sensor",
"pin": 10,
"interval": 30
}
} }
} }
@@ -67,13 +64,7 @@ def test_get_sensor_config():
"""Test getting sensor configuration.""" """Test getting sensor configuration."""
# Create a test configuration # Create a test configuration
test_config = { test_config = {
"sensors": { "sensors": {"test_sensor": {"name": "Test Sensor", "pin": 10, "interval": 30}}
"test_sensor": {
"name": "Test Sensor",
"pin": 10,
"interval": 30
}
}
} }
# Get configuration for an existing sensor # Get configuration for an existing sensor
@@ -92,11 +83,7 @@ def test_get_display_config():
# Create a test configuration # Create a test configuration
test_config = { test_config = {
"displays": { "displays": {
"test_display": { "test_display": {"name": "Test Display", "width": 64, "height": 32}
"name": "Test Display",
"width": 64,
"height": 32
}
} }
} }
@@ -129,7 +116,7 @@ def test_create_default_config():
assert os.path.exists(temp_path) assert os.path.exists(temp_path)
# Load the configuration and check it matches the default # 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) config = json.load(f)
assert config == DEFAULT_CONFIG assert config == DEFAULT_CONFIG

View File

@@ -1,6 +1,7 @@
""" """
Tests for the DHT22 sensor module. Tests for the DHT22 sensor module.
""" """
import pytest import pytest
from src.esp_sensors.dht22 import DHT22Sensor from src.esp_sensors.dht22 import DHT22Sensor
from src.esp_sensors.config import get_sensor_config 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(): def test_dht22_sensor_initialization():
"""Test that a DHT22 sensor can be initialized with valid parameters.""" """Test that a DHT22 sensor can be initialized with valid parameters."""
# Test direct parameter initialization # 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.name == "test_sensor"
assert sensor.pin == 5 assert sensor.pin == 5
assert sensor.interval == 30 assert sensor.interval == 30
@@ -18,23 +19,14 @@ def test_dht22_sensor_initialization():
# Test initialization with custom config # Test initialization with custom config
test_config = { test_config = {
"sensors": { "name": "config_sensor",
"dht22": { "pin": 6,
"name": "config_sensor", "interval": 40,
"pin": 6, "temperature": {"name": "Config Temperature", "unit": "F"},
"interval": 40, "humidity": {"name": "Config Humidity"},
"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.name == "config_sensor"
assert config_sensor.pin == 6 assert config_sensor.pin == 6
assert config_sensor.interval == 40 assert config_sensor.interval == 40
@@ -45,7 +37,7 @@ def test_dht22_sensor_initialization():
def test_dht22_sensor_invalid_unit(): def test_dht22_sensor_invalid_unit():
"""Test that initializing with an invalid unit raises a ValueError.""" """Test that initializing with an invalid unit raises a ValueError."""
with pytest.raises(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(): def test_dht22_sensor_read_temperature():
@@ -68,7 +60,7 @@ def test_dht22_sensor_read_humidity():
def test_dht22_sensor_fahrenheit(): def test_dht22_sensor_fahrenheit():
"""Test that a sensor initialized with Fahrenheit returns appropriate values.""" """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() reading = sensor.read()
assert isinstance(reading, float) assert isinstance(reading, float)
# For Fahrenheit, the reading should be between 59.0 and 86.0 # 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(): def test_dht22_temperature_conversion():
"""Test temperature conversion methods.""" """Test temperature conversion methods."""
# Test Celsius to Fahrenheit # 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 c_sensor._last_reading = 20.0 # Manually set for testing
f_value = c_sensor.to_fahrenheit() f_value = c_sensor.to_fahrenheit()
assert f_value == 68.0 # 20°C = 68°F assert f_value == 68.0 # 20°C = 68°F
# Test Fahrenheit to Celsius # 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 f_sensor._last_reading = 68.0 # Manually set for testing
c_value = f_sensor.to_celsius() c_value = f_sensor.to_celsius()
assert c_value == 20.0 # 68°F = 20°C assert c_value == 20.0 # 68°F = 20°C
@@ -92,7 +84,7 @@ def test_dht22_temperature_conversion():
def test_dht22_metadata(): def test_dht22_metadata():
"""Test that metadata includes the temperature unit, humidity, and type.""" """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() metadata = sensor.get_metadata()
assert metadata["name"] == "test_sensor" assert metadata["name"] == "test_sensor"
assert metadata["pin"] == 5 assert metadata["pin"] == 5

View File

@@ -1,6 +1,7 @@
""" """
Tests for the OLED display module. Tests for the OLED display module.
""" """
import pytest import pytest
from src.esp_sensors.oled_display import OLEDDisplay from src.esp_sensors.oled_display import OLEDDisplay
@@ -15,7 +16,7 @@ def test_oled_display_initialization():
assert display.width == 128 assert display.width == 128
assert display.height == 64 assert display.height == 64
assert display.address == 0x3C assert display.address == 0x3C
assert display.interval == 1 # Default interval is now 1 in the configuration assert display.interval == 60
assert display._values == [] assert display._values == []
@@ -28,7 +29,7 @@ def test_oled_display_custom_parameters():
width=64, width=64,
height=32, height=32,
address=0x3D, address=0x3D,
interval=30 interval=30,
) )
assert display.name == "custom_display" assert display.name == "custom_display"
assert display.scl_pin == 22 assert display.scl_pin == 22
@@ -57,7 +58,7 @@ def test_oled_display_metadata():
assert metadata["width"] == 128 assert metadata["width"] == 128
assert metadata["height"] == 64 assert metadata["height"] == 64
assert metadata["address"] == 0x3C 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["type"] == "SSD1306"
assert metadata["values_count"] == 0 assert metadata["values_count"] == 0

View File

@@ -1,6 +1,7 @@
""" """
Tests for the temperature sensor module. Tests for the temperature sensor module.
""" """
import pytest import pytest
from src.esp_sensors.temperature import TemperatureSensor from src.esp_sensors.temperature import TemperatureSensor