Skip to content

Commit bc7eb68

Browse files
Mikefly123Copilotineskhounateinaction
authored
Introduce VEML7700 Light Sensor Manager (#285)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: ineskhou <ik2512@columbia.edu> Co-authored-by: ineskhou <127782958+ineskhou@users.noreply.github.com> Co-authored-by: Nate Gay <email@n8.gay> Co-authored-by: Nate Gay <email@nategay.me>
1 parent 04dfa3f commit bc7eb68

File tree

11 files changed

+677
-5
lines changed

11 files changed

+677
-5
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ dependencies = [
1616
"adafruit-circuitpython-rfm==1.0.6",
1717
"adafruit-circuitpython-tca9548a @ git+https://github.com/proveskit/Adafruit_CircuitPython_TCA9548A@1.0.0",
1818
"adafruit-circuitpython-ticks==1.1.1",
19-
"adafruit-circuitpython-veml7700==2.0.2",
19+
"adafruit-circuitpython-veml7700==2.1.4",
2020
"proves-circuitpython-rv3028 @ git+https://github.com/proveskit/PROVES_CircuitPython_RV3028@1.0.1",
2121
"proves-circuitpython-sx126 @ git+https://github.com/proveskit/micropySX126X@1.0.0",
2222
"proves-circuitpython-sx1280 @ git+https://github.com/proveskit/CircuitPython_SX1280@1.0.4",
@@ -59,6 +59,7 @@ packages = [
5959
"pysquared.hardware.temperature_sensor.manager",
6060
"pysquared.hardware.burnwire",
6161
"pysquared.hardware.burnwire.manager",
62+
"pysquared.hardware.light_sensor.manager",
6263
"pysquared.nvm",
6364
"pysquared.protos",
6465
"pysquared.rtc",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
This module provides an interface to the light sensors.
3+
"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
These managers interface with specific light sensor implementations.
3+
"""
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
"""This module defines the `VEML7700Manager` class, which provides a high-level interface
2+
for interacting with the VEML7700 light sensor. It handles the initialization of the sensor
3+
and provides methods for reading light levels in various formats.
4+
5+
**Usage:**
6+
```python
7+
logger = Logger()
8+
i2c = busio.I2C(board.SCL, board.SDA)
9+
i2c = initialize_i2c_bus(logger, board.SCL, board.SDA, 100000,)
10+
light_sensor = VEML7700Manager(logger, i2c)
11+
lux_data = light_sensor.get_lux()
12+
```
13+
"""
14+
15+
import time
16+
17+
from adafruit_tca9548a import TCA9548A_Channel
18+
from adafruit_veml7700 import VEML7700
19+
from busio import I2C
20+
21+
from ....logger import Logger
22+
from ....protos.light_sensor import LightSensorProto
23+
from ....sensor_reading.error import (
24+
SensorReadingUnknownError,
25+
SensorReadingValueError,
26+
)
27+
from ....sensor_reading.light import Light
28+
from ....sensor_reading.lux import Lux
29+
from ...exception import HardwareInitializationError
30+
31+
try:
32+
from typing import Literal
33+
except ImportError:
34+
pass
35+
36+
37+
class VEML7700Manager(LightSensorProto):
38+
"""Manages the VEML7700 ambient light sensor."""
39+
40+
def __init__(
41+
self,
42+
logger: Logger,
43+
i2c: I2C | TCA9548A_Channel,
44+
integration_time: Literal[0, 1, 2, 3, 8, 12] = 12,
45+
) -> None:
46+
"""Initializes the VEML7700Manager.
47+
48+
Args:
49+
logger: The logger to use.
50+
i2c: The I2C bus connected to the chip.
51+
integration_time: The integration time for the light sensor (default is 25ms).
52+
Integration times can be one of the following:
53+
- 12: 25ms
54+
- 8: 50ms
55+
- 0: 100ms
56+
- 1: 200ms
57+
- 2: 400ms
58+
- 3: 800ms
59+
60+
Raises:
61+
HardwareInitializationError: If the light sensor fails to initialize.
62+
"""
63+
self._log: Logger = logger
64+
self._i2c: I2C | TCA9548A_Channel = i2c
65+
66+
try:
67+
self._log.debug("Initializing light sensor")
68+
self._light_sensor: VEML7700 = VEML7700(i2c)
69+
self._light_sensor.light_integration_time = integration_time
70+
except Exception as e:
71+
raise HardwareInitializationError(
72+
"Failed to initialize light sensor"
73+
) from e
74+
75+
def get_light(self) -> Light:
76+
"""Gets the light reading of the sensor with default gain and integration time.
77+
78+
Returns:
79+
A Light object containing a non-unit-specific light level reading.
80+
81+
Raises:
82+
SensorReadingUnknownError: If an unknown error occurs while reading the sensor.
83+
"""
84+
try:
85+
return Light(self._light_sensor.light)
86+
except Exception as e:
87+
raise SensorReadingUnknownError("Failed to get light reading") from e
88+
89+
def get_lux(self) -> Lux:
90+
"""Gets the light reading of the sensor with default gain and integration time.
91+
92+
Returns:
93+
A Lux object containing the light level in SI lux.
94+
95+
Raises:
96+
SensorReadingValueError: If the reading returns an invalid value.
97+
SensorReadingUnknownError: If an unknown error occurs while reading the sensor.
98+
"""
99+
try:
100+
lux = self._light_sensor.lux
101+
except Exception as e:
102+
raise SensorReadingUnknownError("Failed to get lux reading") from e
103+
104+
if self._is_invalid_lux(lux):
105+
raise SensorReadingValueError("Lux reading is invalid or zero")
106+
107+
return Lux(lux)
108+
109+
def get_auto_lux(self) -> Lux:
110+
"""Gets the auto lux reading of the sensor. This runs the sensor in auto mode
111+
and returns the lux value by searching through the available gain and integration time
112+
combinations to find the best match.
113+
114+
Returns:
115+
A Lux object containing the light level in SI lux.
116+
117+
Raises:
118+
SensorReadingValueError: If the reading returns an invalid value.
119+
SensorReadingUnknownError: If an unknown error occurs while reading the sensor.
120+
"""
121+
try:
122+
lux = self._light_sensor.autolux
123+
except Exception as e:
124+
raise SensorReadingUnknownError("Failed to get auto lux reading") from e
125+
126+
if self._is_invalid_lux(lux):
127+
raise SensorReadingValueError("Lux reading is invalid or zero")
128+
129+
return Lux(lux)
130+
131+
@staticmethod
132+
def _is_invalid_lux(lux: float | None) -> bool:
133+
"""Determines if the given lux reading is invalid or zero.
134+
Args:
135+
lux (float | None): The lux reading to validate. It can be a float representing
136+
the light level in SI lux, or None if no reading is available.
137+
Returns:
138+
bool: True if the lux reading is invalid (None or zero), False otherwise.
139+
"""
140+
return lux is None or lux == 0
141+
142+
def reset(self) -> None:
143+
"""Resets the light sensor."""
144+
try:
145+
self._light_sensor.light_shutdown = True
146+
time.sleep(0.1) # Allow time for the sensor to reset
147+
self._light_sensor.light_shutdown = False
148+
self._log.debug("Light sensor reset successfully")
149+
except Exception as e:
150+
self._log.error("Failed to reset light sensor:", e)

pysquared/protos/light_sensor.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""This protocol specifies the interface that any light sensor implementation
2+
must adhere to, ensuring consistent behavior across different light sensor
3+
hardware.
4+
"""
5+
6+
from ..sensor_reading.light import Light
7+
from ..sensor_reading.lux import Lux
8+
9+
10+
class LightSensorProto:
11+
"""Protocol defining the interface for a light sensor."""
12+
13+
def get_light(self) -> Light:
14+
"""Gets the light reading of the sensor.
15+
16+
Returns:
17+
A Light object containing a non-unit-specific light level reading.
18+
19+
Raises:
20+
SensorReadingValueError: If the reading returns an invalid value.
21+
SensorReadingTimeoutError: If the reading times out.
22+
SensorReadingUnknownError: If an unknown error occurs while reading the light sensor.
23+
"""
24+
...
25+
26+
def get_lux(self) -> Lux:
27+
"""Gets the light reading of the sensor.
28+
29+
Returns:
30+
A Lux object containing the light level in SI lux.
31+
32+
Raises:
33+
SensorReadingValueError: If the reading returns an invalid value.
34+
SensorReadingTimeoutError: If the reading times out.
35+
SensorReadingUnknownError: If an unknown error occurs while reading the light sensor.
36+
"""
37+
...

pysquared/sensor_reading/light.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Light sensor reading."""
2+
3+
from .base import Reading
4+
5+
6+
class Light(Reading):
7+
"""Light sensor reading (non-unit-specific light levels)."""
8+
9+
value: float
10+
"""Light level (non-unit-specific)."""
11+
12+
def __init__(self, value: float) -> None:
13+
"""Initialize the light sensor reading.
14+
15+
Args:
16+
value: The light level (non-unit-specific)
17+
"""
18+
super().__init__()
19+
self.value = value

pysquared/sensor_reading/lux.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Lux sensor reading."""
2+
3+
from .base import Reading
4+
5+
6+
class Lux(Reading):
7+
"""Lux sensor reading in SI lux."""
8+
9+
value: float
10+
"""Light level in SI lux."""
11+
12+
def __init__(self, value: float) -> None:
13+
"""Initialize the lux sensor reading.
14+
15+
Args:
16+
value: The light level in SI lux
17+
"""
18+
super().__init__()
19+
self.value = value

0 commit comments

Comments
 (0)