Source code for adafruit_tsl2561

# SPDX-FileCopyrightText: 2017 Carter Nelson for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
`adafruit_tsl2561`
====================================================

CircuitPython driver for TSL2561 Light Sensor.

* Author(s): Carter Nelson

Implementation Notes
--------------------

**Hardware:**

* Adafruit `TSL2561 Digital Luminosity/Lux/Light Sensor Breakout
  <https://www.adafruit.com/product/439>`_ (Product ID: 439)

* Adafruit `STEMMA - TSL2561 Digital Lux / Light Sensor
  <https://www.adafruit.com/product/3611>`_ (Product ID: 3611)

* Adafruit `Flora Lux Sensor - TSL2561 Light Sensor
  <https://www.adafruit.com/product/1246>`_ (Product ID: 1246)

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
  https://github.com/adafruit/circuitpython/releases
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""
from adafruit_bus_device.i2c_device import I2CDevice
from micropython import const

try:
    from typing import Optional, Tuple, Union
    from busio import I2C
except ImportError:
    pass

__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TSL2561.git"

_DEFAULT_ADDRESS = const(0x39)
_COMMAND_BIT = const(0x80)
_WORD_BIT = const(0x20)

_CONTROL_POWERON = const(0x03)
_CONTROL_POWEROFF = const(0x00)

_REGISTER_CONTROL = const(0x00)
_REGISTER_TIMING = const(0x01)
_REGISTER_TH_LOW = const(0x02)
_REGISTER_TH_HIGH = const(0x04)
_REGISTER_INT_CTRL = const(0x06)
_REGISTER_CHAN0_LOW = const(0x0C)
_REGISTER_CHAN1_LOW = const(0x0E)
_REGISTER_ID = const(0x0A)

_GAIN_SCALE = (16, 1)
_TIME_SCALE = (1 / 0.034, 1 / 0.252, 1)
_CLIP_THRESHOLD = (4900, 37000, 65000)


