# SPDX-FileCopyrightText: 2017 Tony DiCola for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_tsl2591`
====================================================
CircuitPython module for the TSL2591 precision light sensor. See
examples/simpletest.py for a demo of the usage.
* Author(s): Tony DiCola
Implementation Notes
--------------------
**Hardware:**
* Adafruit `TSL2591 High Dynamic Range Digital Light Sensor
<https://www.adafruit.com/product/1980>`_ (Product ID: 1980)
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://circuitpython.org/downloads
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""
from adafruit_bus_device import i2c_device
from micropython import const
try:
from typing import Tuple
from busio import I2C
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TSL2591.git"
# Internal constants:
_TSL2591_ADDR = const(0x29)
_TSL2591_COMMAND_BIT = const(0xA0)
_TSL2591_SPECIAL_BIT = const(0xE0)
_TSL2591_ENABLE_POWEROFF = const(0x00)
_TSL2591_ENABLE_POWERON = const(0x01)
_TSL2591_ENABLE_AEN = const(0x02)
_TSL2591_REGISTER_ENABLE = const(0x00)
_TSL2591_REGISTER_CONTROL = const(0x01)
_TSL2591_AILTL = const(0x04)
_TSL2591_AILTH = const(0x05)
_TSL2591_AIHTL = const(0x06)
_TSL2591_AIHTH = const(0x07)
_TSL2591_NPAILTL = const(0x08)
_TSL2591_NPAILTH = const(0x09)
_TSL2591_NPAIHTL = const(0x0A)
_TSL2591_NPAIHTH = const(0x0B)
_TSL2591_PERSIST_FILTER = const(0x0C)
_TSL2591_REGISTER_DEVICE_ID = const(0x12)
_TSL2591_REGISTER_CHAN0_LOW = const(0x14)
_TSL2591_REGISTER_CHAN1_LOW = const(0x16)
_TSL2591_LUX_DF = 408.0
_TSL2591_LUX_COEFB = 1.64
_TSL2591_LUX_COEFC = 0.59
_TSL2591_LUX_COEFD = 0.86
_TSL2591_MAX_COUNT_100MS = const(36863) # 0x8FFF
_TSL2591_MAX_COUNT = const(65535) # 0xFFFF
# User-facing constants:
GAIN_LOW = 0x00 # low gain (1x)
"""Low gain (1x)"""
GAIN_MED = 0x10 # medium gain (25x)
"""Medium gain (25x)"""
GAIN_HIGH = 0x20 # medium gain (428x)
"""High gain (428x)"""
GAIN_MAX = 0x30 # max gain (9876x)
"""Max gain (9876x)"""
INTEGRATIONTIME_100MS = 0x00 # 100 millis
"""100 millis"""
INTEGRATIONTIME_200MS = 0x01 # 200 millis
"""200 millis"""
INTEGRATIONTIME_300MS = 0x02 # 300 millis
"""300 millis"""
INTEGRATIONTIME_400MS = 0x03 # 400 millis
"""400 millis"""
INTEGRATIONTIME_500MS = 0x04 # 500 millis
"""500 millis"""
INTEGRATIONTIME_600MS = 0x05 # 600 millis
"""600 millis"""
CLEAR_INTERRUPT = const(0x06)
"""Clears ALS interrupt"""
CLEAR_ALL_INTERRUPTS = const(0x07)
"""Clears ALS and no persist ALS interrupt"""
CLEAR_PERSIST_INTERRUPT = const(0x0A)
"""Clears no persist ALS interrupt"""
ENABLE_AIEN = const(0x10)
"""ALS Interrupt Enable. When asserted permits ALS interrupts to be generated."""
ENABLE_NPIEN = const(0x80)
"""No Persist Interrupt Enable. When asserted NP Threshold conditions will generate an interrupt."""
ENABLE_NPAIEN = const(0x10 | 0x80)
"""ALS and No Persist Interrupt Enable."""
[docs]
class TSL2591:
"""TSL2591 high precision light sensor.
:param ~busio.I2C i2c: The I2C bus the device is connected to
:param int address: The I2C device address. Defaults to :const:`0x29`
**Quickstart: Importing and using the device**
Here is an example of using the :class:`TSL2591` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
import board
import adafruit_tsl2591
Once this is done you can define your `board.I2C` object and define your sensor object
.. code-block:: python
i2c = board.I2C() # uses board.SCL and board.SDA
sensor = adafruit_tsl2591.TSL2591(i2c)
Now you have access to the :attr:`lux`, :attr:`infrared`
:attr:`visible` and :attr:`full_spectrum` attributes
.. code-block:: python
lux = sensor.lux
infrared = sensor.infrared
visible = sensor.visible
full_spectrum = sensor.full_spectrum
"""
# Class-level buffer to reduce memory usage and allocations.
# Note this is NOT thread-safe or re-entrant by design.
_BUFFER = bytearray(2)
def __init__(self, i2c: I2C, address: int = _TSL2591_ADDR) -> None:
self._integration_time = 0
self._gain = 0
self._device = i2c_device.I2CDevice(i2c, address)
# Verify the chip ID.
if self._read_u8(_TSL2591_REGISTER_DEVICE_ID) != 0x50:
raise RuntimeError("Failed to find TSL2591, check wiring!")
# Set default gain and integration times.
self.gain = GAIN_MED
self.integration_time = INTEGRATIONTIME_100MS
# Put the device in a powered on state after initialization.
self.enable()
def _read_u8(self, address: int) -> int:
# Read an 8-bit unsigned value from the specified 8-bit address.
with self._device as i2c:
# Make sure to add command bit to read request.
self._BUFFER[0] = (_TSL2591_COMMAND_BIT | address) & 0xFF
i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=1)
return self._BUFFER[0]
def _read_u16LE(self, address: int) -> int:
# Read a 16-bit little-endian unsigned value from the specified 8-bit
# address.
with self._device as i2c:
# Make sure to add command bit to read request.
self._BUFFER[0] = (_TSL2591_COMMAND_BIT | address) & 0xFF
i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=2)
return (self._BUFFER[1] << 8) | self._BUFFER[0]
def _write_u8(self, address: int, val: int) -> None:
# Write an 8-bit unsigned value to the specified 8-bit address.
with self._device as i2c:
# Make sure to add command bit to write request.
self._BUFFER[0] = (_TSL2591_COMMAND_BIT | address) & 0xFF
self._BUFFER[1] = val & 0xFF
i2c.write(self._BUFFER, end=2)
[docs]
def enable(self) -> None:
"""Put the device in a powered, enabled mode."""
self._write_u8(
_TSL2591_REGISTER_ENABLE,
_TSL2591_ENABLE_POWERON | _TSL2591_ENABLE_AEN,
)
[docs]
def disable(self) -> None:
"""Disable the device and go into low power mode."""
self._write_u8(_TSL2591_REGISTER_ENABLE, _TSL2591_ENABLE_POWEROFF)
[docs]
def clear_interrupt(self, operation: int) -> None:
"""Send special function command to control interrupt bits in the status register (0x13).
Can be a value of:
- ``CLEAR_INTERRUPT``
- ``CLEAR_ALL_INTERRUPTS``
- ``CLEAR_PERSIST_INTERRUPT``
"""
assert operation in {
CLEAR_INTERRUPT,
CLEAR_ALL_INTERRUPTS,
CLEAR_PERSIST_INTERRUPT,
}
control = (_TSL2591_SPECIAL_BIT | operation) & 0xFF
with self._device as i2c:
self._BUFFER[0] = control
i2c.write(self._BUFFER, end=1)
[docs]
def enable_interrupt(self, interrupts: int) -> None:
"""Enable interrupts on device. ENABLE_NPIEN will turn on No Persist interrupts, these
bypass the persist filter and assert immediately when values are detected above the high
threshold or below the low threshold. Similarly, ENABLE_AIEN will assert at the respective
ALS thresholds, but only after the values persist longer than the persist filter cycle
duration. The device powers on with thresholds at 0, meaning enabling interrupts may
cause an immediate assertion.
Can be a value of:
- ``ENABLE_NPIEN``
- ``ENABLE_AIEN``
- ``ENABLE_NPAIEN``
"""
assert interrupts in {ENABLE_NPIEN, ENABLE_AIEN, (ENABLE_NPIEN | ENABLE_AIEN)}
functions = self._read_u8(_TSL2591_REGISTER_ENABLE)
functions = (functions | interrupts) & 0xFF
self._write_u8(_TSL2591_REGISTER_ENABLE, functions)
[docs]
def disable_interrupt(self, interrupts: int) -> None:
"""Disables the requested interrupts.
Can be a value of:
- ``ENABLE_NPIEN``
- ``ENABLE_AIEN``
- ``ENABLE_NPAIEN``
"""
assert interrupts in {ENABLE_NPIEN, ENABLE_AIEN, (ENABLE_NPIEN | ENABLE_AIEN)}
functions = self._read_u8(_TSL2591_REGISTER_ENABLE)
functions = (functions & ~interrupts) & 0xFF
self._write_u8(_TSL2591_REGISTER_ENABLE, functions)
@property
def gain(self) -> int:
"""Get and set the gain of the sensor. Can be a value of:
- ``GAIN_LOW`` (1x)
- ``GAIN_MED`` (25x)
- ``GAIN_HIGH`` (428x)
- ``GAIN_MAX`` (9876x)
"""
control = self._read_u8(_TSL2591_REGISTER_CONTROL)
return control & 0b00110000
@gain.setter
def gain(self, val: int) -> None:
assert val in {GAIN_LOW, GAIN_MED, GAIN_HIGH, GAIN_MAX}
# Set appropriate gain value.
control = self._read_u8(_TSL2591_REGISTER_CONTROL)
control &= 0b11001111
control |= val
self._write_u8(_TSL2591_REGISTER_CONTROL, control)
# Keep track of gain for future lux calculations.
self._gain = val
@property
def integration_time(self) -> int:
"""Get and set the integration time of the sensor. Can be a value of:
- ``INTEGRATIONTIME_100MS`` (100 millis)
- ``INTEGRATIONTIME_200MS`` (200 millis)
- ``INTEGRATIONTIME_300MS`` (300 millis)
- ``INTEGRATIONTIME_400MS`` (400 millis)
- ``INTEGRATIONTIME_500MS`` (500 millis)
- ``INTEGRATIONTIME_600MS`` (600 millis)
"""
control = self._read_u8(_TSL2591_REGISTER_CONTROL)
return control & 0b00000111
@integration_time.setter
def integration_time(self, val: int) -> None:
assert 0 <= val <= 5
# Set control bits appropriately.
control = self._read_u8(_TSL2591_REGISTER_CONTROL)
control &= 0b11111000
control |= val
self._write_u8(_TSL2591_REGISTER_CONTROL, control)
# Keep track of integration time for future reading delay times.
self._integration_time = val
@property
def threshold_low(self) -> int:
"""Get and set the ALS interrupt low threshold bytes. If the detected value is below
the low threshold for the number of persist filter cycles an interrupt will be triggered.
Can be 16-bit value."""
th_low = self._read_u16LE(_TSL2591_AILTL)
return th_low
@threshold_low.setter
def threshold_low(self, val: int) -> None:
lower = val & 0xFF
upper = (val >> 8) & 0xFF
self._write_u8(_TSL2591_AILTL, lower)
self._write_u8(_TSL2591_AILTH, upper)
@property
def threshold_high(self) -> int:
"""Get and set the ALS interrupt high threshold bytes. If the detected value is above
the high threshold for the number of persist filter cycles an interrupt will be triggered.
Can be 16-bit value."""
th_high = self._read_u16LE(_TSL2591_AIHTL)
return th_high
@threshold_high.setter
def threshold_high(self, val: int) -> None:
lower = val & 0xFF
upper = (val >> 8) & 0xFF
self._write_u8(_TSL2591_AIHTL, lower)
self._write_u8(_TSL2591_AIHTH, upper)
@property
def nopersist_threshold_low(self) -> int:
"""Get and set the No Persist ALS low threshold bytes. An interrupt will be triggered
immediately once the detected value is below the low threshold. Can be 16-bit value.
"""
np_th_low = self._read_u16LE(_TSL2591_NPAILTL)
return np_th_low
@nopersist_threshold_low.setter
def nopersist_threshold_low(self, val: int) -> None:
lower = val & 0xFF
upper = (val >> 8) & 0xFF
self._write_u8(_TSL2591_NPAILTL, lower)
self._write_u8(_TSL2591_NPAILTH, upper)
@property
def nopersist_threshold_high(self) -> int:
"""Get and set the No Persist ALS high threshold bytes. An interrupt will be triggered
immediately once the detected value is above the high threshold. Can be 16-bit value.
"""
np_th_high = self._read_u16LE(_TSL2591_NPAIHTL)
return np_th_high
@nopersist_threshold_high.setter
def nopersist_threshold_high(self, val: int) -> None:
lower = val & 0xFF
upper = (val >> 8) & 0xFF
self._write_u8(_TSL2591_NPAIHTL, lower)
self._write_u8(_TSL2591_NPAIHTH, upper)
@property
def persist(self) -> int:
"""Get and set the interrupt persist filter - the number of consecutive out-of-range
ALS cycles necessary to generate an interrupt. Valid persist values are 0 - 15 (inclusive),
corresponding to a preset number of cycles. Only the 4 lower bits will be used to write
to the device.
Can be a value of:
- ``0 (0000)`` - Every ALS cycle generates an interrupt.
- ``1 (0001)`` - Any value outside of threshold range.
- ``2 (0010)`` - 2 consecutive values out of range.
- ``3 (0011)`` - 3 consecutive values out of range.
- ``4 (0100)`` - 5 consecutive values out of range.
- ``5 (0101)`` - 10 consecutive values out of range.
- ``6 (0110)`` - 15 consecutive values out of range.
- ``7 (0111)`` - 20 consecutive values out of range.
- ``8 (1000)`` - 25 consecutive values out of range.
- ``9 (1001)`` - 30 consecutive values out of range.
- ``10 (1010)`` - 35 consecutive values out of range.
- ``11 (1011)`` - 40 consecutive values out of range.
- ``12 (1100)`` - 45 consecutive values out of range.
- ``13 (1101)`` - 50 consecutive values out of range.
- ``14 (1110)`` - 55 consecutive values out of range.
- ``15 (1111)`` - 60 consecutive values out of range.
"""
persist = self._read_u8(_TSL2591_PERSIST_FILTER)
return persist & 0x0F
@persist.setter
def persist(self, val: int) -> None:
assert 0 <= val <= 15
persist = val & 0x0F
self._write_u8(_TSL2591_PERSIST_FILTER, persist)
@property
def raw_luminosity(self) -> Tuple[int, int]:
"""Read the raw luminosity from the sensor (both IR + visible and IR
only channels) and return a 2-tuple of those values. The first value
is IR + visible luminosity (channel 0) and the second is the IR only
(channel 1). Both values are 16-bit unsigned numbers (0-65535).
"""
# Read both the luminosity channels.
channel_0 = self._read_u16LE(_TSL2591_REGISTER_CHAN0_LOW)
channel_1 = self._read_u16LE(_TSL2591_REGISTER_CHAN1_LOW)
return (channel_0, channel_1)
@property
def full_spectrum(self) -> int:
"""Read the full spectrum (IR + visible) light and return its value
as a 16-bit unsigned number.
"""
return self.raw_luminosity[0]
@property
def infrared(self) -> int:
"""Read the infrared light and return its value as a 16-bit unsigned number."""
_, channel_1 = self.raw_luminosity
return channel_1
@property
def visible(self) -> int:
"""Read the visible light and return its value as a 16-bit unsigned number."""
channel_0, channel_1 = self.raw_luminosity
return channel_0 - channel_1
@property
def lux(self) -> float:
"""Read the sensor and calculate a lux value from both its infrared
and visible light channels.
.. note::
:attr:`lux` is not calibrated!
"""
channel_0, channel_1 = self.raw_luminosity
# Compute the atime in milliseconds
atime = 100.0 * self._integration_time + 100.0
# Set the maximum sensor counts based on the integration time (atime) setting
if self._integration_time == INTEGRATIONTIME_100MS:
max_counts = _TSL2591_MAX_COUNT_100MS
else:
max_counts = _TSL2591_MAX_COUNT
# Handle overflow.
if channel_0 >= max_counts or channel_1 >= max_counts:
message = (
"Overflow reading light channels!, Try to reduce the gain of\n "
+ "the sensor using adafruit_tsl2591.GAIN_LOW"
)
raise RuntimeError(message)
# Calculate lux using same equation as Arduino library:
# https://github.com/adafruit/Adafruit_TSL2591_Library/blob/master/Adafruit_TSL2591.cpp
again = 1.0
if self._gain == GAIN_MED:
again = 25.0
elif self._gain == GAIN_HIGH:
again = 428.0
elif self._gain == GAIN_MAX:
again = 9876.0
cpl = (atime * again) / _TSL2591_LUX_DF
lux1 = (channel_0 - (_TSL2591_LUX_COEFB * channel_1)) / cpl
lux2 = ((_TSL2591_LUX_COEFC * channel_0) - (_TSL2591_LUX_COEFD * channel_1)) / cpl
return max(lux1, lux2)