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