Friday, 22 January 2016

Lesson 3 - Remote controlled vehicle

EV3 Direct commands - Lesson 03

Moving motors is the heart of the EV3. A robot needs the ability to move, therefore you need to know, how it's done. Take a look at part 4.9 in the document EV3 Firmware Developer Kit to get a first impression about the operations that correspond to the motors. Nearly all of them start with the same two arguments:

  • LAYER: If you combine more than one EV3 bricks to act as a single machine, you declare one of them to be the master and up to 3 additional bricks to be the masters slaves. Then you send your operations to the master brick and the master sends it to the slaves. This is the meaning of LAYER. Layer 0x|00| is the master, layers 0x|01|, 0x|02| and 0x|03| are its optional slaves. In the standard case, where no slaves exist, we will set LAYER = 0x|00|. Our coding with direct commands allows to connect multiple EV3 devices. Therefore I see no need for different values of argument LAYER.
  • NO or NOS: The motor ports are alphabetically identified with A, B, C and D. At the level of direct commands you name them by numbers, A = 0x|01|, B = 0x|02|, C = 0x|04| and D = 0x|08|. If you write these numbers as binaries, A = 0b 0000 0001, B = 0b 0000 0010, C = 0b 0000 0100 and C = 0b 0000 1000, you see, that the last half byte can be interpreted as a series of four flags. You can combine these flags. If you address an operation to the motors at ports A and C, you set NOS = 0x|05| resp. 0b 0000 0101. If an arguments name is NO, you can address only a single motor port. NOS says, that you can address one or multiple ports.

Start and stop motors

Plug in some motors to the ports of your choice, then start them with full power. This is done by the following direct command:

---------------------------------------------- \ len \ cnt \ty\ hd \op\la\no\power\op\la\no\ ---------------------------------------------- 0x|0D:00|2A:00|80|00:00|A4|00|0F|81:64|A6|00|0F| ---------------------------------------------- \ 13 \ 42 \no\ 0,0 \O \0 \a \ 100 \O \0 \a \ \ \ \ \ \u \ \l \ \u \ \l \ \ \ \ \ \t \ \l \ \t \ \l \ \ \ \ \ \p \ \ \ \p \ \ \ \ \ \ \ \u \ \ \ \u \ \ \ \ \ \ \ \t \ \ \ \t \ \ \ \ \ \ \ \_ \ \ \ \_ \ \ \ \ \ \ \ \P \ \ \ \S \ \ \ \ \ \ \ \o \ \ \ \t \ \ \ \ \ \ \ \w \ \ \ \a \ \ \ \ \ \ \ \e \ \ \ \r \ \ \ \ \ \ \ \r \ \ \ \t \ \ \ ----------------------------------------------

After some time, you, your parents, siblings or children become nerved of the sound of full powered motors. If you are a peace loving character, you should send the following direct command:

---------------------------------- \ len \ cnt \ty\ hd \op\la\no\br\ ---------------------------------- 0x|09:00|2A:00|00|00:00|A3|00|0F|00| ---------------------------------- \ 9 \ 42 \re\ 0,0 \O \0 \a \no\ \ \ \ \ \u \ \l \ \ \ \ \ \ \t \ \l \ \ \ \ \ \ \p \ \ \ \ \ \ \ \ \u \ \ \ \ \ \ \ \ \t \ \ \ \ \ \ \ \ \_ \ \ \ \ \ \ \ \ \S \ \ \ \ \ \ \ \ \t \ \ \ \ \ \ \ \ \o \ \ \ \ \ \ \ \ \p \ \ \ \ ----------------------------------

We have seen three new operations:

  • opOutput_Power = 0x|A4| with the arguments:
    • LAYER
    • NOS
    • POWER: specifies output power [-100 – 100 %] (negative values mean opposite direction).
  • opOutput_Start = 0x|A6| with the arguments:
    • LAYER
    • NOS
  • opOutput_Stop = 0x|A3| with the arguments:
    • LAYER
    • NOS
    • BRAKE: Specify break level [0: Float, 1: Break]. This is not a passive brake, where the motor is fixed at its position. It is an active one, where the motor tries to move back to its original position and this costs energy!
