/ adafruit_lidarlite.py
adafruit_lidarlite.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2018 ladyada for adafruit industries
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  """
 23  `adafruit_lidarlite`
 24  ====================================================
 25  
 26  A CircuitPython & Python library for Garmin LIDAR Lite sensors over I2C
 27  
 28  * Author(s): ladyada
 29  
 30  Implementation Notes
 31  --------------------
 32  
 33  **Hardware:**
 34  
 35  
 36  **Software and Dependencies:**
 37  
 38  * Adafruit CircuitPython firmware for the supported boards:
 39    https://github.com/adafruit/circuitpython/releases
 40  
 41  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 42  
 43  """
 44  
 45  # imports
 46  import time
 47  from adafruit_bus_device.i2c_device import I2CDevice
 48  from digitalio import Direction
 49  from micropython import const
 50  
 51  __version__ = "0.0.0-auto.0"
 52  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LIDARLite.git"
 53  
 54  
 55  _ADDR_DEFAULT = const(0x62)
 56  _REG_ACQ_COMMAND = const(0x00)
 57  _CMD_RESET = const(0)
 58  _CMD_DISTANCENOBIAS = const(3)
 59  _CMD_DISTANCEWITHBIAS = const(4)
 60  
 61  CONFIG_DEFAULT = 0
 62  CONFIG_SHORTFAST = 1
 63  CONFIG_DEFAULTFAST = 2
 64  CONFIG_MAXRANGE = 3
 65  CONFIG_HIGHSENSITIVE = 4
 66  CONFIG_LOWSENSITIVE = 5
 67  
 68  STATUS_BUSY = 0x01
 69  STATUS_REF_OVERFLOW = 0x02
 70  STATUS_SIGNAL_OVERFLOW = 0x04
 71  STATUS_NO_PEAK = 0x08
 72  STATUS_SECOND_RETURN = 0x10
 73  STATUS_HEALTHY = 0x20
 74  STATUS_SYS_ERROR = 0x40
 75  
 76  # The various configuration register values, from arduino library
 77  _LIDAR_CONFIGS = (
 78      (0x80, 0x08, 0x00),  # default
 79      (0x1D, 0x08, 0x00),  # short range, high speed
 80      (0x80, 0x00, 0x00),  # default range, higher speed short range
 81      (0xFF, 0x08, 0x00),  # maximum range
 82      (0x80, 0x08, 0x80),  # high sensitivity & error
 83      (0x80, 0x08, 0xB0),
 84  )  # low sensitivity & error
 85  
 86  
 87  class LIDARLite:
 88      """
 89      A driver for the Garmin LIDAR Lite laser distance sensor.
 90      :param i2c_bus: The `busio.I2C` object to use. This is the only
 91      required parameter.
 92      :param int address: (optional) The I2C address of the device to set after initialization.
 93      """
 94  
 95      def __init__(
 96          self,
 97          i2c_bus,
 98          *,
 99          reset_pin=None,
100          configuration=CONFIG_DEFAULT,
101          address=_ADDR_DEFAULT
102      ):
103          """Initialize the hardware for the LIDAR over I2C. You can pass in an
104          optional reset_pin for when you call reset(). There are a few common
105          configurations Garmin suggests: CONFIG_DEFAULT, CONFIG_SHORTFAST,
106          CONFIG_DEFAULTFAST, CONFIG_MAXRANGE, CONFIG_HIGHSENSITIVE, and
107          CONFIG_LOWSENSITIVE. For the I2C address, the default is 0x62 but if you
108          pass a different number in, we'll try to change the address so multiple
109          LIDARs can be connected. (Note all but one need to be in reset for this
110          to work!)"""
111          self.i2c_device = I2CDevice(i2c_bus, address)
112          self._buf = bytearray(2)
113          self._bias_count = 0
114          self._reset = reset_pin
115          time.sleep(0.5)
116          self.configure(configuration)
117          self._status = self.status
118  
119      def reset(self):
120          """Hardware reset (if pin passed into init) or software reset. Will take
121          100 readings in order to 'flush' measurement unit, otherwise data is off."""
122          # Optional hardware reset pin
123          if self._reset is not None:
124              self._reset.direction = Direction.OUTPUT
125              self._reset.value = True
126              self._reset.value = False
127              time.sleep(0.01)
128              self._reset.value = True
129          else:
130              try:
131                  self._write_reg(_REG_ACQ_COMMAND, _CMD_RESET)
132              except OSError:
133                  pass  # it doesnt respond well once reset
134          time.sleep(1)
135          # take 100 readings to 'flush' out sensor!
136          for _ in range(100):
137              try:
138                  self.read_distance(True)
139              except RuntimeError:
140                  pass
141  
142      def configure(self, config):
143          """Set the LIDAR desired style of measurement. There are a few common
144          configurations Garmin suggests: CONFIG_DEFAULT, CONFIG_SHORTFAST,
145          CONFIG_DEFAULTFAST, CONFIG_MAXRANGE, CONFIG_HIGHSENSITIVE, and
146          CONFIG_LOWSENSITIVE."""
147          settings = _LIDAR_CONFIGS[config]
148          self._write_reg(0x02, settings[0])
149          self._write_reg(0x04, settings[1])
150          self._write_reg(0x1C, settings[2])
151  
152      def read_distance(self, bias=False):
153          """Perform a distance reading with or without 'bias'. It's recommended
154          to take a bias measurement every 100 non-bias readings (they're slower)"""
155          if bias:
156              self._write_reg(_REG_ACQ_COMMAND, _CMD_DISTANCEWITHBIAS)
157          else:
158              self._write_reg(_REG_ACQ_COMMAND, _CMD_DISTANCENOBIAS)
159          dist = self._read_reg(0x8F, 2)
160          if self._status & (STATUS_NO_PEAK | STATUS_SECOND_RETURN):
161              raise RuntimeError("Measurement failure")
162          if (self._status & STATUS_SYS_ERROR) or (not self._status & STATUS_HEALTHY):
163              raise RuntimeError("System failure")
164          return dist[0] << 8 | dist[1]
165  
166      @property
167      def distance(self):
168          """The measured distance in cm. Will take a bias reading every 100 calls"""
169          self._bias_count -= 1
170          if self._bias_count < 0:
171              self._bias_count = 100  # every 100 reads, check bias
172          return self.read_distance(self._bias_count <= 0)
173  
174      @property
175      def status(self):
176          """The status byte, check datasheet for bitmask"""
177          buf = bytearray([0x1])
178          with self.i2c_device as i2c:
179              i2c.write_then_readinto(buf, buf)
180          return buf[0]
181  
182      def _write_reg(self, reg, value):
183          self._buf[0] = reg
184          self._buf[1] = value
185          with self.i2c_device as i2c:
186              # print("Writing: ", [hex(i) for i in self._buf])
187              i2c.write(self._buf)
188          time.sleep(0.001)  # there's a delay in arduino library
189  
190      def _read_reg(self, reg, num):
191          while True:
192              self._status = self.status
193              if not self._status & STATUS_BUSY:
194                  break
195          # no longer busy
196          self._buf[0] = reg
197          with self.i2c_device as i2c:
198              i2c.write_then_readinto(self._buf, self._buf, out_end=1, in_end=num)
199          # print("Read from ", hex(reg), [hex(i) for i in self._buf])
200          return self._buf