/ adafruit_tsl2591.py
adafruit_tsl2591.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2017 Tony DiCola for Adafruit Industries
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  """
 23  `adafruit_tsl2591`
 24  ====================================================
 25  
 26  CircuitPython module for the TSL2591 precision light sensor.  See
 27  examples/simpletest.py for a demo of the usage.
 28  
 29  * Author(s): Tony DiCola
 30  
 31  Implementation Notes
 32  --------------------
 33  
 34  **Hardware:**
 35  
 36  * Adafruit `TSL2591 High Dynamic Range Digital Light Sensor
 37    <https://www.adafruit.com/product/1980>`_ (Product ID: 1980)
 38  
 39  **Software and Dependencies:**
 40  
 41  * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
 42    https://github.com/adafruit/circuitpython/releases
 43  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 44  """
 45  from micropython import const
 46  
 47  import adafruit_bus_device.i2c_device as i2c_device
 48  
 49  
 50  __version__ = "0.0.0-auto.0"
 51  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TSL2591.git"
 52  
 53  
 54  # pylint: disable=bad-whitespace
 55  # Internal constants:
 56  _TSL2591_ADDR = const(0x29)
 57  _TSL2591_COMMAND_BIT = const(0xA0)
 58  _TSL2591_ENABLE_POWEROFF = const(0x00)
 59  _TSL2591_ENABLE_POWERON = const(0x01)
 60  _TSL2591_ENABLE_AEN = const(0x02)
 61  _TSL2591_ENABLE_AIEN = const(0x10)
 62  _TSL2591_ENABLE_NPIEN = const(0x80)
 63  _TSL2591_REGISTER_ENABLE = const(0x00)
 64  _TSL2591_REGISTER_CONTROL = const(0x01)
 65  _TSL2591_REGISTER_DEVICE_ID = const(0x12)
 66  _TSL2591_REGISTER_CHAN0_LOW = const(0x14)
 67  _TSL2591_REGISTER_CHAN1_LOW = const(0x16)
 68  _TSL2591_LUX_DF = 408.0
 69  _TSL2591_LUX_COEFB = 1.64
 70  _TSL2591_LUX_COEFC = 0.59
 71  _TSL2591_LUX_COEFD = 0.86
 72  _TSL2591_MAX_COUNT_100MS = const(36863)  # 0x8FFF
 73  _TSL2591_MAX_COUNT = const(65535)  # 0xFFFF
 74  
 75  # User-facing constants:
 76  GAIN_LOW = 0x00  # low gain (1x)
 77  """Low gain (1x)"""
 78  GAIN_MED = 0x10  # medium gain (25x)
 79  """Medium gain (25x)"""
 80  GAIN_HIGH = 0x20  # medium gain (428x)
 81  """High gain (428x)"""
 82  GAIN_MAX = 0x30  # max gain (9876x)
 83  """Max gain (9876x)"""
 84  INTEGRATIONTIME_100MS = 0x00  # 100 millis
 85  """100 millis"""
 86  INTEGRATIONTIME_200MS = 0x01  # 200 millis
 87  """200 millis"""
 88  INTEGRATIONTIME_300MS = 0x02  # 300 millis
 89  """300 millis"""
 90  INTEGRATIONTIME_400MS = 0x03  # 400 millis
 91  """400 millis"""
 92  INTEGRATIONTIME_500MS = 0x04  # 500 millis
 93  """500 millis"""
 94  INTEGRATIONTIME_600MS = 0x05  # 600 millis
 95  """600 millis"""
 96  # pylint: enable=bad-whitespace
 97  
 98  
 99  class TSL2591:
