Open-source weather station for astronomy
at main 12 kB view raw
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