-
Notifications
You must be signed in to change notification settings - Fork 14
/
adafruit_tsl2591.py
305 lines (251 loc) · 10.3 KB
/
adafruit_tsl2591.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# 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 micropython import const
from adafruit_bus_device import i2c_device
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_ENABLE_POWEROFF = const(0x00)
_TSL2591_ENABLE_POWERON = const(0x01)
_TSL2591_ENABLE_AEN = const(0x02)
_TSL2591_ENABLE_AIEN = const(0x10)
_TSL2591_ENABLE_NPIEN = const(0x80)
_TSL2591_REGISTER_ENABLE = const(0x00)
_TSL2591_REGISTER_CONTROL = const(0x01)
_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"""
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]
# Disable invalid name check since pylint isn't smart enough to know LE
# is an abbreviation for little-endian.
# pylint: disable=invalid-name
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]
# pylint: enable=invalid-name
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)
def enable(self) -> None:
"""Put the device in a fully powered enabled mode."""
self._write_u8(
_TSL2591_REGISTER_ENABLE,
_TSL2591_ENABLE_POWERON
| _TSL2591_ENABLE_AEN
| _TSL2591_ENABLE_AIEN
| _TSL2591_ENABLE_NPIEN,
)
def disable(self) -> None:
"""Disable the device and go into low power mode."""
self._write_u8(_TSL2591_REGISTER_ENABLE, _TSL2591_ENABLE_POWEROFF)
@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 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 32-bit unsigned number.
"""
channel_0, channel_1 = self.raw_luminosity
return (channel_1 << 16) | channel_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 32-bit unsigned number."""
channel_0, channel_1 = self.raw_luminosity
full = (channel_1 << 16) | channel_0
return full - 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)