split dht sensor, add oled impl, add button triggered display example

This commit is contained in:
OMGeeky
2025-05-07 20:19:42 +02:00
parent f5af3419d5
commit 46f4e5a95e
9 changed files with 803 additions and 22 deletions

View File

@@ -10,10 +10,11 @@ except ImportError:
import random
SIMULATION = True
from .sensor import Sensor
from .temperature import TemperatureSensor
from .humidity import HumiditySensor
class DHT22Sensor(Sensor):
class DHT22Sensor(TemperatureSensor, HumiditySensor):
"""DHT22 temperature and humidity sensor implementation."""
def __init__(self, name: str, pin: int, interval: int = 60, unit: str = "C"):
@@ -26,17 +27,15 @@ class DHT22Sensor(Sensor):
interval: Reading interval in seconds (default: 60)
unit: Temperature unit, either "C" for Celsius or "F" for Fahrenheit (default: "C")
"""
super().__init__(name, pin, interval)
if unit not in ["C", "F"]:
raise ValueError("Unit must be either 'C' or 'F'")
self.unit = unit
self._last_humidity = None
# Initialize both parent classes
TemperatureSensor.__init__(self, name, pin, interval, unit)
HumiditySensor.__init__(self, name, pin, interval)
# Initialize the sensor if not in simulation mode
if not SIMULATION:
self._sensor = dht.DHT22(Pin(pin))
def read(self) -> float:
def read_temperature(self) -> float:
"""
Read the current temperature.
@@ -44,14 +43,11 @@ class DHT22Sensor(Sensor):
The temperature reading as a float
"""
if SIMULATION:
# Simulate temperature reading
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)
# Simulate humidity reading (between 30% and 90%)
self._last_humidity = round(random.uniform(30.0, 90.0), 1)
# Use parent class simulation for temperature
temp_reading = super().read_temperature()
# Also update humidity in simulation mode
self._last_humidity = super().read_humidity()
return temp_reading
else:
# Actual hardware reading
try:
@@ -63,6 +59,7 @@ class DHT22Sensor(Sensor):
temp = (temp * 9 / 5) + 32
self._last_reading = round(temp, 1)
# Also read humidity while we're at it
self._last_humidity = round(self._sensor.humidity(), 1)
except Exception as e:
print(f"Error reading DHT22 sensor: {e}")
@@ -74,6 +71,15 @@ class DHT22Sensor(Sensor):
return self._last_reading
def read(self) -> float:
"""
Read the current temperature (wrapper for read_temperature).
Returns:
The temperature reading as a float
"""
return self.read_temperature()
def read_humidity(self) -> float:
"""
Read the current humidity.
@@ -84,8 +90,8 @@ class DHT22Sensor(Sensor):
# If we haven't read yet, read only humidity
if self._last_humidity is None:
if SIMULATION:
# Simulate humidity reading (between 30% and 90%)
self._last_humidity = round(random.uniform(30.0, 90.0), 1)
# Use parent class simulation
return super().read_humidity()
else:
# Actual hardware reading
try:
@@ -104,9 +110,13 @@ class DHT22Sensor(Sensor):
Returns:
A dictionary containing sensor metadata
"""
metadata = super().get_metadata()
metadata["unit"] = self.unit
metadata["last_humidity"] = self._last_humidity
# Get metadata from TemperatureSensor
temp_metadata = TemperatureSensor.get_metadata(self)
# Get metadata from HumiditySensor
humidity_metadata = HumiditySensor.get_metadata(self)
# Combine metadata from both parent classes
metadata = {**temp_metadata, **humidity_metadata}
metadata["type"] = "DHT22"
return metadata

View File

@@ -0,0 +1,44 @@
"""
Humidity sensor module for ESP-based sensors.
"""
import random
from .sensor import Sensor
class HumiditySensor(Sensor):
"""Humidity sensor implementation."""
def __init__(self, name: str, pin: int, interval: int = 60):
"""
Initialize a new humidity sensor.
Args:
name: The name of the sensor
pin: The GPIO pin number the sensor is connected to
interval: Reading interval in seconds (default: 60)
"""
super().__init__(name, pin, interval)
self._last_humidity = None
def read_humidity(self) -> float:
"""
Read the current humidity.
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)
return self._last_humidity
def get_metadata(self):
"""
Get sensor metadata including humidity information.
Returns:
A dictionary containing sensor metadata
"""
metadata = super().get_metadata()
metadata["last_humidity"] = self._last_humidity
return metadata

