mirror of
https://github.com/OMGeeky/homecontrol.esp-sensors.git
synced 2025-12-26 17:02:29 +01:00
split dht sensor, add oled impl, add button triggered display example
This commit is contained in:
89
docs/button_triggered_display.md
Normal file
89
docs/button_triggered_display.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Button-Triggered Sensor Display
|
||||
|
||||
This document explains how to use the button-triggered sensor display example, which demonstrates an energy-efficient approach to reading and displaying sensor data on ESP32/ESP8266 devices.
|
||||
|
||||
## Overview
|
||||
|
||||
The button-triggered display example shows how to:
|
||||
|
||||
1. Set up a button input on an ESP device
|
||||
2. Use low-power sleep mode to conserve energy
|
||||
3. Wake up and read sensor data when the button is pressed
|
||||
4. Display the data on an OLED screen
|
||||
|
||||
This approach is ideal for battery-powered applications where energy conservation is important.
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
- ESP32 or ESP8266 development board
|
||||
- DHT22 temperature and humidity sensor
|
||||
- SSD1306 OLED display (128x64 pixels recommended)
|
||||
- Pushbutton
|
||||
- 10K pull-up resistor (if your button doesn't have an internal pull-up)
|
||||
- Breadboard and jumper wires
|
||||
|
||||
## Wiring
|
||||
|
||||
1. **DHT22 Sensor**:
|
||||
- Connect VCC to 3.3V
|
||||
- Connect GND to ground
|
||||
- Connect DATA to GPIO4 (or change the pin in the code)
|
||||
|
||||
2. **OLED Display**:
|
||||
- Connect VCC to 3.3V
|
||||
- Connect GND to ground
|
||||
- Connect SCL to GPIO22 (or change the pin in the code)
|
||||
- Connect SDA to GPIO21 (or change the pin in the code)
|
||||
|
||||
3. **Button**:
|
||||
- Connect one side to GPIO0 (or change the pin in the code)
|
||||
- Connect the other side to ground
|
||||
- Connect a 10K pull-up resistor between GPIO0 and 3.3V (if not using internal pull-up)
|
||||
|
||||
## Running the Example
|
||||
|
||||
1. Flash MicroPython to your ESP device if you haven't already
|
||||
2. Upload the `button_triggered_display.py` script to your device
|
||||
3. Run the script
|
||||
|
||||
```python
|
||||
import button_triggered_display
|
||||
button_triggered_display.main()
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Energy Conservation
|
||||
|
||||
The example uses ESP32's light sleep mode to conserve energy when not actively reading or displaying data. In light sleep mode:
|
||||
|
||||
- The CPU is paused
|
||||
- RAM is preserved
|
||||
- Peripherals can be configured to wake the device
|
||||
- Power consumption is significantly reduced
|
||||
|
||||
### Button Wake-Up
|
||||
|
||||
The device is configured to wake up from sleep when the button is pressed. This is done using the `wake_on_ext0` function, which allows an external pin to trigger a wake-up event.
|
||||
|
||||
### Simulation Mode
|
||||
|
||||
The example includes a simulation mode that runs when not on actual ESP hardware. This allows you to test the functionality on a development computer before deploying to the ESP device.
|
||||
|
||||
## Customization
|
||||
|
||||
You can customize the example by:
|
||||
|
||||
1. Changing the GPIO pins for the sensor, display, or button
|
||||
2. Adjusting the display time before going back to sleep
|
||||
3. Adding additional sensors
|
||||
4. Modifying the information displayed on the OLED screen
|
||||
|
||||
## Power Consumption
|
||||
|
||||
Typical power consumption in different states:
|
||||
|
||||
- Active mode (reading sensors and updating display): ~80-120mA
|
||||
- Light sleep mode: ~0.8-1.5mA
|
||||
|
||||
This represents a power saving of approximately 98% during idle periods, significantly extending battery life.
|
||||
165
docs/oled_display.md
Normal file
165
docs/oled_display.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# OLED Display Module
|
||||
|
||||
This module provides a class for interfacing with SSD1306 OLED displays via I2C on ESP32/ESP8266 microcontrollers.
|
||||
|
||||
## Features
|
||||
|
||||
- Compatible with SSD1306 OLED displays
|
||||
- I2C interface support
|
||||
- Display text at specific coordinates
|
||||
- Display a list of values (e.g., sensor readings)
|
||||
- Simulation mode for testing without hardware
|
||||
- Integration with the ESP Sensors framework
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
- ESP32 or ESP8266 microcontroller
|
||||
- SSD1306 OLED display (common sizes: 128x64, 128x32, 64x48)
|
||||
- I2C connection (2 pins: SCL and SDA)
|
||||
|
||||
## Installation
|
||||
|
||||
The OLED display module is part of the ESP Sensors package. No additional installation is required if you have already installed the package.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Initialization
|
||||
|
||||
```python
|
||||
from esp_sensors.oled_display import OLEDDisplay
|
||||
|
||||
# Initialize the display
|
||||
display = OLEDDisplay(
|
||||
name="Status Display",
|
||||
scl_pin=22, # GPIO pin for I2C clock
|
||||
sda_pin=21, # GPIO pin for I2C data
|
||||
width=128, # Display width in pixels
|
||||
height=64, # Display height in pixels
|
||||
address=0x3C # I2C address (default: 0x3C)
|
||||
)
|
||||
```
|
||||
|
||||
### Displaying Text
|
||||
|
||||
```python
|
||||
# Clear the display
|
||||
display.clear()
|
||||
|
||||
# Display text at specific coordinates
|
||||
display.display_text("Hello, World!", x=0, y=0)
|
||||
display.display_text("Line 2", x=0, y=10)
|
||||
display.display_text("ESP32", x=64, y=30)
|
||||
```
|
||||
|
||||
### Displaying Multiple Values
|
||||
|
||||
```python
|
||||
# Display a list of values (e.g., sensor readings)
|
||||
display.display_values([
|
||||
"Temperature: 25.5°C",
|
||||
"Humidity: 45%",
|
||||
"Pressure: 1013 hPa",
|
||||
"Time: 12:34:56"
|
||||
])
|
||||
```
|
||||
|
||||
### Integration with Sensors
|
||||
|
||||
```python
|
||||
from esp_sensors.dht22 import DHT22Sensor
|
||||
|
||||
# Initialize a DHT22 sensor
|
||||
sensor = DHT22Sensor("Living Room", pin=4)
|
||||
|
||||
# Read sensor values
|
||||
temperature = sensor.read_temperature()
|
||||
humidity = sensor.read_humidity()
|
||||
|
||||
# Display sensor values
|
||||
display.display_values([
|
||||
f"Temp: {temperature:.1f}°C",
|
||||
f"Humidity: {humidity:.1f}%"
|
||||
])
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Class: OLEDDisplay
|
||||
|
||||
#### Constructor
|
||||
|
||||
```python
|
||||
OLEDDisplay(name, scl_pin, sda_pin, width=128, height=64, address=0x3C, interval=60)
|
||||
```
|
||||
|
||||
Parameters:
|
||||
- `name` (str): The name of the display
|
||||
- `scl_pin` (int): The GPIO pin number for the SCL (clock) line
|
||||
- `sda_pin` (int): The GPIO pin number for the SDA (data) line
|
||||
- `width` (int): Display width in pixels (default: 128)
|
||||
- `height` (int): Display height in pixels (default: 64)
|
||||
- `address` (int): I2C address of the display (default: 0x3C)
|
||||
- `interval` (int): Refresh interval in seconds (default: 60)
|
||||
|
||||
#### Methods
|
||||
|
||||
##### clear()
|
||||
|
||||
Clears the display.
|
||||
|
||||
```python
|
||||
display.clear()
|
||||
```
|
||||
|
||||
##### display_text(text, x=0, y=0, color=1)
|
||||
|
||||
Displays text at the specified position.
|
||||
|
||||
Parameters:
|
||||
- `text` (str): The text to display
|
||||
- `x` (int): X coordinate (default: 0)
|
||||
- `y` (int): Y coordinate (default: 0)
|
||||
- `color` (int): Pixel color (1 for white, 0 for black, default: 1)
|
||||
|
||||
```python
|
||||
display.display_text("Hello", x=10, y=20)
|
||||
```
|
||||
|
||||
##### display_values(values)
|
||||
|
||||
Displays a list of values on the OLED screen.
|
||||
|
||||
Parameters:
|
||||
- `values` (list): List of values to display (strings or objects with __str__ method)
|
||||
|
||||
```python
|
||||
display.display_values(["Line 1", "Line 2", "Line 3"])
|
||||
```
|
||||
|
||||
##### get_metadata()
|
||||
|
||||
Returns a dictionary containing display metadata.
|
||||
|
||||
```python
|
||||
metadata = display.get_metadata()
|
||||
print(metadata)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Display Not Working
|
||||
|
||||
1. Check the I2C address (common addresses are 0x3C and 0x3D)
|
||||
2. Verify the SCL and SDA pin connections
|
||||
3. Ensure the display is powered correctly (usually 3.3V)
|
||||
4. Try a different I2C bus speed if available
|
||||
|
||||
### Text Not Displaying Correctly
|
||||
|
||||
1. Check that the coordinates are within the display dimensions
|
||||
2. Ensure the text doesn't exceed the display width
|
||||
3. Try using smaller font or breaking text into multiple lines
|
||||
|
||||
## Example
|
||||
|
||||
See the `examples/oled_display_example.py` file for a complete example of using the OLED display with sensors.
|
||||
137
examples/button_triggered_display.py
Normal file
137
examples/button_triggered_display.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Example of an energy-efficient sensor display that activates on button press.
|
||||
|
||||
This example demonstrates how to:
|
||||
1. Set up a button input
|
||||
2. Use low-power sleep mode to conserve energy
|
||||
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__), '..')))
|
||||
|
||||
from src.esp_sensors.oled_display import OLEDDisplay
|
||||
from src.esp_sensors.dht22 import DHT22Sensor
|
||||
|
||||
# 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
|
||||
SIMULATION = True
|
||||
print("Running in simulation mode - hardware functions will be simulated")
|
||||
|
||||
|
||||
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)...")
|
||||
try:
|
||||
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.
|
||||
"""
|
||||
# Initialize a DHT22 sensor
|
||||
dht_sensor = DHT22Sensor(
|
||||
name="Living Room",
|
||||
pin=4, # GPIO pin for DHT22 data
|
||||
interval=5, # Read every 5 seconds
|
||||
unit="C" # Celsius
|
||||
)
|
||||
|
||||
# Initialize an OLED display
|
||||
display = OLEDDisplay(
|
||||
name="Status Display",
|
||||
scl_pin=22, # GPIO pin for I2C clock
|
||||
sda_pin=21, # GPIO pin for I2C data
|
||||
width=128, # Display width in pixels
|
||||
height=64, # Display height in pixels
|
||||
interval=1 # Update every second
|
||||
)
|
||||
|
||||
# Set up button on GPIO pin 0 (common button pin on many ESP boards)
|
||||
button_pin = 0
|
||||
if not SIMULATION:
|
||||
button = Pin(button_pin, Pin.IN, Pin.PULL_UP)
|
||||
|
||||
# Display initialization message
|
||||
display.clear()
|
||||
display.display_text("Ready - Press Button", 0, 0)
|
||||
print("System initialized. Waiting for button press...")
|
||||
|
||||
# Main loop - sleep until button press, then read and display sensor data
|
||||
try:
|
||||
while True:
|
||||
# Wait for button press
|
||||
if SIMULATION:
|
||||
# In simulation mode, wait for Enter key
|
||||
if not simulate_button_press():
|
||||
break # Exit if Ctrl+C was pressed
|
||||
else:
|
||||
# In hardware mode, check if button is pressed (active low)
|
||||
if button.value() == 1: # Button not pressed
|
||||
# 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.light_sleep() # Light sleep preserves RAM but saves power
|
||||
# When we get here, the button was pressed
|
||||
|
||||
print("Button pressed! Reading sensor data...")
|
||||
|
||||
# Read sensor values
|
||||
temperature = dht_sensor.read_temperature()
|
||||
humidity = dht_sensor.read_humidity()
|
||||
|
||||
# Format values for display
|
||||
temp_str = f"Temp: {temperature:.1f} C"
|
||||
hum_str = f"Humidity: {humidity:.1f}%"
|
||||
time_str = f"Time: {time.time():.0f}"
|
||||
name_str = f"Sensor: {dht_sensor.name}"
|
||||
|
||||
# Display values
|
||||
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}")
|
||||
|
||||
# Keep display on for a few seconds before going back to sleep
|
||||
time.sleep(5)
|
||||
|
||||
# Clear display to save power
|
||||
display.clear()
|
||||
display.display_text("Ready - Press Button", 0, 0)
|
||||
|
||||
if SIMULATION:
|
||||
print("Display cleared. Ready for next button press.")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
# Clean up on exit
|
||||
display.clear()
|
||||
display.display_text("Shutting down...", 0, 0)
|
||||
time.sleep(1)
|
||||
display.clear()
|
||||
print("Program terminated by user")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
84
examples/oled_display_example.py
Normal file
84
examples/oled_display_example.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
Example usage of the OLED display with temperature and humidity sensors.
|
||||
"""
|
||||
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__), '..')))
|
||||
|
||||
from src.esp_sensors.oled_display import OLEDDisplay
|
||||
from src.esp_sensors.dht22 import DHT22Sensor
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to demonstrate OLED display usage with sensors.
|
||||
"""
|
||||
# Initialize a DHT22 sensor
|
||||
dht_sensor = DHT22Sensor(
|
||||
name="Living Room",
|
||||
pin=4, # GPIO pin for DHT22 data
|
||||
interval=5, # Read every 5 seconds
|
||||
unit="C" # Celsius
|
||||
)
|
||||
|
||||
# Initialize an OLED display
|
||||
display = OLEDDisplay(
|
||||
name="Status Display",
|
||||
scl_pin=22, # GPIO pin for I2C clock
|
||||
sda_pin=21, # GPIO pin for I2C data
|
||||
width=128, # Display width in pixels
|
||||
height=64, # Display height in pixels
|
||||
interval=1 # Update every second
|
||||
)
|
||||
|
||||
# Display initialization message
|
||||
display.clear()
|
||||
display.display_text("Initializing...", 0, 0)
|
||||
time.sleep(2)
|
||||
|
||||
# Main loop - run for 5 iterations as a demonstration
|
||||
try:
|
||||
print("Starting demonstration (5 iterations)...")
|
||||
for i in range(5):
|
||||
print(f"\nIteration {i+1}/5:")
|
||||
|
||||
# Read sensor values
|
||||
temperature = dht_sensor.read_temperature()
|
||||
humidity = dht_sensor.read_humidity()
|
||||
|
||||
# Format values for display
|
||||
temp_str = f"Temp: {temperature:.1f} C"
|
||||
hum_str = f"Humidity: {humidity:.1f}%"
|
||||
time_str = f"Time: {time.time():.0f}"
|
||||
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)"
|
||||
])
|
||||
|
||||
# Print to console in simulation mode
|
||||
print(f"Updated display with: {temp_str}, {hum_str}")
|
||||
|
||||
# Wait for next update
|
||||
print(f"Waiting {display.interval} second(s)...")
|
||||
time.sleep(display.interval)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
# Clean up on exit
|
||||
display.clear()
|
||||
display.display_text("Shutting down...", 0, 0)
|
||||
time.sleep(1)
|
||||
display.clear()
|
||||
print("Program terminated by user")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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
|
||||
|
||||
|
||||
44
src/esp_sensors/humidity.py
Normal file
44
src/esp_sensors/humidity.py
Normal 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
|
||||
136
src/esp_sensors/oled_display.py
Normal file
136
src/esp_sensors/oled_display.py
Normal 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
|
||||
@@ -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.
|
||||
|
||||
107
tests/test_oled_display.py
Normal file
107
tests/test_oled_display.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Tests for the OLED display module.
|
||||
"""
|
||||
import pytest
|
||||
from src.esp_sensors.oled_display import OLEDDisplay
|
||||
|
||||
|
||||
def test_oled_display_initialization():
|
||||
"""Test that an OLED display can be initialized with valid parameters."""
|
||||
display = OLEDDisplay("test_display", scl_pin=22, sda_pin=21)
|
||||
assert display.name == "test_display"
|
||||
assert display.scl_pin == 22
|
||||
assert display.sda_pin == 21
|
||||
assert display.pin == 21 # pin in base class is set to sda_pin
|
||||
assert display.width == 128
|
||||
assert display.height == 64
|
||||
assert display.address == 0x3C
|
||||
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
|
||||
)
|
||||
assert display.name == "custom_display"
|
||||
assert display.scl_pin == 22
|
||||
assert display.sda_pin == 21
|
||||
assert display.width == 64
|
||||
assert display.height == 32
|
||||
assert display.address == 0x3D
|
||||
assert display.interval == 30
|
||||
|
||||
|
||||
def test_oled_display_read():
|
||||
"""Test that reading from the display returns a success value."""
|
||||
display = OLEDDisplay("test_display", scl_pin=22, sda_pin=21)
|
||||
reading = display.read()
|
||||
assert reading == 1.0
|
||||
|
||||
|
||||
def test_oled_display_metadata():
|
||||
"""Test that metadata includes the display parameters."""
|
||||
display = OLEDDisplay("test_display", scl_pin=22, sda_pin=21)
|
||||
metadata = display.get_metadata()
|
||||
assert metadata["name"] == "test_display"
|
||||
assert metadata["pin"] == 21
|
||||
assert metadata["scl_pin"] == 22
|
||||
assert metadata["sda_pin"] == 21
|
||||
assert metadata["width"] == 128
|
||||
assert metadata["height"] == 64
|
||||
assert metadata["address"] == 0x3C
|
||||
assert metadata["interval"] == 60
|
||||
assert metadata["type"] == "SSD1306"
|
||||
assert metadata["values_count"] == 0
|
||||
|
||||
|
||||
def test_oled_display_values():
|
||||
"""Test that display values are stored correctly."""
|
||||
display = OLEDDisplay("test_display", scl_pin=22, sda_pin=21)
|
||||
|
||||
# Test with empty values
|
||||
assert display._values == []
|
||||
|
||||
# Test with string values
|
||||
test_values = ["Temperature: 25.0°C", "Humidity: 45.0%"]
|
||||
display.display_values(test_values)
|
||||
assert display._values == test_values
|
||||
|
||||
# Check that metadata reflects the number of values
|
||||
metadata = display.get_metadata()
|
||||
assert metadata["values_count"] == 2
|
||||
|
||||
|
||||
def test_oled_display_clear():
|
||||
"""Test that clearing the display works in simulation mode."""
|
||||
display = OLEDDisplay("test_display", scl_pin=22, sda_pin=21)
|
||||
|
||||
# This is mostly a coverage test since we can't check the actual display in tests
|
||||
display.clear()
|
||||
|
||||
# Verify that clearing doesn't affect stored values
|
||||
test_values = ["Temperature: 25.0°C"]
|
||||
display.display_values(test_values)
|
||||
display.clear()
|
||||
assert display._values == test_values
|
||||
|
||||
|
||||
def test_oled_display_text():
|
||||
"""Test that displaying text works in simulation mode."""
|
||||
display = OLEDDisplay("test_display", scl_pin=22, sda_pin=21)
|
||||
|
||||
# This is mostly a coverage test since we can't check the actual display in tests
|
||||
display.display_text("Hello, World!")
|
||||
|
||||
# Verify that displaying text doesn't affect stored values
|
||||
test_values = ["Temperature: 25.0°C"]
|
||||
display.display_values(test_values)
|
||||
display.display_text("Hello, World!")
|
||||
assert display._values == test_values
|
||||
Reference in New Issue
Block a user