[docs] class TSL2561: """Class which provides interface to TSL2561 light sensor.""" def __init__(self, i2c: I2C, address: int = _DEFAULT_ADDRESS) -> None: self.buf = bytearray(3) self.i2c_device = I2CDevice(i2c, address) partno, revno = self.chip_id # data sheet says TSL2561 = 0001, reality says 0101 if not partno == 5: raise RuntimeError( f"Failed to find TSL2561! Part {hex(partno)} Rev {hex(revno)}" ) self.enabled = True @property def chip_id(self) -> Tuple[int, int]: """A tuple containing the part number and the revision number.""" chip_id = self._read_register(_REGISTER_ID) partno = (chip_id >> 4) & 0x0F revno = chip_id & 0x0F return (partno, revno) @property def enabled(self) -> bool: """The state of the sensor.""" return (self._read_register(_REGISTER_CONTROL) & 0x03) != 0 @enabled.setter def enabled(self, enable: bool) -> None: """Enable or disable the sensor.""" if enable: self._enable() else: self._disable() @property def lux(self) -> Optional[float]: """The computed lux value or None when value is not computable.""" return self._compute_lux() @property def broadband(self) -> int: """The broadband channel value.""" return self._read_broadband() @property def infrared(self) -> int: """The infrared channel value.""" return self._read_infrared() @property def luminosity(self) -> Tuple[int, int]: """The overall luminosity as a tuple containing the broadband channel and the infrared channel value.""" return (self.broadband, self.infrared) @property def gain(self) -> int: """The gain. 0:1x, 1:16x.""" return self._read_register(_REGISTER_TIMING) >> 4 & 0x01 @gain.setter def gain(self, value: int) -> None: """Set the gain. 0:1x, 1:16x.""" value &= 0x01 value <<= 4 current = self._read_register(_REGISTER_TIMING) self.buf[0] = _COMMAND_BIT | _REGISTER_TIMING self.buf[1] = (current & 0xEF) | value with self.i2c_device as i2c: i2c.write(self.buf, end=2) @property def integration_time(self) -> int: """The integration time. 0:13.7ms, 1:101ms, 2:402ms, or 3:manual""" current = self._read_register(_REGISTER_TIMING) return current & 0x03 @integration_time.setter def integration_time(self, value: int) -> None: """Set the integration time. 0:13.7ms, 1:101ms, 2:402ms, or 3:manual.""" value &= 0x03 current = self._read_register(_REGISTER_TIMING) self.buf[0] = _COMMAND_BIT | _REGISTER_TIMING self.buf[1] = (current & 0xFC) | value with self.i2c_device as i2c: i2c.write(self.buf, end=2) @property def threshold_low(self) -> int: """The low light interrupt threshold level.""" low, high = self._read_register(_REGISTER_TH_LOW, 2) return high << 8 | low @threshold_low.setter def threshold_low(self, value: int) -> None: self.buf[0] = _COMMAND_BIT | _WORD_BIT | _REGISTER_TH_LOW self.buf[1] = value & 0xFF self.buf[2] = (value >> 8) & 0xFF with self.i2c_device as i2c: i2c.write(self.buf) @property def threshold_high(self) -> int: """The upper light interrupt threshold level.""" low, high = self._read_register(_REGISTER_TH_HIGH, 2) return high << 8 | low @threshold_high.setter def threshold_high(self, value: int) -> None: self.buf[0] = _COMMAND_BIT | _WORD_BIT | _REGISTER_TH_HIGH self.buf[1] = value & 0xFF self.buf[2] = (value >> 8) & 0xFF with self.i2c_device as i2c: i2c.write(self.buf) @property def cycles(self) -> int: """The number of integration cycles for which an out of bounds value must persist to cause an interrupt.""" value = self._read_register(_REGISTER_INT_CTRL) return value & 0x0F @cycles.setter def cycles(self, value: int) -> None: current = self._read_register(_REGISTER_INT_CTRL) self.buf[0] = _COMMAND_BIT | _REGISTER_INT_CTRL self.buf[1] = current | (value & 0x0F) with self.i2c_device as i2c: i2c.write(self.buf, end=2) @property def interrupt_mode(self) -> int: """The interrupt mode selection. ==== ========================= Mode Description ==== ========================= 0 Interrupt output disabled 1 Level Interrupt 2 SMBAlert compliant 3 Test Mode ==== ========================= """ return (self._read_register(_REGISTER_INT_CTRL) >> 4) & 0x03 @interrupt_mode.setter def interrupt_mode(self, value: int) -> None: current = self._read_register(_REGISTER_INT_CTRL) self.buf[0] = _COMMAND_BIT | _REGISTER_INT_CTRL self.buf[1] = (current & 0x0F) | ((value & 0x03) << 4) with self.i2c_device as i2c: i2c.write(self.buf, end=2)
[docs] def clear_interrupt(self) -> None: """Clears any pending interrupt.""" self.buf[0] = 0xC0 with self.i2c_device as i2c: i2c.write(self.buf, end=1)
def _compute_lux(self) -> Optional[float]: """Based on datasheet for FN package.""" ch0, ch1 = self.luminosity if ch0 == 0: return None if ch0 > _CLIP_THRESHOLD[self.integration_time]: return None if ch1 > _CLIP_THRESHOLD[self.integration_time]: return None ratio = ch1 / ch0 if 0 <= ratio <= 0.50: lux = 0.0304 * ch0 - 0.062 * ch0 * ratio**1.4 elif ratio <= 0.61: lux = 0.0224 * ch0 - 0.031 * ch1 elif ratio <= 0.80: lux = 0.0128 * ch0 - 0.0153 * ch1 elif ratio <= 1.30: lux = 0.00146 * ch0 - 0.00112 * ch1 else: lux = 0.0 # Pretty sure the floating point math formula on pg. 23 of datasheet # is based on 16x gain and 402ms integration time. Need to scale # result for other settings. # Scale for gain. lux *= _GAIN_SCALE[self.gain] # Scale for integration time. lux *= _TIME_SCALE[self.integration_time] return lux def _enable(self) -> None: self._write_control_register(_CONTROL_POWERON) def _disable(self) -> None: self._write_control_register(_CONTROL_POWEROFF) def _read_register(self, reg: int, count: int = 1) -> Union[int, Tuple[int, int]]: if count not in (1, 2): raise RuntimeError("Can only read up to 2 consecutive registers") self.buf[0] = _COMMAND_BIT | reg if count == 2: self.buf[0] |= _WORD_BIT with self.i2c_device as i2c: i2c.write_then_readinto(self.buf, self.buf, out_end=1, in_start=1) if count == 1: return self.buf[1] return self.buf[1], self.buf[2] def _write_control_register(self, reg: int) -> None: self.buf[0] = _COMMAND_BIT | _REGISTER_CONTROL self.buf[1] = reg with self.i2c_device as i2c: i2c.write(self.buf, end=2) def _read_broadband(self) -> int: low, high = self._read_register(_REGISTER_CHAN0_LOW, 2) return high << 8 | low def _read_infrared(self) -> int: low, high = self._read_register(_REGISTER_CHAN1_LOW, 2) return high << 8 | low