/ adafruit_tfmini.py
adafruit_tfmini.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_tfmini`
 24  ====================================================
 25  
 26  A CircuitPython/Python library for Benewake's TF mini distance sensor
 27  
 28  * Author(s): ladyada
 29  
 30  Implementation Notes
 31  --------------------
 32  
 33  **Hardware:**
 34  
 35  **Software and Dependencies:**
 36  
 37  * Adafruit CircuitPython firmware for the supported boards:
 38    https://github.com/adafruit/circuitpython/releases
 39  
 40  """
 41  
 42  import time
 43  import struct
 44  
 45  __version__ = "0.0.0-auto.0"
 46  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TFmini.git"
 47  
 48  _STARTCONFIG = b"\x42\x57\x02\x00\x00\x00\x01\x02"
 49  _STARTREPLY = b"\x57\x02\x01\x00\x00\x01\x02"  # minus header 0x42
 50  _CONFIGPARAM = b"\x42\x57\x02\x00"
 51  _ENDCONFIG = b"\x42\x57\x02\x00\x00\x00\x00\x02"
 52  _ENDREPLY = b"\x42\x57\x02\x01\x00\x00\x00\x02"
 53  
 54  MODE_SHORT = 2
 55  MODE_LONG = 7
 56  
 57  
 58  class TFmini:
 59      """TF mini communication module, use with just RX or TX+RX for advanced
 60      command & control.
 61      :param uart: the pyseral or busio.uart compatible uart device
 62      :param timeout: how long we'll wait for valid data or response, in seconds. Default is 1
 63      """
 64  
 65      def __init__(self, uart, *, timeout=1):
 66          self._uart = uart
 67          self._uart.baudrate = 115200
 68          self.timeout = timeout
 69          self._strength = None
 70          self._mode = None
 71  
 72      @property
 73      def distance(self):
 74          """The most recent distance measurement in centimeters"""
 75          try:
 76              self._uart.reset_input_buffer()
 77          except AttributeError:
 78              # not implemented, we'll just keep going
 79              pass
 80  
 81          # listen for new packet
 82          stamp = time.monotonic()
 83          while time.monotonic() - stamp < self.timeout:
 84              # look for the header start
 85              x = self._uart.read(1)
 86              if not x or x[0] != 0x59:
 87                  continue
 88              # get remaining packet
 89              data = self._uart.read(8)
 90              # check first byte is magicbyte
 91              frame, dist, self._strength, self._mode, _, checksum = struct.unpack(
 92                  "<BHHBBB", data
 93              )
 94              # look for second 0x59 frame indicator
 95              if frame != 0x59:
 96                  continue
 97              # calculate and check sum
 98              mysum = (sum(data[0:7]) + 0x59) & 0xFF
 99              if mysum != checksum:
100                  continue
101              return dist
102          raise RuntimeError("Timed out looking for valid data")
103  
104      @property
105      def strength(self):
106          """The signal validity, higher value means better measurement"""
107          _ = self.distance  # trigger distance measurement
108          return self._strength
109  
110      @property
111      def mode(self):
112          """The measurement mode can be MODE_SHORT (2) or MODE_LONG (7)"""
113          _ = self.distance  # trigger distance measurement
114          return self._mode
115  
116      @mode.setter
117      def mode(self, newmode):
118          if not newmode in (MODE_LONG, MODE_SHORT):
119              raise ValueError("Invalid mode")
120          self._set_config(_CONFIGPARAM + bytes([0, 0, newmode, 0x11]))
121  
122      def _set_config(self, command):
123          """Manager for sending commands, put sensor into config mode, config,
124          then exit configuration mode!"""
125          self._uart.write(_STARTCONFIG)
126          stamp = time.monotonic()
127          while (time.monotonic() - stamp) < self.timeout:
128              # look for the header start
129              x = self._uart.read(1)
130              if not x or x[0] != 0x42:
131                  continue
132              echo = self._uart.read(len(_STARTREPLY))
133              # print("start ", [hex(i) for i in echo])
134              if echo != _STARTREPLY:
135                  raise RuntimeError("Did not receive config start echo")
136              break
137  
138          # Finally, send the command
139          self._uart.write(command)
140          # print([hex(i) for i in command])
141          echo = self._uart.read(len(command))
142          cmdreply = bytearray(len(command))
143          cmdreply[:] = command
144          cmdreply[3] = 0x1
145          # print("cmd ", [hex(i) for i in echo])
146          if echo != cmdreply:
147              raise RuntimeError("Did not receive config command echo")
148  
149          self._uart.write(_ENDCONFIG)
150          echo = self._uart.read(len(_ENDREPLY))
151          # print("end ", [hex(i) for i in echo])
152          if echo != _ENDREPLY:
153              raise RuntimeError("Did not receive config end echo")