With these three operations, we are able to start and stop our motors. There is another one, we should talk about, which sets the polarity. Normally the movement of a motor is described in terms of forward and backward direction and one uses positive and negative numbers. If the construction of our machine results in opposite polarity, there is an operation, to change it:
  • opOutput_Polarity = 0x|A7| with the arguments:
    • LAYER
    • NOS
    • POLARITY: specifies polarity [-1, 0, 1]
      • -1: Motor will run backward
      • 0: Motor will run opposite direction
      • +1: Motor will run forward

Defining speed instead of power often is the better choice. Luckily the LEGO motors are regulated ones and we can easily define constant speed for motor movement with the following operation:

  • opOutput_Speed = 0x|A5| with the arguments:
    • LAYER
    • NOS
    • SPEED: specifies output speed [-100 – 100 %] (negative values mean opposite direction).

Please construct a vehicle, that is powered by two motors, one for the left side, the other for the right one. Well designed examples are:

but a simple one also will serve our needs. Connect the right motor to port A, the left one to port D and send the following direct command:
------------------------------------------- \ len \ cnt \ty\ hd \op\la\no\sp\op\la\no\ ------------------------------------------- 0x|0C:00|2A:00|80|00:00|A5|00|09|3D|A6|00|09| ------------------------------------------- \ 12 \ 42 \no\ 0,0 \O \0 \A \-3\O \0 \A \ \ \ \ \ \u \ \+ \ \u \ \+ \ \ \ \ \ \t \ \D \ \t \ \D \ \ \ \ \ \p \ \ \ \p \ \ \ \ \ \ \ \u \ \ \ \u \ \ \ \ \ \ \ \t \ \ \ \t \ \ \ \ \ \ \ \_ \ \ \ \_ \ \ \ \ \ \ \ \S \ \ \ \S \ \ \ \ \ \ \ \p \ \ \ \t \ \ \ \ \ \ \ \e \ \ \ \a \ \ \ \ \ \ \ \e \ \ \ \r \ \ \ \ \ \ \ \d \ \ \ \t \ \ \ -------------------------------------------
Hopefully your vehicle moves straight with low velocity. Use the direct command above (opOutput_Stop) to stop it.

Remote controlled vehicles

Now it's time to code a real application. Impress your friends or children with a remote controlled EV3 vehicle. We start with a simple remote control and use the keyboards arrow-keys to change speed and direction. First add the new operations to your class EV3, then write your remote-control-program. As usual I have done it in python3:


#!/usr/bin/env python3

import curses
import ev3

def move(speed: int, turn: int) -> None:
    global myEV3, stdscr
    stdscr.addstr(5, 0, 'speed: {}, turn: {}      '.format(speed, turn))
    if turn > 0:
        speed_right = speed
        speed_left  = round(speed * (1 - turn / 100))
    else:
        speed_right = round(speed * (1 + turn / 100))
        speed_left  = speed
    ops = b''.join([
        ev3.opOutput_Speed,
        ev3.LCX(0),                       # LAYER
        ev3.LCX(ev3.PORT_A),              # NOS
        ev3.LCX(speed_right),             # SPEED
        ev3.opOutput_Speed,
        ev3.LCX(0),                       # LAYER
        ev3.LCX(ev3.PORT_D),              # NOS
        ev3.LCX(speed_left),              # SPEED
        ev3.opOutput_Start,
        ev3.LCX(0),                       # LAYER
        ev3.LCX(ev3.PORT_A + ev3.PORT_D)  # NOS
    ])
    myEV3.send_direct_cmd(ops)

def stop() -> None:
    global myEV3, stdscr
    stdscr.addstr(5, 0, 'vehicle stopped                         ')
    ops = b''.join([
        ev3.opOutput_Stop,
        ev3.LCX(0),                       # LAYER
        ev3.LCX(ev3.PORT_A + ev3.PORT_D), # NOS
        ev3.LCX(0)                        # BRAKE
    ])
    myEV3.send_direct_cmd(ops)

def react(c):
    global speed, turn
    if c in [ord('q'), 27, ord('p')]:
        stop()
        return
    elif c == curses.KEY_LEFT:
        turn += 5
        turn = min(turn, 200)
    elif c == curses.KEY_RIGHT:
        turn -= 5
        turn = max(turn, -200)
    elif c == curses.KEY_UP:
        speed += 5
        speed = min(speed, 100)
    elif c == curses.KEY_DOWN:
        speed -= 5
        speed = max(speed, -100)
    move(speed, turn)