View File

@@ -0,0 +1,136 @@
"""
OLED display module for ESP32 using SSD1306 controller.
"""
import time
try:
from machine import Pin, I2C
import ssd1306
SIMULATION = False
except ImportError:
SIMULATION = True
from .sensor import Sensor
class OLEDDisplay(Sensor):
"""SSD1306 OLED display implementation."""
def __init__(self, name: str, scl_pin: int, sda_pin: int, width: int = 128, height: int = 64,
address: int = 0x3C, interval: int = 60):
"""
Initialize a new OLED display.
Args:
name: The name of the display
scl_pin: The GPIO pin number for the SCL (clock) line
sda_pin: The GPIO pin number for the SDA (data) line
width: Display width in pixels (default: 128)
height: Display height in pixels (default: 64)
address: I2C address of the display (default: 0x3C)
interval: Refresh interval in seconds (default: 60)
"""
# Use sda_pin as the pin parameter for the Sensor base class
super().__init__(name, sda_pin, interval)
self.scl_pin = scl_pin
self.sda_pin = sda_pin
self.width = width
self.height = height
self.address = address
self._values = []
# Initialize the display if not in simulation mode
if not SIMULATION:
try:
i2c = I2C(0, scl=Pin(scl_pin), sda=Pin(sda_pin))
self._display = ssd1306.SSD1306_I2C(width, height, i2c, addr=address)
self._display.fill(0) # Clear the display
self._display.text("Initialized", 0, 0, 1)
self._display.show()
except Exception as e:
print(f"Error initializing OLED display: {e}")
self._display = None
else:
# In simulation mode, just print to console
print(f"Simulated OLED display initialized: {width}x{height}")
self._display = None
def clear(self):
"""
Clear the display.
"""
if SIMULATION:
print("Simulated OLED display cleared")
else:
if self._display:
self._display.fill(0)
self._display.show()
def display_text(self, text: str, x: int = 0, y: int = 0, color: int = 1):
"""
Display text at the specified position.
Args:
text: The text to display
x: X coordinate (default: 0)
y: Y coordinate (default: 0)
color: Pixel color (1 for white, 0 for black, default: 1)
"""
if SIMULATION:
print(f"Simulated OLED display text at ({x}, {y}): {text}")
else:
if self._display:
self._display.text(text, x, y, color)
self._display.show()
def display_values(self, values: list):
"""
Display a list of values on the OLED screen.
Args:
values: List of values to display (strings or objects with __str__ method)
"""
self._values = values
if SIMULATION:
print("Simulated OLED display values:")
for i, value in enumerate(values):
print(f" Line {i}: {value}")
else:
if self._display:
self._display.fill(0) # Clear the display
# Display each value on a new line (8 pixels per line)
for i, value in enumerate(values):
if i * 10 < self.height: # Make sure we don't go off the screen
self._display.text(str(value), 0, i * 10, 1)
self._display.show()
def read(self) -> float:
"""
Update the display (placeholder to satisfy Sensor interface).
Returns:
Always returns 1.0 to indicate success
"""
# This method is required by the Sensor interface but doesn't make sense for a display
# We'll just return a constant value
return 1.0
def get_metadata(self):
"""
Get display metadata.
Returns:
A dictionary containing display metadata
"""
metadata = super().get_metadata()
metadata["scl_pin"] = self.scl_pin
metadata["sda_pin"] = self.sda_pin
metadata["width"] = self.width
metadata["height"] = self.height
metadata["address"] = self.address
metadata["type"] = "SSD1306"
metadata["values_count"] = len(self._values)
return metadata

View File

@@ -23,7 +23,7 @@ class TemperatureSensor(Sensor):
raise ValueError("Unit must be either 'C' or 'F'")
self.unit = unit
def read(self) -> float:
def read_temperature(self) -> float:
"""
Read the current temperature.
@@ -38,6 +38,15 @@ class TemperatureSensor(Sensor):
self._last_reading = round(random.uniform(59.0, 86.0), 1)
return self._last_reading
def read(self) -> float:
"""
Read the current temperature (wrapper for read_temperature).
Returns:
The temperature reading as a float
"""
return self.read_temperature()
def get_metadata(self):
"""
Get sensor metadata including temperature unit.