/ 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