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.
shepherd-agent/shepherd/plugins/betterservo.py

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