mirror of
https://github.com/OMGeeky/homecontrol.esp-sensors.git
synced 2025-12-26 17:02:29 +01:00
let junie create initial structure
This commit is contained in:
152
.junie/guidelines.md
Normal file
152
.junie/guidelines.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# ESP Sensors Project Guidelines
|
||||
|
||||
This document provides guidelines and instructions for developing and maintaining the ESP Sensors project.
|
||||
|
||||
## Build and Configuration Instructions
|
||||
|
||||
### Environment Setup
|
||||
|
||||
1. **Python Version**: This project uses Python 3.12. Ensure you have this version installed.
|
||||
|
||||
2. **Virtual Environment**: Always use a virtual environment for development:
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
```
|
||||
|
||||
3. **Dependencies**: Install the required dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
The project follows this structure:
|
||||
```
|
||||
esp-sensors/
|
||||
├── src/
|
||||
│ └── esp_sensors/ # Main package
|
||||
│ ├── __init__.py
|
||||
│ ├── sensor.py # Base sensor class
|
||||
│ └── temperature.py # Temperature sensor implementation
|
||||
├── tests/ # Test directory
|
||||
├── .junie/ # Project documentation
|
||||
├── pyproject.toml # Project configuration
|
||||
└── requirements.txt # Dependencies
|
||||
```
|
||||
|
||||
## Testing Information
|
||||
|
||||
### Running Tests
|
||||
|
||||
1. **Basic Test Run**:
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
2. **Verbose Output**:
|
||||
```bash
|
||||
python -m pytest -v
|
||||
```
|
||||
|
||||
3. **With Coverage**:
|
||||
```bash
|
||||
python -m pytest --cov=src.esp_sensors
|
||||
```
|
||||
|
||||
4. **Generate Coverage Report**:
|
||||
```bash
|
||||
python -m pytest --cov=src.esp_sensors --cov-report=html
|
||||
```
|
||||
This will create a `htmlcov` directory with an HTML coverage report.
|
||||
|
||||
### Adding New Tests
|
||||
|
||||
1. Create test files in the `tests` directory with the naming pattern `test_*.py`.
|
||||
2. Test functions should be named with the prefix `test_`.
|
||||
3. Use pytest fixtures for common setup and teardown operations.
|
||||
|
||||
### Example Test
|
||||
|
||||
Here's a simple example of a test for a temperature sensor:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from src.esp_sensors.temperature import TemperatureSensor
|
||||
|
||||
def test_temperature_sensor_initialization():
|
||||
"""Test that a temperature sensor can be initialized with valid parameters."""
|
||||
sensor = TemperatureSensor("test_sensor", 5, 30, "C")
|
||||
assert sensor.name == "test_sensor"
|
||||
assert sensor.pin == 5
|
||||
assert sensor.interval == 30
|
||||
assert sensor.unit == "C"
|
||||
```
|
||||
|
||||
## Code Style and Development Guidelines
|
||||
|
||||
### Code Formatting
|
||||
|
||||
This project uses [Black](https://black.readthedocs.io/) for code formatting:
|
||||
|
||||
```bash
|
||||
# Check if files need formatting
|
||||
black --check .
|
||||
|
||||
# Format files
|
||||
black .
|
||||
```
|
||||
|
||||
### Type Hints
|
||||
|
||||
Always use type hints in function signatures and variable declarations:
|
||||
|
||||
```python
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
def process_reading(value: float, metadata: Dict[str, Any]) -> Optional[float]:
|
||||
# Function implementation
|
||||
pass
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
- Use docstrings for all modules, classes, and functions.
|
||||
- Follow the Google docstring style.
|
||||
- Include examples in docstrings where appropriate.
|
||||
|
||||
Example:
|
||||
```python
|
||||
def read(self) -> float:
|
||||
"""
|
||||
Read the current sensor value.
|
||||
|
||||
Returns:
|
||||
The sensor reading as a float
|
||||
"""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Use specific exception types rather than generic exceptions.
|
||||
- Handle exceptions at the appropriate level.
|
||||
- Log exceptions with context information.
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. Create a new branch for each feature or bug fix.
|
||||
2. Write tests before implementing features (Test-Driven Development).
|
||||
3. Ensure all tests pass before submitting changes.
|
||||
4. Format code with Black before committing.
|
||||
5. Update documentation as needed.
|
||||
|
||||
## ESP-Specific Development Notes
|
||||
|
||||
When developing for actual ESP hardware:
|
||||
|
||||
1. This project is designed to work with MicroPython on ESP32/ESP8266 devices.
|
||||
2. For hardware testing, you'll need to flash MicroPython to your device.
|
||||
3. Use tools like `ampy` or `rshell` to upload code to the device.
|
||||
4. Consider memory constraints when developing for ESP devices.
|
||||
5. For production, optimize code to reduce memory usage and power consumption.
|
||||
22
pyproject.toml
Normal file
22
pyproject.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ['py312']
|
||||
include = '\.pyi?$'
|
||||
exclude = '''
|
||||
/(
|
||||
\.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| _build
|
||||
| buck-out
|
||||
| build
|
||||
| dist
|
||||
)/
|
||||
'''
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = "test_*.py"
|
||||
python_functions = "test_*"
|
||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# Core dependencies
|
||||
pytest==7.4.0
|
||||
pytest-cov==4.1.0
|
||||
black==24.3.0
|
||||
|
||||
# ESP-specific dependencies (these would be used in a real implementation)
|
||||
# micropython-esp32==1.19.1
|
||||
# micropython-umqtt.simple==1.3.4
|
||||
3
src/esp_sensors/__init__.py
Normal file
3
src/esp_sensors/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
ESP Sensors package for home control and automation.
|
||||
"""
|
||||
48
src/esp_sensors/sensor.py
Normal file
48
src/esp_sensors/sensor.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Base sensor module for ESP-based sensors.
|
||||
"""
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class Sensor:
|
||||
"""Base class for all sensors."""
|
||||
|
||||
def __init__(self, name: str, pin: int, interval: int = 60):
|
||||
"""
|
||||
Initialize a new 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)
|
||||
"""
|
||||
self.name = name
|
||||
self.pin = pin
|
||||
self.interval = interval
|
||||
self._last_reading: Optional[float] = None
|
||||
|
||||
def read(self) -> float:
|
||||
"""
|
||||
Read the current sensor value.
|
||||
|
||||
Returns:
|
||||
The sensor reading as a float
|
||||
"""
|
||||
# This is a placeholder that would be overridden by subclasses
|
||||
# In a real implementation, this would interact with the hardware
|
||||
self._last_reading = 0.0
|
||||
return self._last_reading
|
||||
|
||||
def get_metadata(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get sensor metadata.
|
||||
|
||||
Returns:
|
||||
A dictionary containing sensor metadata
|
||||
"""
|
||||
return {
|
||||
"name": self.name,
|
||||
"pin": self.pin,
|
||||
"interval": self.interval,
|
||||
"last_reading": self._last_reading,
|
||||
}
|
||||
72
src/esp_sensors/temperature.py
Normal file
72
src/esp_sensors/temperature.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Temperature sensor module for ESP-based sensors.
|
||||
"""
|
||||
import random
|
||||
from .sensor import Sensor
|
||||
|
||||
|
||||
class TemperatureSensor(Sensor):
|
||||
"""Temperature sensor implementation."""
|
||||
|
||||
def __init__(self, name: str, pin: int, interval: int = 60, unit: str = "C"):
|
||||
"""
|
||||
Initialize a new temperature 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)
|
||||
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
|
||||
|
||||
def read(self) -> float:
|
||||
"""
|
||||
Read the current temperature.
|
||||
|
||||
Returns:
|
||||
The temperature reading as a float
|
||||
"""
|
||||
# This is a simulation for testing purposes
|
||||
# In a real implementation, this would read from the actual sensor
|
||||
if self.unit == "C":
|
||||
self._last_reading = round(random.uniform(15.0, 30.0), 1)
|
||||
else:
|
||||
self._last_reading = round(random.uniform(59.0, 86.0), 1)
|
||||
return self._last_reading
|
||||
|
||||
def get_metadata(self):
|
||||
"""
|
||||
Get sensor metadata including temperature unit.
|
||||
|
||||
Returns:
|
||||
A dictionary containing sensor metadata
|
||||
"""
|
||||
metadata = super().get_metadata()
|
||||
metadata["unit"] = self.unit
|
||||
return metadata
|
||||
|
||||
def to_fahrenheit(self) -> float | None:
|
||||
"""
|
||||
Convert the last reading to Fahrenheit if it was in Celsius.
|
||||
|
||||
Returns:
|
||||
The temperature in Fahrenheit
|
||||
"""
|
||||
if self.unit == "F" or self._last_reading is None:
|
||||
return self._last_reading
|
||||
return (self._last_reading * 9 / 5) + 32
|
||||
|
||||
def to_celsius(self) -> float | None:
|
||||
"""
|
||||
Convert the last reading to Celsius if it was in Fahrenheit.
|
||||
|
||||
Returns:
|
||||
The temperature in Celsius
|
||||
"""
|
||||
if self.unit == "C" or self._last_reading is None:
|
||||
return self._last_reading
|
||||
return (self._last_reading - 32) * 5 / 9
|
||||
68
tests/test_temperature_sensor.py
Normal file
68
tests/test_temperature_sensor.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Tests for the temperature sensor module.
|
||||
"""
|
||||
import pytest
|
||||
from src.esp_sensors.temperature import TemperatureSensor
|
||||
|
||||
|
||||
def test_temperature_sensor_initialization():
|
||||
"""Test that a temperature sensor can be initialized with valid parameters."""
|
||||
sensor = TemperatureSensor("test_sensor", 5, 30, "C")
|
||||
assert sensor.name == "test_sensor"
|
||||
assert sensor.pin == 5
|
||||
assert sensor.interval == 30
|
||||
assert sensor.unit == "C"
|
||||
|
||||
|
||||
def test_temperature_sensor_invalid_unit():
|
||||
"""Test that initializing with an invalid unit raises a ValueError."""
|
||||
with pytest.raises(ValueError):
|
||||
TemperatureSensor("test_sensor", 5, 30, "K")
|
||||
|
||||
|
||||
def test_temperature_sensor_read():
|
||||
"""Test that reading from the sensor returns a float value."""
|
||||
sensor = TemperatureSensor("test_sensor", 5)
|
||||
reading = sensor.read()
|
||||
assert isinstance(reading, float)
|
||||
# For Celsius, the reading should be between 15.0 and 30.0
|
||||
assert 15.0 <= reading <= 30.0
|
||||
|
||||
|
||||
def test_temperature_sensor_fahrenheit():
|
||||
"""Test that a sensor initialized with Fahrenheit returns appropriate values."""
|
||||
sensor = TemperatureSensor("test_sensor", 5, unit="F")
|
||||
reading = sensor.read()
|
||||
assert isinstance(reading, float)
|
||||
# For Fahrenheit, the reading should be between 59.0 and 86.0
|
||||
assert 59.0 <= reading <= 86.0
|
||||
|
||||
|
||||
def test_temperature_conversion():
|
||||
"""Test temperature conversion methods."""
|
||||
# Test Celsius to Fahrenheit
|
||||
c_sensor = TemperatureSensor("celsius_sensor", 5, 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 = TemperatureSensor("fahrenheit_sensor", 5, 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
|
||||
|
||||
|
||||
def test_metadata():
|
||||
"""Test that metadata includes the temperature unit."""
|
||||
sensor = TemperatureSensor("test_sensor", 5, unit="C")
|
||||
metadata = sensor.get_metadata()
|
||||
assert metadata["name"] == "test_sensor"
|
||||
assert metadata["pin"] == 5
|
||||
assert metadata["unit"] == "C"
|
||||
assert metadata["last_reading"] is None # No reading yet
|
||||
|
||||
# After a reading
|
||||
sensor.read()
|
||||
metadata = sensor.get_metadata()
|
||||
assert metadata["last_reading"] is not None
|
||||
Reference in New Issue
Block a user