def main(window) -> None:
    global stdscr
    stdscr = window
    stdscr.clear()      # print introduction
    stdscr.refresh()
    stdscr.addstr(0, 0, 'Use Arrows to navigate your EV3-vehicle')
    stdscr.addstr(1, 0, 'Pause your vehicle with key <p>')
    stdscr.addstr(2, 0, 'Terminate with key <q>')

    while True:
        c = stdscr.getch()
        if c in [ord('q'), 27]:
            react(c)
            break
        elif c in [ord('p'),
                   curses.KEY_RIGHT, curses.KEY_LEFT, curses.KEY_UP, curses.KEY_DOWN]:
            react(c)

speed = 0
turn  = 0   
myEV3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99')
stdscr = None

# ops = opOutput_Polarity + b'\x00' + LCX(PORT_A + PORT_D) + LCX(-1)
# myEV3.send_direct_cmd(ops)

curses.wrapper(main)
      
Some remarks:
  • I used the module curses to realize an event handler for keyboard events.
  • Method wrapper takes control over the resources keyboard and terminal. It changes the behavior of the terminal, creates a window object and calls function main.
  • Function main writes some text to the terminal and then waits for key events (method getch).
  • If one of the relevant keys is hit, function react is called, keys <q> or <Ctrl-c> break the loop.
  • Variable speed defines the speed of the faster wheel.
  • Variable turn defines the direction. Value 0 means straight on, positive values result in left turns, negative values in right turns. Large absolute values of turn mean small radius of the turn. Maximum value +200 and minimum value -200 mean circling on place. In the next lesson, we will come back to this definition of driving a turn.
  • I changed speed and turn in steps of 5. This seems to be a good compromize beween precision and speed of reaction.
  • Maybe, the construction of your vehicle needs the change of polarity for both wheels. This is done by the commented code.
  • The right wheel is connected to port A, the left one to port D.
  • For small velocities, the precision of the turns radius becomes worse, because of the rounding to integer values.

Interruption as a design concept

Last lesson, we compared interruption with an impatient and badly behaving character. This lesson shows, that exactly this behaviour can be a practicable design concept.

The remote control program sends unlimited commands, which would move the motors forever, if not interrupted by the next one. A human mind, that controls the driving, wants her corrections immediately take place. It's absolutely correct, that the newer command interrupts the older one.

Another positive aspect of this design concept is the short execution time of the direct commands. They are not time consuming. The movement of the motors lasts long, but the execution of the direct command is finished, when the motors got their new parameters. Control is back immediately and the program is free, to do the next task. The resource EV3 device isn't blocked, it's available for the next direct command.

You have seen, how the interruption process of the remote control is coded. The central part is a loop, that asks for key events. If a relevant key event occurs, a function is called, which changes the parameters of the movement and sends a direct command to the EV3 device.

When we played the triad in c'' (last lesson), we avoided interruption. We had a fixed schedule of all the tones to play. Our opinion was, that every interruption disturbs the plan and needs to be prevented. When drawing the triangle, we already discussed two alternatives for the timing, by the direct command or by the local program. Playing a triad can also be done with all the scheduling in the local program and using interruption. This is an alternative to get the correct timing:


#!/usr/bin/env python3

import ev3, time

my_ev3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99')

ops = b''.join([
    ev3.opUI_Write,
    ev3.LED,
    ev3.LED_RED,
    ev3.opSound,
    ev3.TONE,
    ev3.LCX(1),
    ev3.LCX(262),
    ev3.LCX(0)
])
my_ev3.send_direct_cmd(ops)
time.sleep(0.5)
ops = b''.join([
    ev3.opUI_Write,
    ev3.LED,
    ev3.LED_GREEN,
    ev3.opSound,
    ev3.TONE,
    ev3.LCX(1),
    ev3.LCX(330),
    ev3.LCX(0)
])
my_ev3.send_direct_cmd(ops)
time.sleep(0.5)
ops = b''.join([
    ev3.opUI_Write,
    ev3.LED,
    ev3.LED_RED,
    ev3.opSound,
    ev3.TONE,
    ev3.LCX(1),
    ev3.LCX(392),
    ev3.LCX(0)
])
my_ev3.send_direct_cmd(ops)
time.sleep(0.5)
ops = b''.join([
    ev3.opUI_Write,
    ev3.LED,
    ev3.LED_RED_FLASH,
    ev3.opSound,
    ev3.TONE,
    ev3.LCX(1),
    ev3.LCX(523),
    ev3.LCX(0)
])
my_ev3.send_direct_cmd(ops)
time.sleep(2)
ops = b''.join([
    ev3.opUI_Write,
    ev3.LED,
    ev3.LED_GREEN,
    ev3.opSound,
    ev3.BREAK
])
my_ev3.send_direct_cmd(ops)
      
