You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
4.9 KiB
148 lines
4.9 KiB
from gpiozero import PWMOutputDevice, SourceMixin, CompositeDevice
|
|
|
|
|
|
class BetterServo(SourceMixin, CompositeDevice):
|
|
"""
|
|
Copy of GPIOZero servo, but with control over pulse width and active_high
|
|
"""
|
|
def __init__(
|
|
self, pin=None, initial_value=0.0,
|
|
min_pulse_width=1/1000, max_pulse_width=2/1000,
|
|
frame_width=20/1000, pin_factory=None, active_high=True):
|
|
if min_pulse_width >= max_pulse_width:
|
|
raise ValueError('min_pulse_width must be less than max_pulse_width')
|
|
if max_pulse_width >= frame_width:
|
|
raise ValueError('max_pulse_width must be less than frame_width')
|
|
self._frame_width = frame_width
|
|
self._min_dc = min_pulse_width / frame_width
|
|
self._dc_range = (max_pulse_width - min_pulse_width) / frame_width
|
|
self._min_value = -1
|
|
self._value_range = 2
|
|
super(BetterServo, self).__init__(
|
|
pwm_device=PWMOutputDevice(
|
|
pin, frequency=int(1 / frame_width), pin_factory=pin_factory,
|
|
active_high=False
|
|
),
|
|
pin_factory=pin_factory
|
|
)
|
|
self.pwm_device.active_high=active_high
|
|
try:
|
|
self.value = initial_value
|
|
except:
|
|
self.close()
|
|
raise
|
|
|
|
@property
|
|
def frame_width(self):
|
|
"""
|
|
The time between control pulses, measured in seconds.
|
|
"""
|
|
return self._frame_width
|
|
|
|
@property
|
|
def min_pulse_width(self):
|
|
"""
|
|
The control pulse width corresponding to the servo's minimum position,
|
|
measured in seconds.
|
|
"""
|
|
return self._min_dc * self.frame_width
|
|
|
|
@property
|
|
def max_pulse_width(self):
|
|
"""
|
|
The control pulse width corresponding to the servo's maximum position,
|
|
measured in seconds.
|
|
"""
|
|
return (self._dc_range * self.frame_width) + self.min_pulse_width
|
|
|
|
@property
|
|
def pulse_width(self):
|
|
"""
|
|
Returns the current pulse width controlling the servo.
|
|
"""
|
|
if self.pwm_device.frequency is None:
|
|
return None
|
|
else:
|
|
return self.pwm_device.state * self.frame_width
|
|
|
|
@pulse_width.setter
|
|
def pulse_width(self, value):
|
|
if value is None:
|
|
self.pwm_device.frequency = None
|
|
elif self.min_pulse_width <= value <= self.max_pulse_width:
|
|
self.pwm_device.frequency = int(1 / self.frame_width)
|
|
self.pwm_device.value = (value / self.frame_width)
|
|
else:
|
|
raise OutputDeviceBadValue("Servo pulse_width must be between min and max supplied during construction, or None")
|
|
|
|
def min(self):
|
|
"""
|
|
Set the servo to its minimum position.
|
|
"""
|
|
self.value = -1
|
|
|
|
def mid(self):
|
|
"""
|
|
Set the servo to its mid-point position.
|
|
"""
|
|
self.value = 0
|
|
|
|
def max(self):
|
|
"""
|
|
Set the servo to its maximum position.
|
|
"""
|
|
self.value = 1
|
|
|
|
def detach(self):
|
|
"""
|
|
Temporarily disable control of the servo. This is equivalent to
|
|
setting :attr:`value` to ``None``.
|
|
"""
|
|
self.value = None
|
|
|
|
def _get_value(self):
|
|
if self.pwm_device.frequency is None:
|
|
return None
|
|
else:
|
|
return (
|
|
((self.pwm_device.state - self._min_dc) / self._dc_range) *
|
|
self._value_range + self._min_value)
|
|
|
|
@property
|
|
def value(self):
|
|
"""
|
|
Represents the position of the servo as a value between -1 (the minimum
|
|
position) and +1 (the maximum position). This can also be the special
|
|
value ``None`` indicating that the servo is currently "uncontrolled",
|
|
i.e. that no control signal is being sent. Typically this means the
|
|
servo's position remains unchanged, but that it can be moved by hand.
|
|
"""
|
|
result = self._get_value()
|
|
if result is None:
|
|
return result
|
|
else:
|
|
# NOTE: This round() only exists to ensure we don't confuse people
|
|
# by returning 2.220446049250313e-16 as the default initial value
|
|
# instead of 0. The reason _get_value and _set_value are split
|
|
# out is for descendents that require the un-rounded values for
|
|
# accuracy
|
|
return round(result, 14)
|
|
|
|
@value.setter
|
|
def value(self, value):
|
|
if value is None:
|
|
self.pwm_device.frequency = None
|
|
elif -1 <= value <= 1:
|
|
self.pwm_device.frequency = int(1 / self.frame_width)
|
|
self.pwm_device.value = (
|
|
self._min_dc + self._dc_range *
|
|
((value - self._min_value) / self._value_range)
|
|
)
|
|
else:
|
|
raise OutputDeviceBadValue(
|
|
"Servo value must be between -1 and 1, or None")
|
|
|
|
@property
|
|
def is_active(self):
|
|
return self.value is not None
|