/ adafruit_servokit.py
adafruit_servokit.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2018 Kattni Rembor 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_servokit`
 24  ====================================================
 25  
 26  CircuitPython helper library for the PWM/Servo FeatherWing, Shield and Pi HAT and Bonnet kits.
 27  
 28  * Author(s): Kattni Rembor
 29  
 30  Implementation Notes
 31  --------------------
 32  
 33  **Hardware:**
 34  
 35  * `8-Channel PWM or Servo FeatherWing <https://www.adafruit.com/product/2928>`_
 36  * `Adafruit 16-Channel 12-bit PWM/Servo Shield <https://www.adafruit.com/product/1411>`_
 37  * `Adafruit 16-Channel PWM/Servo HAT for Raspberry Pi <https://www.adafruit.com/product/2327>`_
 38  * `Adafruit 16-Channel PWM/Servo Bonnet for Raspberry Pi <https://www.adafruit.com/product/3416>`_
 39  
 40  **Software and Dependencies:**
 41  
 42  * Adafruit CircuitPython firmware for the supported boards:
 43    https://github.com/adafruit/circuitpython/releases
 44  
 45  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 46  * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
 47  * Adafruit's PCA9685 library: https://github.com/adafruit/Adafruit_CircuitPython_PCA9685
 48  * Adafruit's Motor library: https://github.com/adafruit/Adafruit_CircuitPython_Motor
 49  
 50  """
 51  
 52  import board
 53  from adafruit_pca9685 import PCA9685
 54  
 55  __version__ = "0.0.0-auto.0"
 56  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ServoKit.git"
 57  
 58  
 59  class ServoKit:
 60      """Class representing an Adafruit PWM/Servo FeatherWing, Shield or Pi HAT and Bonnet kits.
 61  
 62         Automatically uses the I2C bus on a Feather, Metro or Raspberry Pi.
 63  
 64         Initialise the PCA9685 chip at ``address``.
 65  
 66         The internal reference clock is 25MHz but may vary slightly with environmental conditions and
 67         manufacturing variances. Providing a more precise ``reference_clock_speed`` can improve the
 68         accuracy of the frequency and duty_cycle computations. See the ``calibration.py`` example in
 69         the `PCA9685 GitHub repo <https://github.com/adafruit/Adafruit_CircuitPython_PCA9685>`_ for
 70         how to derive this value by measuring the resulting pulse widths.
 71  
 72         :param int channels: The number of servo channels available. Must be 8 or 16. The FeatherWing
 73                              has 8 channels. The Shield, HAT, and Bonnet have 16 channels.
 74         :param int address: The I2C address of the PCA9685. Default address is ``0x40``.
 75         :param int reference_clock_speed: The frequency of the internal reference clock in Hertz.
 76                                           Default reference clock speed is ``25000000``.
 77         :param int frequency: The overall PWM frequency of the PCA9685 in Hertz.
 78                                           Default frequency is ``50``.
 79  
 80      """
 81  
 82      def __init__(
 83          self,
 84          *,
 85          channels,
 86          i2c=None,
 87          address=0x40,
 88          reference_clock_speed=25000000,
 89          frequency=50
 90      ):
 91          if channels not in [8, 16]:
 92              raise ValueError("servo_channels must be 8 or 16!")
 93          self._items = [None] * channels
 94          self._channels = channels
 95          if i2c is None:
 96              i2c = board.I2C()
 97          self._pca = PCA9685(
 98              i2c, address=address, reference_clock_speed=reference_clock_speed
 99          )
100          self._pca.frequency = frequency
101  
102          self._servo = _Servo(self)
103          self._continuous_servo = _ContinuousServo(self)
104  
105      @property
106      def servo(self):
107          """:py:class:``~adafruit_motor.servo.Servo`` controls for standard servos.
108  
109          This FeatherWing example rotates a servo on channel ``0`` to ``180`` degrees for one second,
110          and then returns it to ``0`` degrees.
111  
112          .. code-block:: python
113  
114              import time
115              from adafruit_servokit import ServoKit
116  
117              kit = ServoKit(channels=8)
118  
119              kit.servo[0].angle = 180
120              time.sleep(1)
121              kit.servo[0].angle = 0
122  
123          """
124          return self._servo
125  
126      @property
127      def continuous_servo(self):
128          """:py:class:``~adafruit_motor.servo.ContinuousServo`` controls for continuous rotation
129          servos.
130  
131          This FeatherWing example rotates a continuous rotation servo on channel ``0`` forward for
132          one second, then backward for one second, and then stops the rotation.
133  
134          .. code-block:: python
135  
136              import time
137              from adafruit_servokit import ServoKit
138  
139              kit = ServoKit(channels=8)
140  
141              kit.continuous_servo[0].throttle = 1
142              time.sleep(1)
143              kit.continuous_servo[0].throttle = -1
144              time.sleep(1)
145              kit.continuous_servo[0].throttle = 0
146  
147          """
148          return self._continuous_servo
149  
150  
151  class _Servo:
152      # pylint: disable=protected-access
153      def __init__(self, kit):
154          self.kit = kit
155  
156      def __getitem__(self, servo_channel):
157          import adafruit_motor.servo  # pylint: disable=import-outside-toplevel
158  
159          num_channels = self.kit._channels
160          if servo_channel >= num_channels or servo_channel < 0:
161              raise ValueError("servo must be 0-{}!".format(num_channels - 1))
162          servo = self.kit._items[servo_channel]
163          if servo is None:
164              servo = adafruit_motor.servo.Servo(self.kit._pca.channels[servo_channel])
165              self.kit._items[servo_channel] = servo
166              return servo
167          if isinstance(self.kit._items[servo_channel], adafruit_motor.servo.Servo):
168              return servo
169          raise ValueError("Channel {} is already in use.".format(servo_channel))
170  
171      def __len__(self):
172          return len(self.kit._items)
173  
174  
175  class _ContinuousServo:
176      # pylint: disable=protected-access
177      def __init__(self, kit):
178          self.kit = kit
179  
180      def __getitem__(self, servo_channel):
181          import adafruit_motor.servo  # pylint: disable=import-outside-toplevel
182  
183          num_channels = self.kit._channels
184          if servo_channel >= num_channels or servo_channel < 0:
185              raise ValueError("servo must be 0-{}!".format(num_channels - 1))
186          servo = self.kit._items[servo_channel]
187          if servo is None:
188              servo = adafruit_motor.servo.ContinuousServo(
189                  self.kit._pca.channels[servo_channel]
190              )
191              self.kit._items[servo_channel] = servo
192              return servo
193          if isinstance(
194              self.kit._items[servo_channel], adafruit_motor.servo.ContinuousServo
195          ):
196              return servo
197          raise ValueError("Channel {} is already in use.".format(servo_channel))
198  
199      def __len__(self):
200          return len(self.kit._items)