This version also realizes a fixed schedule, but the timing happens in the program and not on the EV3 device (resp. in the direct command). Again this has the advantage, not to block the EV3 device. As long, as we execute only one task at a time, blocking does not matter. If we try to combine f.i. sound and driving in independent and parallel executed tasks, local timing and interruption are a must.

If we would set sync_mode == SYNC in the program above, the data traffic would grow but we wouldn't hear any difference. It's worth to reflect, that this program runs synchronously because none of the direct commands is time consuming. sync_mode == ASYNC or sync_mode == STD are designed for asynchronous execution, but we also can code synchronous execution with these settings. Asynchronous execution only happens when the direct command is time consuming (resp. the timing is done by the direct command) and the local program does not wait until the direct command ends. Interruption helps to avoid time consuming direct commands and is synchronous execution.

Subclassing EV3

Up to now, we composed the operations directly in our programs. This cries for encapsulation and abstraction. I think of specialized classes, one for vehicles with two drived wheels, one for tones and music and so on. The design should allow to use them parallel. My solution is a number of subclasses of class EV3, which all communicate with the same EV3 device. This says, they have to share resources. To realize this, I have changed the constructor of class EV3:


     |  __init__(self, protocol:str=None, host:str=None, ev3_obj=None)
     |      Establish a connection to a LEGO EV3 device
     |      
     |      Keyword Arguments (either protocol and host or ev3_obj):
     |      protocol: None, 'Bluetooth', 'Usb' or 'Wifi'
     |      host: None or mac-address of the LEGO EV3 (f.i. '00:16:53:42:2B:99')
     |      ev3_obj: None or an existing EV3 object (its connections will be used)
      
The code:

class EV3:

    def __init__(self, protocol: str=None, host: str=None, ev3_obj=None):
        assert ev3_obj or protocol, \
            'Either protocol or ev3_obj needs to be given'
        if ev3_obj:
            assert isinstance(ev3_obj, EV3), \
                'ev3_obj needs to be instance of EV3'
            self._protocol = ev3_obj._protocol
            self._device = ev3_obj._device
            self._socket = ev3_obj._socket
        elif protocol:
            assert protocol in [BLUETOOTH, WIFI, USB], \
                'Protocol ' + protocol + 'is not valid'
            self._protocol = None
            self._device = None
            self._socket = None
            if protocol == BLUETOOTH:
                assert host, 'Protocol ' + protocol + 'needs host-id'
                self._connect_bluetooth(host)
            elif protocol == WIFI:
                self._connect_wifi()
            elif protocol == USB:
                self._connect_usb()
        self._verbosity = 0
        self._sync_mode = STD
        self._msg_cnt = 41
      
A few annotations:
  • There are two ways to create a new instance of class EV3:
    • As before, call the constructor with the arguments protocol and host for a new connection.
    • The alternative is calling it with an existing EV3 object as argument ev3_obj, from which the connections are used.
  • This is not limited to connections. For future extensions, we can share any of the resources.
  • Every class has its own _verbosity and _sync_mode. This is o.k., but we make the message counter _msg_cnt a class attribute.
    
    class EV3:
        _msg_cnt = 41
       
With this in mind, I coded class TwoWheelVehicle, here its constructor:

class TwoWheelVehicle(ev3.EV3):

    def __init__(
            self,
            protocol: str=None,
            host: str=None,
            ev3_obj: ev3.EV3=None
    ):
        super().__init__(protocol=protocol, host=host, ev3_obj=ev3_obj)
        self._polarity = 1
        self._port_left = ev3.PORT_D
        self._port_right = ev3.PORT_A
      
