/ adafruit_veml6070.py
adafruit_veml6070.py
1 # This CircuitPython library is based on Limor Fried's Arduino 2 # library for the Adafruit VEML6070 UV Sensor Breakout. 3 # License information from that library is included below. 4 # 5 # Designed specifically to work with the VEML6070 sensor from Adafruit 6 # ----> https://www.adafruit.com/products/2899 7 # 8 # These sensors use I2C to communicate, 2 pins are required to 9 # interface. 10 # 11 # Adafruit invests time and resources providing this open source code, 12 # please support Adafruit and open-source hardware by purchasing 13 # products from Adafruit! 14 # 15 # Arduino Library: Written by Limor Fried/Ladyada for Adafruit Industries. 16 # MIT license, all text above must be included in any redistribution 17 # https://github.com/adafruit/Adafruit_VEML6070 18 # 19 # CircuitPython Library Author: Michael Schroeder(sommersoft). No 20 # affiliation to Adafruit is implied. 21 """ 22 `adafruit_veml6070` - VEML6070 UV Sensor 23 ==================================================== 24 25 CircuitPython library to support VEML6070 UV Index sensor. 26 27 * Author(s): Limor Fried & Michael Schroeder 28 29 Implementation Notes 30 -------------------- 31 32 **Hardware:** 33 34 * Adafruit `VEML6070 UV Index Sensor Breakout 35 <https://www.adafruit.com/products/2899>`_ (Product ID: 2899) 36 37 **Software and Dependencies:** 38 39 * Adafruit CircuitPython firmware (2.2.0+) for the ESP8622 and M0-based boards: 40 https://github.com/adafruit/circuitpython/releases 41 * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice 42 43 **Notes:** 44 45 #. Datasheet: https://cdn-learn.adafruit.com/assets/assets/000/032/482/original/veml6070.pdf 46 47 """ 48 49 __version__ = "0.0.0-auto.0" 50 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VEML6070.git" 51 52 from adafruit_bus_device.i2c_device import I2CDevice 53 from micropython import const 54 55 56 # Set I2C addresses: 57 # pylint: disable=bad-whitespace 58 _VEML6070_ADDR_ARA = const(0x18 >> 1) 59 _VEML6070_ADDR_CMD = const(0x70 >> 1) 60 _VEML6070_ADDR_LOW = const(0x71 >> 1) 61 _VEML6070_ADDR_HIGH = const(0x73 >> 1) 62 63 # Integration Time dictionary. [0] is the byte setting; [1] is the risk 64 # level divisor. 65 _VEML6070_INTEGRATION_TIME = { 66 "VEML6070_HALF_T": [0x00, 0], 67 "VEML6070_1_T": [0x01, 1], 68 "VEML6070_2_T": [0x02, 2], 69 "VEML6070_4_T": [0x03, 4], 70 } 71 72 # UV Risk Level dictionary. [0],[1] are the lower and uppper bounds of the range 73 _VEML6070_RISK_LEVEL = { 74 "LOW": [0, 560], 75 "MODERATE": [561, 1120], 76 "HIGH": [1121, 1494], 77 "VERY HIGH": [1495, 2054], 78 "EXTREME": [2055, 9999], 79 } 80 # pylint: enable=bad-whitespace 81 82 83 class VEML6070: 84 """ 85 Driver base for the VEML6070 UV Light Sensor 86 87 :param i2c_bus: The `busio.I2C` object to use. This is the only required parameter. 88 :param str _veml6070_it: The integration time you'd like to set initially. Availble 89 options: ``VEML6070_HALF_T``, ``VEML6070_1_T``, ``VEML6070_2_T``, and 90 ``VEML6070_4_T``. The higher the '_x_' value, the more accurate 91 the reading is (at the cost of less samples per reading). 92 Defaults to ``VEML6070_1_T`` if parameter not passed. To change 93 setting after intialization, use 94 ``[veml6070].set_integration_time(new_it)``. 95 :param bool ack: The inital setting of ``ACKnowledge`` on alert. Defaults to ``False`` 96 if parameter not passed. To change setting after intialization, 97 use ``[veml6070].set_ack(new_ack)``. 98 99 Example: 100 101 .. code-block:: python 102 103 from board import * 104 import busio, veml6070, time 105 106 with busio.I2C(SCL, SDA) as i2c: 107 uv = veml6070.VEML6070(i2c, 'VEML6070_1_T', True) 108 109 # take 10 readings 110 for j in range(10): 111 uv_raw = uv.uv_raw 112 risk_level = uv.get_index(uv_raw) 113 print('Reading: ', uv_raw, ' | Risk Level: ', risk_level) 114 time.sleep(1) 115 """ 116 117 def __init__(self, i2c_bus, _veml6070_it="VEML6070_1_T", ack=False): 118 # Check if the IT is valid 119 if _veml6070_it not in _VEML6070_INTEGRATION_TIME: 120 raise ValueError( 121 "Integration Time invalid. Valid values are: ", 122 _VEML6070_INTEGRATION_TIME.keys(), 123 ) 124 125 # Check if ACK is valid 126 if ack not in (True, False): 127 raise ValueError("ACK must be 'True' or 'False'.") 128 129 # Passed checks; set self values 130 self._ack = int(ack) 131 self._ack_thd = 0x00 132 self._it = _veml6070_it 133 134 # Latch the I2C addresses 135 self.i2c_cmd = I2CDevice(i2c_bus, _VEML6070_ADDR_CMD) 136 self.i2c_low = I2CDevice(i2c_bus, _VEML6070_ADDR_LOW) 137 self.i2c_high = I2CDevice(i2c_bus, _VEML6070_ADDR_HIGH) 138 139 # Initialize the VEML6070 140 ara_buf = bytearray(1) 141 try: 142 with I2CDevice(i2c_bus, _VEML6070_ADDR_ARA) as ara: 143 ara.readinto(ara_buf) 144 except ValueError: # the ARA address is never valid? datasheet error? 145 pass 146 self.buf = bytearray(1) 147 self.buf[0] = ( 148 self._ack << 5 | _VEML6070_INTEGRATION_TIME[self._it][0] << 2 | 0x02 149 ) 150 with self.i2c_cmd as i2c_cmd: 151 i2c_cmd.write(self.buf) 152 153 @property 154 def uv_raw(self): 155 """ 156 Reads and returns the value of the UV intensity. 157 """ 158 buffer = bytearray(2) 159 with self.i2c_low as i2c_low: 160 i2c_low.readinto(buffer, end=1) 161 162 with self.i2c_high as i2c_high: 163 i2c_high.readinto(buffer, start=1) 164 165 return buffer[1] << 8 | buffer[0] 166 167 @property 168 def ack(self): 169 """ 170 Turns on or off the ACKnowledge function of the sensor. The ACK function will send 171 a signal to the host when the value of the sensed UV light changes beyond the 172 programmed threshold. 173 """ 174 return self._ack 175 176 @ack.setter 177 def ack(self, new_ack): 178 if new_ack != bool(new_ack): 179 raise ValueError("ACK must be 'True' or 'False'.") 180 self._ack = int(new_ack) 181 self.buf[0] = ( 182 self._ack << 5 183 | self._ack_thd << 4 184 | _VEML6070_INTEGRATION_TIME[self._it][0] << 2 185 | 0x02 186 ) 187 with self.i2c_cmd as i2c_cmd: 188 i2c_cmd.write(self.buf) 189 190 @property 191 def ack_threshold(self): 192 """ 193 The ACKnowledge Threshold, which alerts the host controller to value changes 194 greater than the threshold. Available settings are: ``0`` = 102 steps; ``1`` = 145 steps. 195 ``0`` is the default setting. 196 """ 197 return self._ack_thd 198 199 @ack_threshold.setter 200 def ack_threshold(self, new_ack_thd): 201 if new_ack_thd not in (0, 1): 202 raise ValueError("ACK Threshold must be '0' or '1'.") 203 self._ack_thd = int(new_ack_thd) 204 self.buf[0] = ( 205 self._ack << 5 206 | self._ack_thd << 4 207 | _VEML6070_INTEGRATION_TIME[self._it][0] << 2 208 | 0x02 209 ) 210 with self.i2c_cmd as i2c_cmd: 211 i2c_cmd.write(self.buf) 212 213 @property 214 def integration_time(self): 215 """ 216 The Integration Time of the sensor, which is the refresh interval of the 217 sensor. The higher the refresh interval, the more accurate the reading is (at 218 the cost of less sampling). The available settings are: ``VEML6070_HALF_T``, 219 ``VEML6070_1_T``, ``VEML6070_2_T``, ``VEML6070_4_T``. 220 """ 221 return self._it 222 223 @integration_time.setter 224 def integration_time(self, new_it): 225 if new_it not in _VEML6070_INTEGRATION_TIME: 226 raise ValueError( 227 "Integration Time invalid. Valid values are: ", 228 _VEML6070_INTEGRATION_TIME.keys(), 229 ) 230 231 self._it = new_it 232 self.buf[0] = ( 233 self._ack << 5 234 | self._ack_thd << 4 235 | _VEML6070_INTEGRATION_TIME[new_it][0] << 2 236 | 0x02 237 ) 238 with self.i2c_cmd as i2c_cmd: 239 i2c_cmd.write(self.buf) 240 241 def sleep(self): 242 """ 243 Puts the VEML6070 into sleep ('shutdown') mode. Datasheet claims a current draw 244 of 1uA while in shutdown. 245 """ 246 self.buf[0] = 0x03 247 with self.i2c_cmd as i2c_cmd: 248 i2c_cmd.write(self.buf) 249 250 def wake(self): 251 """ 252 Wakes the VEML6070 from sleep. ``[veml6070].uv_raw`` will also wake from sleep. 253 """ 254 self.buf[0] = ( 255 self._ack << 5 256 | self._ack_thd << 4 257 | _VEML6070_INTEGRATION_TIME[self._it][0] << 2 258 | 0x02 259 ) 260 with self.i2c_cmd as i2c_cmd: 261 i2c_cmd.write(self.buf) 262 263 def get_index(self, _raw): 264 """ 265 Calculates the UV Risk Level based on the captured UV reading. Requres the ``_raw`` 266 argument (from ``veml6070.uv_raw``). Risk level is available for Integration Times (IT) 267 1, 2, & 4. The result is automatically scaled to the current IT setting. 268 269 LEVEL* UV Index 270 ===== ======== 271 LOW 0-2 272 MODERATE 3-5 273 HIGH 6-7 274 VERY HIGH 8-10 275 EXTREME >=11 276 277 * Not to be considered as accurate condition reporting. 278 Calculation is based on VEML6070 Application Notes: 279 http://www.vishay.com/docs/84310/designingveml6070.pdf 280 281 """ 282 283 # get the divisor for the current IT 284 div = _VEML6070_INTEGRATION_TIME[self._it][1] 285 if div == 0: 286 raise ValueError( 287 "[veml6070].get_index only available for Integration Times 1, 2, & 4.", 288 "Use [veml6070].set_integration_time(new_it) to change the Integration Time.", 289 ) 290 291 # adjust the raw value using the divisor, then loop through the Risk Level dict 292 # to find which range the adjusted raw value is in. 293 raw_adj = int(_raw / div) 294 for levels in _VEML6070_RISK_LEVEL: 295 tmp_range = range( 296 _VEML6070_RISK_LEVEL[levels][0], _VEML6070_RISK_LEVEL[levels][1] 297 ) 298 if raw_adj in tmp_range: 299 risk = levels 300 break 301 302 return risk