Ev3rstorm Marvin

The script below gives the well-known Ev3rstorm robot a new and rather obnoxious personality, that of Marvin, the paranoid android star of the Hitchhiker's Guide to the Galaxy. This script was kindly offered by Denis Demidov, contributor to the EV3dev project and Senior Researcher at the Supercomputer Center of Russian Academy of Sciences. It is intended to animate the well-known Ev3rstorm robot, build instructions for which can be found HERE. These instructions are designed for the home edition of the EV3 kit - if you have the education edition then you will probably not be able to use this script because it makes use of the remote control and the ball-shooter which are not included in the éducation edition. The essential characteristics of this robot are that it is driven by large motors attached to ports B and C, that a medium motor is attached to port A or D, that the touch sensor, color sensor and IR sensors are attached to any sensor ports. This script is copied from this page.

Interestingly, the script multiplies the number of functions that can be activated by the remote control by using two different channels. With the remote control set to channel one, the remote control red and blue button pairs control the large motors, making the robot move around. With the remote control set to channel 2, either of the remote control 'up' buttons will fire a ball up and either of the remote control 'down' buttons will fire a ball down.

A fuller description of how this script works will follow shortly.

#!/usr/bin/env python3

import time, random

import ev3dev.ev3 as ev3

random.seed( time.time() )

def quote(topic):

    """

    Recite a random Marvin the Paranoid Android quote on the specified topic.

    See https://en.wikipedia.org/wiki/Marvin_(character)

    """

    marvin_quotes = {

            'initiating' : (

                "Life? Don't talk to me about life!",

                "Now I've got a headache.",

                "This will all end in tears.",

                ),

            'depressed' : (

                "I think you ought to know I'm feeling very depressed.",

                "Incredible... it's even worse than I thought it would be.",

                "I'd make a suggestion, but you wouldn't listen.",

                ),

            }

    ev3.Sound.speak(random.choice(marvin_quotes[topic])).wait()

def check(condition, message):

    """

    Check that condition is true,

    loudly complain and throw an exception otherwise.

    """

    if not condition:

        ev3.Sound.speak(message).wait()

        quote('depressed')

        raise Exception(message)

class ev3rstorm:

    def __init__(self):

        # Connect the required equipement

        self.lm = ev3.LargeMotor('outB')

        self.rm = ev3.LargeMotor('outC')

        self.mm = ev3.MediumMotor()

        self.ir = ev3.InfraredSensor()

        self.ts = ev3.TouchSensor()

        self.cs = ev3.ColorSensor()

        self.screen = ev3.Screen()

        # Check if everything is attached

        check(self.lm.connected, 'My left leg is missing!')

        check(self.rm.connected, 'Right leg is not found!')

        check(self.mm.connected, 'My left arm is not connected!')

        check(self.ir.connected, 'My eyes, I can not see!')

        check(self.ts.connected, 'Touch sensor is not attached!')

        check(self.cs.connected, 'Color sensor is not responding!')

        # Reset the motors

        for m in (self.lm, self.rm, self.mm):

            m.reset()

            m.position = 0

            m.stop_action = 'brake'

        self.draw_face()

        quote('initiating')

    def draw_face(self):

        w,h = self.screen.shape

        y = h // 2

        eye_xrad = 20

        eye_yrad = 30

        pup_xrad = 10

        pup_yrad = 10

        def draw_eye(x):

            self.screen.draw.ellipse((x-eye_xrad, y-eye_yrad, x+eye_xrad, y+eye_yrad))

            self.screen.draw.ellipse((x-pup_xrad, y-pup_yrad, x+pup_xrad, y+pup_yrad), fill='black')

        draw_eye(w//3)

        draw_eye(2*w//3)

        self.screen.update()

    def shoot(self, direction='up'):

        """

        Shot a ball in the specified direction (valid choices are 'up' and 'down')

        """

        self.mm.run_to_rel_pos(speed_sp=900, position_sp=(-1080 if direction == 'up' else 1080))

        while 'running' in self.mm.state:

            time.sleep(0.1)

    def rc_loop(self):

        """

        Enter the remote control loop. RC buttons on channel 1 control the

        robot movement, channel 2 is for shooting things.

        The loop ends when the touch sensor is pressed.

        """

        def roll(motor, led_group, speed):

            """

            Generate remote control event handler. It rolls given motor into

            given direction (1 for forward, -1 for backward). When motor rolls

            forward, the given led group flashes green, when backward -- red.

            When motor stops, the leds are turned off.

            The on_press function has signature required by RemoteControl

            class.  It takes boolean state parameter; True when button is

            pressed, False otherwise.

            """

            def on_press(state):

                if state:

                    # Roll when button is pressed

                    motor.run_forever(speed_sp=speed)

                    ev3.Leds.set_color(led_group,

                            ev3.Leds.GREEN if speed > 0 else ev3.Leds.RED)

                else:

                    # Stop otherwise

                    motor.stop()

                    ev3.Leds.set_color(led_group, ev3.Leds.BLACK)

            return on_press

        rc1 = ev3.RemoteControl(self.ir, 1)

        rc1.on_red_up    = roll(self.lm, ev3.Leds.LEFT,   900)

        rc1.on_red_down  = roll(self.lm, ev3.Leds.LEFT,  -900)

        rc1.on_blue_up   = roll(self.rm, ev3.Leds.RIGHT,  900)

        rc1.on_blue_down = roll(self.rm, ev3.Leds.RIGHT, -900)

        def shoot(direction):

            def on_press(state):

                if state: self.shoot(direction)

            return on_press

        rc2 = ev3.RemoteControl(self.ir, 2)

        rc2.on_red_up    = shoot('up')

        rc2.on_blue_up   = shoot('up')

        rc2.on_red_down  = shoot('down')

        rc2.on_blue_down = shoot('down')

        # Now that the event handlers are assigned,

        # lets enter the processing loop:

        while not self.ts.is_pressed():

            rc1.process()

            rc2.process()

            time.sleep(0.1)

if __name__ == '__main__':

    Marvin = ev3rstorm()

    Marvin.rc_loop()