I added two methods:

    def move(self, speed: int, turn: int) -> None:
        assert self._sync_mode != ev3.SYNC, "no unlimited operations allowed in sync_mode SYNC"
        assert isinstance(speed, int), "speed needs to be an integer value"
        assert -100 <= speed and speed <= 100, "speed needs to be in range [-100 - 100]"
        assert isinstance(turn, int), "turn needs to be an integer value"
        assert -200 <= turn and turn <= 200, "turn needs to be in range [-200 - 200]"
        if self._polarity == -1:
            speed *= -1
        if self._port_left < self._port_right:
            turn *= -1
        if turn > 0:
            speed_right = speed
            speed_left  = round(speed * (1 - turn / 100))
        else:
            speed_right = round(speed * (1 + turn / 100))
            speed_left  = speed
        ops = b''.join([
            ev3.opOutput_Speed,
            ev3.LCX(0),              # LAYER
            ev3.LCX(PORT_A),         # NOS
            ev3.LCX(speed_right),    # SPEED
            ev3.opOutput_Speed,
            ev3.LCX(0),              # LAYER
            ev3.LCX(PORT_D),         # NOS
            ev3.LCX(speed_left),     # SPEED
            ev3.opOutput_Start,
            ev3.LCX(0),              # LAYER
            ev3.LCX(PORT_A + PORT_D) # NOS
        ])
        self.send_direct_cmd(ops)
      
and

    def stop(self, brake: bool=False) -> None:
        assert isinstance(brake, bool), "brake needs to be a boolean value"
        if brake:
            br = 1
        else:
            br = 0
        ops_stop = b''.join([
            ev3.opOutput_Stop,
            ev3.LCX(0),                                  # LAYER
            ev3.LCX(self._port_left + self._port_right), # NOS
            ev3.LCX(br)                                  # BRAKE
        ])
        self.send_direct_cmd(ops)
      
We need three properties:

    @property
    def polarity(self):
        return self._polarity
    @polarity.setter
    def polarity(self, value: int):
        assert isinstance(value, int), "polarity needs to be of type int"
        assert value in [1, -1], "allowed polarity values are: -1 or 1"
        self._polarity = value

    @property
    def port_right(self):
        return self._port_right
    @port_right.setter
    def port_right(self, value: int):
        assert isinstance(value, int), "port needs to be of type int"
        assert value in [ev3.PORT_A, ev3.PORT_B, ev3.PORT_C, ev3.PORT_D], "value is not an allowed port"
        self._port_right = value

    @property
    def port_left(self):
        return self._port_left
    @port_left.setter
    def port_left(self, value: int):
        assert isinstance(value, int), "port needs to be of type int"
        assert value in [ev3.PORT_A, ev3.PORT_B, ev3.PORT_C, ev3.PORT_D], "value is not an allowed port"
        self._port_left = value
      
That's it, we have a new class TwoWheelVehicle with this API:

Help on module ev3_vehicle:

NAME
    ev3_vehicle - EV3 vehicle

