Sunday, October 9, 2016

Raspberry Pi & Arch Linux - Day 12 - Build Wheel Component



In this post, we will present the code of Wheel Component.

One of the interesting problems when we try to control a wheel is the fact that the left wheel  and right wheel are mirror symmetric. It means that we cannot use the same code to control the two wheels at the same time.

For example, let us assume that we have a Motor class and it has two methods: (1)spin_clockwisely and (2)spin_anti_clockwisely. If the Motor is used in the right wheel then spin_clockwisely function will make the robot move forward while the spin_anti_clockwisely function will make the robot move backward. It is easy to see if the Motor is used in the left wheel then the effect is opposite, meaning that the spin_clockwisely function will make the robot move backward and the spin_anti_clockwisely function will make the robot forward.

In our design, we do not want the Motor class to handle the mirror symmetry problem because the motor does not know if it is used in a wheel and our Component class is specifically designed to represent a physical device. Therefore, we will create a WheelComponent to control the motor used in a wheel.


class WheelComponent(Component):
    """
    Represent a single wheel.
    """

    def __init__(self, name=None, mirror=False, pin_signal=None, repeat=10, pulse=None, width=None):
        """
        Args:
            name:           the name of the component.
            mirror:         Bool. The left wheel and right wheel is a mirro image of each other. Therefore, with the same configuration and the 
                            same operaton the effect is opposite. For example, let assume we are in a scenario where when we increase the pulse, 
                            the motor spins faster clockwisely. If this motor is used for right wheel, when the pulse is increased, the robot will
                            be speed up; while if it is used for right wheel, the robot will be slowed down. This is a mirror effect. The mirror 
                            parameter is used to handle the mirror effet so we can have a unified interface to control both the left and right wheel.
            pin_signal:     the pin number for sending the pulse to the motor.
            pulse:          the pulse that is send to the motor. If the pulse is None,it will be set to the reference pulse of the underlying motor 
                            class, which make the motor still. The default value of pulse is None.
            repeat:         the number pulse sent to the motor in a cycle.
            width:          In the communication protocol, the signal consists of two parts: (1)pulse and (2)silence. The width specifies the length 
                            of the slient period. If the width is None, it will be set to the width value of the underlaying motor class.

        """

        assert name is not None
        assert pin_signal is not None
        self._name = name
        self._pin_signal = pin_signal
        self._motor = WheelMotor(pin_signal=self._pin_signal)
        self._reference_pulse = self._motor.reference_pulse
        self._max_deviation = self._motor.max_pulse_deviation
        self._max_pulse = self._reference_pulse + self._max_deviation
        self._min_pulse = self._reference_pulse - self._max_deviation

        self._pulse = pulse if pulse is not None else self._reference_pulse
        self._width = width if width is not None else self._motor.width
        self._repeat = repeat
        self._mirror = mirror


    def run(self):
        self._motor.generate_pulse(repeat=self._repeat,pulse=self._pulse, width=0.020)

    def send_msg(self,Q):
        pass

    @property
    def pulse(self):
        return self._pulse
    @pulse.setter
    def pulse(self, val):
        self._pulse = min(val, self._width)


    @property
    def repeat(self):
        return self._repeat
    @repeat.setter
    def repeat(self,val):
        self._repeat = min(val, 50)

    def increase_speed(self, scale):
        scale = min(scale, 1.)
        scale = max(scale, -1.)

        increment = scale *  self._max_deviation

        if self._mirror:
            increment = -1 * increment

        new_pulse = self.pulse + increment
        new_pulse = min(self._max_pulse, new_pulse)
        new_pulse = max(self._min_pulse, new_pulse)

        self._pulse = new_pulse

    def stop(self):
        self._pulse = self._reference_pulse


Note that in the __init__ function ,we have a parameter called mirror and it is used in the increase_speed method. This parameter indicates if we want to use the mirror effect of an operation. In case of increasing the speed of the rotation, it flips the sign of the incremental amount of pulse. In this way, we have a unified interface to control both the left and right wheel.

To use the component, we need to wrap it in a ContinuousComponentWrapper as we did before. Here is a sample code that shows how we can increase the speed of the wheel.


pin_signal_left = 13
pin_signal_right = 15

left_wheel_component  = WheelComponent(name='left_wheel', mirror=False, pin_signal=pin_signal_left, repeat=20, pulse=None, width=None)
right_wheel_component = WheelComponent(name='right_wheel', mirror=True, pin_signal=pin_signal_right, repeat=20, pulse=None, width=None)

cmd_Q_left_wheel     = mp.Queue()
output_Q_left_wheel  = mp.Queue()
cmd_Q_right_wheel    = mp.Queue()
output_Q_right_wheel = mp.Queue()


left_wheel  = ContinuousComponentWrapper(component=left_wheel_component,cmd_Q=cmd_Q_left_wheel,output_Q=output_Q_left_wheel)
right_wheel = ContinuousComponentWrapper(component=right_wheel_component,cmd_Q=cmd_Q_right_wheel,output_Q=output_Q_right_wheel)


left_wheel.start()
right_wheel.start()

scale = 0.
while True:
    print('scale: {}'.format(scale))
    time.sleep(3)
    cmd_Q_left_wheel.put(('increase_speed',(0.1,), {}))
    cmd_Q_right_wheel.put(('increase_speed', (0.1,), {}))
    scale += 0.1
--END--

No comments:

Post a Comment