100      """TSL2591 high precision light sensor.
101          :param busio.I2C i2c: The I2C bus connected to the sensor
102          :param int address: The I2C address of the sensor.  If not specified
103          the sensor default will be used.
104      """
105  
106      # Class-level buffer to reduce memory usage and allocations.
107      # Note this is NOT thread-safe or re-entrant by design.
108      _BUFFER = bytearray(2)
109  
110      def __init__(self, i2c, address=_TSL2591_ADDR):
111          self._integration_time = 0
112          self._gain = 0
113          self._device = i2c_device.I2CDevice(i2c, address)
114          # Verify the chip ID.
115          if self._read_u8(_TSL2591_REGISTER_DEVICE_ID) != 0x50:
116              raise RuntimeError("Failed to find TSL2591, check wiring!")
117          # Set default gain and integration times.
118          self.gain = GAIN_MED
119          self.integration_time = INTEGRATIONTIME_100MS
120          # Put the device in a powered on state after initialization.
121          self.enable()
122  
123      def _read_u8(self, address):
124          # Read an 8-bit unsigned value from the specified 8-bit address.
125          with self._device as i2c:
126              # Make sure to add command bit to read request.
127              self._BUFFER[0] = (_TSL2591_COMMAND_BIT | address) & 0xFF
128              i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=1)
129          return self._BUFFER[0]
130  
131      # Disable invalid name check since pylint isn't smart enough to know LE
132      # is an abbreviation for little-endian.
133      # pylint: disable=invalid-name
134      def _read_u16LE(self, address):
135          # Read a 16-bit little-endian unsigned value from the specified 8-bit
136          # address.
137          with self._device as i2c:
138              # Make sure to add command bit to read request.
139              self._BUFFER[0] = (_TSL2591_COMMAND_BIT | address) & 0xFF
140              i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=2)
141          return (self._BUFFER[1] << 8) | self._BUFFER[0]
142  
143      # pylint: enable=invalid-name
144  
145      def _write_u8(self, address, val):
146          # Write an 8-bit unsigned value to the specified 8-bit address.
147          with self._device as i2c:
148              # Make sure to add command bit to write request.
149              self._BUFFER[0] = (_TSL2591_COMMAND_BIT | address) & 0xFF
150              self._BUFFER[1] = val & 0xFF
151              i2c.write(self._BUFFER, end=2)
152  
153      def enable(self):
154          """Put the device in a fully powered enabled mode."""
155          self._write_u8(
156              _TSL2591_REGISTER_ENABLE,
157              _TSL2591_ENABLE_POWERON
158              | _TSL2591_ENABLE_AEN
159              | _TSL2591_ENABLE_AIEN
160              | _TSL2591_ENABLE_NPIEN,
161          )
162  
163      def disable(self):
164          """Disable the device and go into low power mode."""
165          self._write_u8(_TSL2591_REGISTER_ENABLE, _TSL2591_ENABLE_POWEROFF)
166  
167      @property
168      def gain(self):
169          """Get and set the gain of the sensor.  Can be a value of:
170  
171          - ``GAIN_LOW`` (1x)
172          - ``GAIN_MED`` (25x)
173          - ``GAIN_HIGH`` (428x)
174          - ``GAIN_MAX`` (9876x)
175          """
176          control = self._read_u8(_TSL2591_REGISTER_CONTROL)
177          return control & 0b00110000
178  
179      @gain.setter
180      def gain(self, val):
181          assert val in (GAIN_LOW, GAIN_MED, GAIN_HIGH, GAIN_MAX)
182          # Set appropriate gain value.
183          control = self._read_u8(_TSL2591_REGISTER_CONTROL)
184          control &= 0b11001111
185          control |= val
186          self._write_u8(_TSL2591_REGISTER_CONTROL, control)
187          # Keep track of gain for future lux calculations.
188          self._gain = val
189  
190      @property
191      def integration_time(self):
192          """Get and set the integration time of the sensor.  Can be a value of:
193  
194          - ``INTEGRATIONTIME_100MS`` (100 millis)
195          - ``INTEGRATIONTIME_200MS`` (200 millis)
196          - ``INTEGRATIONTIME_300MS`` (300 millis)
197          - ``INTEGRATIONTIME_400MS`` (400 millis)
198          - ``INTEGRATIONTIME_500MS`` (500 millis)
199          - ``INTEGRATIONTIME_600MS`` (600 millis)
200          """
201          control = self._read_u8(_TSL2591_REGISTER_CONTROL)
202          return control & 0b00000111
203  
204      @integration_time.setter
205      def integration_time(self, val):
206          assert 0 <= val <= 5
207          # Set control bits appropriately.
208          control = self._read_u8(_TSL2591_REGISTER_CONTROL)
209          control &= 0b11111000
210          control |= val
211          self._write_u8(_TSL2591_REGISTER_CONTROL, control)
212          # Keep track of integration time for future reading delay times.
213          self._integration_time = val
214  
215      @property
216      def raw_luminosity(self):
217          """Read the raw luminosity from the sensor (both IR + visible and IR
218          only channels) and return a 2-tuple of those values.  The first value
219          is IR + visible luminosity (channel 0) and the second is the IR only
220          (channel 1).  Both values are 16-bit unsigned numbers (0-65535).
221          """
222          # Read both the luminosity channels.
223          channel_0 = self._read_u16LE(_TSL2591_REGISTER_CHAN0_LOW)
224          channel_1 = self._read_u16LE(_TSL2591_REGISTER_CHAN1_LOW)
225          return (channel_0, channel_1)
226  
227      @property
228      def full_spectrum(self):
229          """Read the full spectrum (IR + visible) light and return its value
230          as a 32-bit unsigned number.
231          """
232          channel_0, channel_1 = self.raw_luminosity
233          return (channel_1 << 16) | channel_0
234  
235      @property
236      def infrared(self):
237          """Read the infrared light and return its value as a 16-bit unsigned number.
238          """
239          _, channel_1 = self.raw_luminosity
240          return channel_1
241  
242      @property
243      def visible(self):
244          """Read the visible light and return its value as a 32-bit unsigned number.
245          """
246          channel_0, channel_1 = self.raw_luminosity
247          full = (channel_1 << 16) | channel_0
248          return full - channel_1
249  
250      @property
251      def lux(self):
252          """Read the sensor and calculate a lux value from both its infrared
253          and visible light channels. Note: ``lux`` is not calibrated!
254          """
255          channel_0, channel_1 = self.raw_luminosity
256  
257          # Compute the atime in milliseconds
258          atime = 100.0 * self._integration_time + 100.0
259  
260          # Set the maximum sensor counts based on the integration time (atime) setting
261          if self._integration_time == INTEGRATIONTIME_100MS:
262              max_counts = _TSL2591_MAX_COUNT_100MS
263          else:
264              max_counts = _TSL2591_MAX_COUNT
265  
266          # Handle overflow.
267          if channel_0 >= max_counts or channel_1 >= max_counts:
268              raise RuntimeError("Overflow reading light channels!")
269          # Calculate lux using same equation as Arduino library:
270          #  https://github.com/adafruit/Adafruit_TSL2591_Library/blob/master/Adafruit_TSL2591.cpp
271          again = 1.0
272          if self._gain == GAIN_MED:
273              again = 25.0
274          elif self._gain == GAIN_HIGH:
275              again = 428.0
276          elif self._gain == GAIN_MAX:
277              again = 9876.0
278          cpl = (atime * again) / _TSL2591_LUX_DF
279          lux1 = (channel_0 - (_TSL2591_LUX_COEFB * channel_1)) / cpl
280          lux2 = (
281              (_TSL2591_LUX_COEFC * channel_0) - (_TSL2591_LUX_COEFD * channel_1)
282          ) / cpl
283          return max(lux1, lux2)