CLASSES
    ev3.EV3(builtins.object)
        TwoWheelVehicle
    
    class TwoWheelVehicle(ev3.EV3)
     |  ev3.EV3 vehicle with two drived Wheels
     |  
     |  Method resolution order:
     |      TwoWheelVehicle
     |      ev3.EV3
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __init__(self, protocol:str=None, host:str=None, ev3_obj:ev3.EV3=None)
     |      Establish a connection to a LEGO EV3 device
     |      
     |      Keyword Arguments (either protocol and host or ev3_obj):
     |      protocol
     |        BLUETOOTH == 'Bluetooth'
     |        USB == 'Usb'
     |        WIFI == 'Wifi'
     |      host: mac-address of the LEGO EV3 (f.i. '00:16:53:42:2B:99')
     |      ev3_obj: an existing EV3 object (its connections will be used)
     |  
     |  move(self, speed:int, turn:int) -> None
     |      Start unlimited movement of the vehicle
     |      
     |      Arguments:
     |      speed: speed in percent [-100 - 100]
     |        > 0: forward
     |        < 0: backward
     |      turn: type of turn [-200 - 200]
     |        -200: circle right on place
     |        -100: turn right with unmoved right wheel
     |         0  : straight
     |         100: turn left with unmoved left wheel
     |         200: circle left on place
     |  
     |  stop(self, brake:bool=False) -> None
     |      Stop movement of the vehicle
     |      
     |      Arguments:
     |      brake: flag if activating brake
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  polarity
     |      polarity of motor rotation (values: -1, 1, default: 1)
     |  
     |  port_left
     |      port of left wheel (default: PORT_D)
     |  
     |  port_right
     |      port of right wheel (default: PORT_A)
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from ev3.EV3:
     |  
     |  __del__(self)
     |      closes the connection to the LEGO EV3
     |  
     |  send_direct_cmd(self, ops:bytes, local_mem:int=0, global_mem:int=0) -> bytes
     |      Send a direct command to the LEGO EV3
     |      
     |      Arguments:
     |      ops: holds netto data only (operations), the following fields are added:
     |        length: 2 bytes, little endian
     |        counter: 2 bytes, little endian
     |        type: 1 byte, DIRECT_COMMAND_REPLY or DIRECT_COMMAND_NO_REPLY
     |        header: 2 bytes, holds sizes of local and global memory
     |      
     |      Keyword Arguments:
     |      local_mem: size of the local memory
     |      global_mem: size of the global memory
     |      
     |      Returns: 
     |        sync_mode is STD: reply (if global_mem > 0) or message counter
     |        sync_mode is ASYNC: message counter
     |        sync_mode is SYNC: reply of the LEGO EV3
     |  
     |  wait_for_reply(self, counter:bytes) -> bytes
     |      Ask the LEGO EV3 for a reply and wait until it is received
     |      
     |      Arguments:
     |      counter: is the message counter of the corresponding send_direct_cmd
     |      
     |      Returns:
     |      reply to the direct command
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from ev3.EV3:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  sync_mode
     |      sync mode (standard, asynchronous, synchronous)
     |      
     |      STD:   Use DIRECT_COMMAND_REPLY if global_mem > 0,
     |             wait for reply if there is one.
     |      ASYNC: Use DIRECT_COMMAND_REPLY if global_mem > 0,
     |             never wait for reply (it's the task of the calling program).
     |      SYNC:  Always use DIRECT_COMMAND_REPLY and wait for reply.
     |      
     |      The general idea is:
     |      ASYNC: Interruption or EV3 device queues direct commands,
     |             control directly comes back.
     |      SYNC:  EV3 device is blocked until direct command is finished,
     |             control comes back, when direct command is finished.               
     |      STD:   NO_REPLY like ASYNC with interruption or EV3 queuing,
     |             REPLY like SYNC, synchronicity of program and EV3 device.
     |  
     |  verbosity
     |      level of verbosity (prints on stdout).
      
From now on, we will use class TwoWheelVehicle, when we move a vehicle with two drived wheels. Actually this was more an exercise, than a real thing, but next lesson, we will add functionality to this class. For the moment you may ask, if effort and benefit really are balanced. But look, our program has become shorter:

#!/usr/bin/env python3

import curses
import ev3, ev3_vehicle

def react(c):
    global speed, turn, my_vehicle
    if c in [ord('q'), 27, ord('p')]:
        my_vehicle.stop()
        return
    elif c == curses.KEY_LEFT:
        turn += 5
        turn = min(turn, 200)
    elif c == curses.KEY_RIGHT:
        turn -= 5
        turn = max(turn, -200)
    elif c == curses.KEY_UP:
        speed += 5
        speed = min(speed, 100)
    elif c == curses.KEY_DOWN:
        speed -= 5
        speed = max(speed, -100)
    stdscr.addstr(5, 0, 'speed: {}, turn: {}      '.format(speed, turn))
    my_vehicle.move(speed, turn)

def main(window) -> None:
    global stdscr
    stdscr = window
    stdscr.clear()      # print introduction
    stdscr.refresh()
    stdscr.addstr(0, 0, 'Use Arrows to navigate your EV3-vehicle')
    stdscr.addstr(1, 0, 'Pause your vehicle with key <p>')
    stdscr.addstr(2, 0, 'Terminate with key <q>')

    while True:
        c = stdscr.getch()
        if c in [ord('q'), 27]:
            react(c)
            break
        elif c in [ord('p'),
                   curses.KEY_RIGHT, curses.KEY_LEFT, curses.KEY_UP, curses.KEY_DOWN]:
            react(c)

speed = 0
turn  = 0   
my_vehicle = ev3_vehicle.TwoWheelVehicle(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99')
stdscr = None

curses.wrapper(main)
      
If you did download module ev3_vehicle.py from ev3-python3, you need to call the constructor of TwoWheelVehicle with two additional arguments radius_wheel and tread. For the moment you may set them to any positive value.

Conclusion

Now we know, how to move motors and we got experience with two important design concepts, interruption and subclassing. Beside we coded something like a real app, a remote control for vehicles.

I hope, that your remote control works and made you a technic-star. We are at the end of this lesson. A glance at the document EV3 Firmware Developer Kit tells you, that there exists a number of additional operations to move motors. Most of them are for precise, synchronized and smooth movements. This will be the topic of our next lesson. I hope to see you again.

1 comment: