Monday 11 January 2016

Lesson 1 - The art of doing nothing

EV3 Direct commands - Lesson 01

LEGO's EV3 is a fascinating game tool. Its standard way to program is LEGO's graphical programming tool. You can write programs, transfer them to your EV3 brick and start them. But there is another way to interact with your EV3. Look at it as a server and send commands to it, which will be answered with data and/or actions. In this case, the machine, your program runs on, is the client. This opens fascinating new perspectives. If the program runs on your smartphone, you win great chances of interactivity and handiness. If your client is a PC or Laptop, you win a comfortable keyboard and display. Another new option is the combination of multiple EV3s in one robot. One client communicates with multiple servers, which allows robots with unlimited numbers of motors and sensors. Or think of your EV3 as a machine, that produces data. The client can continually receive data from the EV3s sensors, which also opens new chances. If you want to enter this new world, you have to use EV3s direct commands, which needs some work of your mind. If you are ready to invest it, then read further on, if not, be happy with your EV3 as it is and wait until others present cool new applications.

Before we start with the real stuff, we cast a first glance to the communication protocols of the EV3 and to the character of LEGO Direct Commands. Your EV3 provides three types of communication, bluetooth, wifi and USB. Bluetooth and USB can be used without any additional equipment, the communication via wifi needs a dongle. All three allow you, to develop applications, that run on any computer and communicate with your EV3 brick. Maybe you know EV3s predecessor, the NXT and its communication protocol. It offered about 20 system commands, which resembled the call of functions. LEGO changed its philosophy, the EV3 offers a command syntax in machine code. On one side this means freedom to realize real algorithms but on the other side it became harder to get in.

The official documents, I found, are pure technical documentation. They lack in motivation and in educational aspects. I hope, this text will be a comfortable entrance to the inside of your EV3. If you are already a programmer or you try to get one, you will immerse into the world of bits and bytes. If not, it will become hard but the only alternative is, to keep your hands apart. Give it a try, beneath the effective communication with your LEGO EV3 you will learn a lot about machine code, the basics of all computers.

Documents to know

LEGO has published good and detailed documentation, which you can download from: http://www.lego.com/en-gb/mindstorms/downloads. For our purpose, you absolutely need to look at the documents, you find under the title Advanced Users - Developer Kits (PC/MAC). The document EV3 Firmware Developer Kit is the reference book of the LEGO EV3 direct commands. I hope you will intensively read it.

There exists a communication library in C#, that uses direct commands to communicate with a LEGO EV3. If you like to use software out of the box and if you like C#, this may be your choice: http://www.monobrick.dk.

My original idea was, not to publish any sources. Programming is more fun, when the functionality grows step by step. Bugs are part of the game, searching for bugs is hard but the daily life of any programming person. Short conclusion, the code grew to a volume and complexity, where the original idea became unrealistic. I have published the code on github, from where you can download: ev3-python3.

Lesson 1 - The art of doing nothing

You did it, you really want to step in, that's fine! This lesson is about the most basic communication. We will implement a first call and reply cycle. Via wifi, bluetooth or USB you will send information to your EV3 and you will get a well defined answer. Don't hold your breath, we will not start with an application, that fills the world with wonder. It's the opposite, it will do absolutely nothing. This sounds less than it is, if you manage to do that, lean back and feel happy, you are on the way.

Little excursion to the nomenclature of bits and bytes

Maybe you already know, how to write binary and hexadecimal numbers, the meaning of big and little endian and so on. If you really can write the value 156 as a 4-bytes integer in binary notation and little endian format, then you can skip this excursion. If not, you need to read it, because you really need to know that.

Let's start with the basics! Nearly all modern computers group 8 bits to one byte and address their memory per byte (your EV3 is a modern computer and does so). In the following we use the following notation for binary numbers: 0b 1001 1100

The leading 0b tells us, that a binary notation of a number will follow, the 8 digits per byte are grouped 4 by 4 as half bytes. It is the binary notation of the number 156, which you can read as:
156 = 1*128 + 0*64 + 0*32 + 1*16 + 1*8 + 1*4 + 0*2 + 0*1. Alternative interpretations of the same byte are possible. It can be red as a sequence of 8 flags or it can be the ascii code of the sign £. The interpretation depends on the context. In the moment we concentrate on numbers.

Binary notation is very lengthy, so it is a common habit to write the half bytes as hexadecimal numbers, where the letters A to F are the digits for the numbers 10 to 15. Hexadecimal notation is compact and its transformation from and to binary notation is easy. This is because one hexadecimal digit stands for one half byte. The notation for hexadecimal numbers (here the value 156) is: 0x 9C. You can read it as: 156 = 9*16 + 12*1. The leading 0x tells us, that hexadecimal notation will follow. Because of its compactness, we can write and read larger numbers. As a 4-byte integer, the value 156 is written as: 0x 00 00 00 9C

We will separate the bytes by colons ":" or vertical bars "|". We use vertical bars for the high order separation and colons for the low order. We will write the 2-byte integer of the value 156 as: 0x|00:9C| Now we can hold lists of values in a single row. The sequence of 255 (as an unsigned 1-byte integer), 156 (as 2-byte integer) and 65,536 (as 4-byte integer) is written as: 0x|FF|00:9C|00:01:00:00|

What about negative numbers? Most computer languages distinct between signed and unsigned integers. If integers are signed, their first bit becomes a flag for negative sign and the integer gets another range. A signed 1-byte integer has the range -128 to 127, a signed 2-byte integer the range -32,768 to 32,767 and so on. The value of negative numbers is calculated as the minimal value (-128, -32,768 etc.) plus the value of the rest. The lowest value of a signed 1-byte integer, -128 is written as: 0b 1000 0000 or 0x|80| and the value -1 as signed 2-byte integer (-32,768 + 32,767): 0b 1111 1111 1111 1111 or 0x|FF:FF|

And what is little endian? O.k. I will not keep back this secret. The little endian format inverses the positions of the bytes (what you are used to, is named big endian). The value 156 as 2-byte integer in little endian format is written as: 0x|9C:00|

Maybe this sounds like a bad joke, but I'm sorry, EV3 direct commands read and write all numbers in little endian format, which is not my fault. But I can give you some comfort. First, this lesson uses numbers sparely. Second, there exist good tools to manage little endian numbers. In python, you can use the struct module, in java, ByteBuffer may be the object of your choice.

The direct command for doing nothing

The first example presents the simplest of all possible direct commands. You will send a message to your EV3 and hopefully it will answer. Let's look at the message to send, it consists of the following 8 bytes:

------------------------- \ len \ cnt \ty\ hd \op\ ------------------------- 0x|06:00|2A:00|00|00:00|01| ------------------------- \ 6 \ 42 \Re\ 0,0 \N \ \ \ \ \ \o \ \ \ \ \ \p \ -------------------------

The message itself is the line, that starts wit 0x. On top of the message you see some annotations about the type of the parts of the message. The bottom shows annotations about their values. The 8 bytes of the message consist of the following parts:

  • Length of the message (bytes 0, 1): The first two byte are not part of the direct command itself. They are part of the communication protocols, which in case of the EV3 can be Wifi, Bluetooth or USB. The length is coded as a 2-byte unsigned integer in little endian format, 0x|06:00| therefore stands for the value 6.
  • Message counter (bytes 2, 3): The next two bytes are the footprint of this direct command. The message counter will be included in the answer and allows to match the direct command and its reply. This too is a 2-byte unsigned integer in little endian format. In our case we set the message counter to 0x|2A:00|, which is the value 42.
  • Message type (byte 4): It may have the following two values:
    • DIRECT_COMMAND_REPLY = 0x|00|
    • DIRECT_COMMAND_NO_REPLY = 0x|80|
    In our case we want the EV3 to reply the message.
  • Header (bytes 5, 6): The next two bytes, the last in front of the first operation are the header. It includes a combination of two numbers, which define the memory sizes of the direct command (yes, its plural, we have two memories, a local and a global one). We soon will come back to the details of this memory sizes. For the moment we are lucky, that our command does not need any memory, therefore we set the header to 0x|00:00|.
  • Operations (starting at byte 7): In our case one single byte, that stands for: opNOP = 0x|01|, do nothing, the idle operation of the EV3.

Sending Messages to the EV3

Our task is, to sent the above described message to our EV3. How to do it? You can choose between three communication protocols, Bluetooth, Wifi and USB and you can choose any programming language, that supports at least one of the communication protocols. In the following I present examples in python and in java. If you miss your favorite language, it would be great to translate the programs to your preferred computer language and send them to me. They will be published with credits at this place.

Bluetooth

You need access to a computer with Bluetooth enabled and you need to enable Bluetooth on your EV3. Next you have to couple the two devices. This can be initiated from the EV3 or from your computer. The process is described in the EV3's user guide. If you need help, you will find tutorials on the web, here a link to a LEGO page: http://www.lego.com/en-gb/mindstorms/support/. The coupling process will present you the mac-address of your EV3. You need to note it. As an alternative, you can read the mac-address from your EV3's display under Brick Info / ID.

python

You have to do the following steps:

  • Copy the code into a file with the name EV3_do_nothing_bluetooth.py.
  • Change the mac-address from 00:16:53:42:2B:99 to the value of your EV3.
  • Open a terminal and navigate to the directory of your program.
  • Run it by typing python3 EV3_do_nothing_bluetooth.py.


#!/usr/bin/env python3

import socket
import struct

class EV3():
    def __init__(self, host: str):
        self._socket = socket.socket(
            socket.AF_BLUETOOTH,
            socket.SOCK_STREAM,
            socket.BTPROTO_RFCOMM
        )
        self._socket.connect((host, 1))

    def __del__(self):
        if isinstance(self._socket, socket.socket):
            self._socket.close()

    def send_direct_cmd(self, ops: bytes, local_mem: int=0, global_mem: int=0) -> bytes:
        cmd = b''.join([
            struct.pack('<h', len(ops) + 5),
            struct.pack('<h', 42),
            DIRECT_COMMAND_REPLY,
            struct.pack('<h', local_mem*1024 + global_mem),
            ops
        ])
        self._socket.send(cmd)
        print_hex('Sent', cmd)
        reply = self._socket.recv(5 + global_mem)
        print_hex('Recv', reply)
        return reply

def print_hex(desc: str, data: bytes) -> None:
    print(desc + ' 0x|' + ':'.join('{:02X}'.format(byte) for byte in data) + '|')

DIRECT_COMMAND_REPLY = b'\x00'
opNop = b'\x01'
my_ev3 = EV3('00:16:53:42:2B:99')
ops_nothing = opNop
my_ev3.send_direct_cmd(ops_nothing)
    
The implementation of socket depends on the operating system of your computer. If AF_BLUETOOTH is not supported (you got an error message like AttributeError: module 'socket' has no attribute 'AF_BLUETOOTH'), you can use pybluez, which means you import bluetooth instead of socket. In my case this says:
  • install pybluez with pip3: sudo pip3 install pybluez
  • change the program:
    
    #!/usr/bin/env python3
    
    import bluetooth
    import struct
    
    class EV3():
        def __init__(self, host: str):
            self._socket = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
            self._socket.connect((host, 1))
    
        def __del__(self):
            if isinstance(self._socket, bluetooth.BluetoothSocket):
                self._socket.close()
    ...
     
  • run the program

java

My choice to communicate with bluetooth-devices is bluecove. After downloading the java-archive bluecove-2.1.0.jar (on Unix also bluecove-gpl-2.1.0.jar), you can add them to your classpath. On my Unix-machine, this is done by:
export CLASSPATH=$CLASSPATH:./bluecove-2.1.0.jar:./bluecove-gpl-2.1.0.jar
Then, you have to do the following steps:

  • Copy the code into a file with the name EV3_do_nothing_bluetooth.java.
  • Change the mac-address from 001653422B99 to the value of your EV3.
  • Open a terminal and navigate to the directory of your program.
  • Compile it by typing javac EV3_do_nothing_bluetooth.java.
  • Run it by typing java EV3_do_nothing_bluetooth


import java.io.*;
import javax.microedition.io.*;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import java.io.*;

public class EV3_do_nothing_bluetooth {
    static final String mac_addr = "001653422B99";

    static final byte  opNop                        = (byte)  0x01;
    static final byte  DIRECT_COMMAND_REPLY         = (byte)  0x00;

    static InputStream in;
    static OutputStream out;

    public static void connectBluetooth ()
 throws IOException {
     String s = "btspp://" + mac_addr + ":1";
     StreamConnection c = (StreamConnection) Connector.open(s);
     in = c.openInputStream();
     out = c.openOutputStream();
    }

    public static ByteBuffer sendDirectCmd (ByteBuffer operations,
         int local_mem, int global_mem)
 throws IOException {
 ByteBuffer buffer = ByteBuffer.allocateDirect(operations.position() + 7);
 buffer.order(ByteOrder.LITTLE_ENDIAN);
 buffer.putShort((short) (operations.position() + 5));   // length
 buffer.putShort((short) 42);                            // counter
 buffer.put(DIRECT_COMMAND_REPLY);                       // type
 buffer.putShort((short) (local_mem*1024 + global_mem)); // header
 for (int i=0; i < operations.position(); i++) {         // operations
     buffer.put(operations.get(i));
 }

 byte[] cmd = new byte [buffer.position()];
 for (int i=0; i<buffer.position(); i++) cmd[i] = buffer.get(i);
 out.write(cmd);
 printHex("Sent", buffer);

 byte[] reply = new byte[global_mem + 5];
 in.read(reply);
 buffer = ByteBuffer.wrap(reply);
 buffer.position(reply.length);
 printHex("Recv", buffer);

 return buffer;
    }

    public static void printHex(String desc, ByteBuffer buffer) {
 System.out.print(desc + " 0x|");
 for (int i= 0; i < buffer.position() - 1; i++) {
     System.out.printf("%02X:", buffer.get(i));
 }
 System.out.printf("%02X|", buffer.get(buffer.position() - 1));
 System.out.println();
    }

    public static void main (String args[] ) {
 try {
     connectBluetooth();

     ByteBuffer operations = ByteBuffer.allocateDirect(1);
     operations.put(opNop);

     ByteBuffer reply = sendDirectCmd(operations, 0, 0);
 }
 catch (Exception e) {
     e.printStackTrace(System.err);
 }
    }
}
 

Wifi

You need a wifi dongle to connect your EV3 to your local network. The first part of the following document describes this process: http://www.monobrick.dk/guides/how-to-establish-a-wifi-connection-with-the-ev3-brick/. Now your EV3 is part of the local network and has a network address. From all hosts of the network you can communicate with it. As described in the above mentioned document, it needs the following steps to establish a TCP/IP connection to the EV3:

  • Listen for a UDP broadcast from the EV3 on port 3015.
  • Send a UDP message back to the EV3 to make it accept a TCP/IP connection.
  • Establish a TCP/IP connection on port 5555.
  • Send an unlock message to the EV3 over TCP/IP.

python

You have to do the following steps:

  • Copy the code into a file with the name EV3_do_nothing_wifi.py.
  • Open a terminal and navigate to the directory of your program.
  • Run it by typing python3 EV3_do_nothing_wifi.py


#!/usr/bin/env python3

import socket
import struct
import re

class EV3():
    def __init__(self, host: str):
        # listen on port 3015 for a UDP broadcast from the EV3
        UDPSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        UDPSock.bind(('', 3015))
        data, addr = UDPSock.recvfrom(67)

        # pick serial number, port, name and protocol
        # from the broadcast message
        matcher = re.search('Serial-Number: (\w*)\s\n' +
                            'Port: (\d{4,4})\s\n' +
                            'Name: (\w+)\s\n' +
                            'Protocol: (\w+)\s\n',
                            data.decode('utf-8'))
        serial_number = matcher.group(1)
        port = matcher.group(2)
        name = matcher.group(3)
        protocol = matcher.group(4)
        if serial_number.upper() != host.replace(':', '').upper():
            self._socket = None
            raise ValueError('found ev3 but not ' + host)

        # Send an UDP message back to the EV3
        # to make it accept a TCP/IP connection
        UDPSock.sendto(' '.encode('utf-8'), (addr[0], int(port)))
        UDPSock.close()

        # Establish a TCP/IP connection with EV3s address and port
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._socket.connect((addr[0], int(port)))

        # Send an unlock message to the EV3 over TCP/IP
        msg = ''.join([
            'GET /target?sn=' + serial_number + 'VMTP1.0\n',
            'Protocol: ' + protocol
        ])
        self._socket.send(msg.encode('utf-8'))
        reply = self._socket.recv(16).decode('utf-8')
        if not reply.startswith('Accept:EV340'):
            raise IOError('No wifi connection to ' + name + ' established')

    def __del__(self):
        if isinstance(self._socket, socket.socket):
            self._socket.close()

    def send_direct_cmd(self, ops: bytes, local_mem: int=0, global_mem: int=0) -> bytes:
        cmd = b''.join([
            struct.pack('<h', len(ops) + 5),
            struct.pack('<h', 42),
            DIRECT_COMMAND_REPLY,
            struct.pack('<h', local_mem*1024 + global_mem),
            ops
        ])
        self._socket.send(cmd)
        print_hex('Sent', cmd)
        reply = self._socket.recv(5 + global_mem)
        print_hex('Recv', reply)
        return reply

def print_hex(desc: str, data: bytes) -> None:
    print(desc + ' 0x|' + ':'.join('{:02X}'.format(byte) for byte in data) + '|')

DIRECT_COMMAND_REPLY = b'\x00'
opNop = b'\x01'
my_ev3 = EV3('00:16:53:42:2B:99')
ops_nothing = opNop
my_ev3.send_direct_cmd(ops_nothing)
    

java

You have to do the following steps:

  • Copy the code into a file with the name EV3_do_nothing_wifi.java.
  • Open a terminal and navigate to the directory of your program.
  • Compile it by typing javac EV3_do_nothing_wifi.java.
  • Run it by typing java EV3_do_nothing_wifi.


import java.net.Socket;
import java.net.SocketException;
import java.net.ServerSocket;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ByteOrder;

import java.io.*;
import java.util.regex.*;

public class EV3_do_nothing_wifi {
    static final byte  opNop                        = (byte)  0x01;
    static final byte  DIRECT_COMMAND_REPLY         = (byte)  0x00;

    static InputStream in;
    static OutputStream out;

    public static void connectWifi ()
 throws IOException, SocketException {
     // listen for a UDP broadcast from the EV3 on port 3015
     DatagramSocket listener = new DatagramSocket(3015);
     DatagramPacket packet_r = new DatagramPacket(new byte[67], 67);
     listener.receive(packet_r); // receive the broadcast message
     String broadcast_message = new String(packet_r.getData());
     /* pick serial number, port, name and protocol 
        from the broadcast message */
     Pattern broadcast_pattern = 
     Pattern.compile("Serial-Number: (\\w*)\\s\\n" +
                            "Port:\\s(\\d{4,4})\\s\\n" +
                            "Name:\\s(\\w+)\\s\\n" +
                            "Protocol:\\s(\\w+)\\s\\n");
     Matcher matcher = broadcast_pattern.matcher(broadcast_message);
     String serial_number, name, protocol;
     int port;
     if(matcher.matches()) {
  serial_number = matcher.group(1);
  port = Integer.valueOf(matcher.group(2));
  name = matcher.group(3);
  protocol = matcher.group(4);
     }
     else {
  throw new IOException("Unexpected Broadcast message: " + broadcast_message);
     }
     InetAddress adr = packet_r.getAddress();
     // connect the EV3 with its address and port
     listener.connect(adr, port);
     /* Send an UDP message back to the EV3 
        to make it accept a TCP/IP connection */
     listener.send(new DatagramPacket(new byte[1], 1));
     // close the UDP connection
     listener.close();

     // Establish a TCP/IP connection with EV3s address and port
     Socket socket = new Socket(adr, port);
     in     = socket.getInputStream();
     out    = socket.getOutputStream();

     // Send an unlock message to the EV3 over TCP/IP
     String unlock_message = "GET /target?sn=" + serial_number + 
                                    "VMTP1.0\n" + 
                                    "Protocol: " + protocol;
     out.write(unlock_message.getBytes());

     byte[] reply = new byte[16];           // read reply
     in.read(reply);
     if (! (new String(reply)).startsWith("Accept:EV340")) {
  throw new IOException("No wifi connection established " + name);
     } 
    }

    public static ByteBuffer sendDirectCmd (ByteBuffer operations,
         int local_mem, int global_mem)
 throws IOException {
 ByteBuffer buffer = ByteBuffer.allocateDirect(operations.position() + 7);
 buffer.order(ByteOrder.LITTLE_ENDIAN);
 buffer.putShort((short) (operations.position() + 5));   // length
 buffer.putShort((short) 42);                            // counter
 buffer.put(DIRECT_COMMAND_REPLY);                       // type
 buffer.putShort((short) (local_mem*1024 + global_mem)); // header
 for (int i=0; i<operations.position(); i++) {           // operations
     buffer.put(operations.get(i));
 }

 byte[] cmd = new byte [buffer.position()];
 for (int i=0; i<buffer.position(); i++) cmd[i] = buffer.get(i);
 out.write(cmd);
 printHex("Sent", buffer);

 byte[] reply = new byte[global_mem + 5];
 in.read(reply);
 buffer = ByteBuffer.wrap(reply);
 buffer.position(reply.length);
 printHex("Recv", buffer);

 return buffer;
    }

    public static void printHex(String desc, ByteBuffer buffer) {
 System.out.print(desc + " 0x|");
 for (int i= 0; i<buffer.position() - 1; i++) {
     System.out.printf("%02X:", buffer.get(i));
 }
 System.out.printf("%02X|", buffer.get(buffer.position() - 1));
 System.out.println();
    }

    public static void main (String args[] ) {
 try {
     connectWifi();

     ByteBuffer operations = ByteBuffer.allocateDirect(1);
     operations.put(opNop);

     ByteBuffer reply = sendDirectCmd(operations, 0, 0);
 }
 catch (Exception e) {
     e.printStackTrace(System.err);
 }
    }
}
    

USB

The Universal Serial Bus is an industrial standard to connect electronic devices. Your EV3 has a 2.0 Mini-B receptacle (titled PC). This is the comunication-protocol with the best performance, but it needs a wire. On your computer, where your program runs, you need the permission to communicate with the LEGO EV3. In my case, I had to add the following udev-rule (my operation system is Unix):
ATTRS{idVendor}=="0694",ATTRS{idProduct}=="0005",MODE="0666",GROUP=<group>

This identifies EV3-devices by their vendor-id 0x0694 and product-id 0x0005. The mode 0666 allows read- and write-access for all users, that belong to <group> (change <group> to a group, you are member of).

The EV3-USB-device descriptor shows one configuration with one interface and two endpoints, 0x01 for sending data to the EV3, 0x81 for receiving data from the EV3. The data are sent and received in packages of 1024 bytes.

python

Maybe, you need to install pyusb. For my system this was done with:
sudo pip3 install --pre pyusb If pyusb is installed, you have to do the following steps:

  • Copy the code into a file with the name EV3_do_nothing_usb.py.
  • Open a terminal and navigate to the directory of your program.
  • Run it by typing python3 EV3_do_nothing_usb.py


#!/usr/bin/env python3

import usb.core
import struct

class EV3():
    def __init__(self, host: str):
        self._device = usb.core.find(idVendor=ID_VENDOR_LEGO, idProduct=ID_PRODUCT_EV3)
        if self._device is None:
            raise RuntimeError("No Lego EV3 found")
        serial_number = usb.util.get_string(self._device, self._device.iSerialNumber)
        if serial_number.upper() != host.replace(':', '').upper():
            raise ValueError('found ev3 but not ' + host)
        if self._device.is_kernel_driver_active(0) is True:
            self._device.detach_kernel_driver(0)
        self._device.set_configuration()
        self._device.read(EP_IN, 1024, 100)

    def __del__(self): pass

    def send_direct_cmd(self, ops: bytes, local_mem: int=0, global_mem: int=0) -> bytes:
        cmd = b''.join([
            struct.pack('<h', len(ops) + 5),
            struct.pack('<h', 42),
            DIRECT_COMMAND_REPLY,
            struct.pack('<h', local_mem*1024 + global_mem),
            ops
        ])
        self._device.write(EP_OUT, cmd, 100)
        print_hex('Sent', cmd)
        reply = self._device.read(EP_IN, 1024, 100)[0:5+global_mem]
        print_hex('Recv', reply)
        return reply

def print_hex(desc: str, data: bytes) -> None:
    print(desc + ' 0x|' + ':'.join('{:02X}'.format(byte) for byte in data) + '|')

ID_VENDOR_LEGO = 0x0694
ID_PRODUCT_EV3 = 0x0005
EP_IN  = 0x81
EP_OUT = 0x01
DIRECT_COMMAND_REPLY = b'\x00'
opNop = b'\x01'
my_ev3 = EV3('00:16:53:42:2B:99')
ops_nothing = opNop
my_ev3.send_direct_cmd(ops_nothing)
    

java

My choice to communicate with usb-devices is usb4java. After the download of the java-archives, you can add them to your classpath. On my Unix-machine, this is done by:
export CLASSPATH=$CLASSPATH:./usb4java-1.2.0.jar:./libusb4java-1.2.0-linux-x86_64.jar Then, you have to do the following steps:

  • Copy the code into a file with the name EV3_do_nothing_usb.java.
  • Open a terminal and navigate to the directory of your program.
  • Compile it by typing javac EV3_do_nothing_usb.java.
  • Run it by typing java EV3_do_nothing_usb


import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.DeviceList;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ByteOrder;

public class EV3_do_nothing_usb {
    static final short ID_VENDOR_LEGO = (short) 0x0694;
    static final short ID_PRODUCT_EV3 = (short) 0x0005;
    static final byte  EP_IN          = (byte)  0x81;
    static final byte  EP_OUT         = (byte)  0x01;

    static final byte  opNop                        = (byte)  0x01;
    static final byte  DIRECT_COMMAND_REPLY         = (byte)  0x00;

    static DeviceHandle handle;

    public static void connectUsb () {
 int result = LibUsb.init(null);
 Device device = null;
 DeviceList list = new DeviceList();
 result = LibUsb.getDeviceList(null, list);
 if (result < 0){
     throw new RuntimeException("Unable to get device list. Result=" + result);
 }
 boolean found = false;
 for (Device dev: list) {
     DeviceDescriptor descriptor = new DeviceDescriptor();
     result = LibUsb.getDeviceDescriptor(dev, descriptor);
     if (result != LibUsb.SUCCESS) {
  throw new LibUsbException("Unable to read device descriptor", result);
     }
     if (  descriptor.idVendor()  == ID_VENDOR_LEGO
    || descriptor.idProduct() == ID_PRODUCT_EV3) {
  device = dev;
  found = true;
  break;
     }
 }
 LibUsb.freeDeviceList(list, true);
 if (! found) throw new RuntimeException("Lego EV3 device not found.");

 handle = new DeviceHandle();
 result = LibUsb.open(device, handle);
 if (result != LibUsb.SUCCESS) {
     throw new LibUsbException("Unable to open USB device", result);
 }
 boolean detach = LibUsb.kernelDriverActive(handle, 0) != 0;

 if (detach) result = LibUsb.detachKernelDriver(handle, 0);
 if (result != LibUsb.SUCCESS) {
     throw new LibUsbException("Unable to detach kernel driver", result);
 }

 result = LibUsb.claimInterface(handle, 0);
 if (result != LibUsb.SUCCESS) {
     throw new LibUsbException("Unable to claim interface", result);
 }
    }

    public static ByteBuffer sendDirectCmd (ByteBuffer operations,
         int local_mem, int global_mem) {
 ByteBuffer buffer = ByteBuffer.allocateDirect(operations.position() + 7);
 buffer.order(ByteOrder.LITTLE_ENDIAN);
 buffer.putShort((short) (operations.position() + 5));   // length
 buffer.putShort((short) 42);                            // counter
 buffer.put(DIRECT_COMMAND_REPLY);                       // type
 buffer.putShort((short) (local_mem*1024 + global_mem)); // header
 for (int i=0; i < operations.position(); i++) {         // operations
     buffer.put(operations.get(i));
 }

 IntBuffer transferred = IntBuffer.allocate(1);
 int result = LibUsb.bulkTransfer(handle, EP_OUT, buffer, transferred, 100); 
 if (result != LibUsb.SUCCESS) {
     throw new LibUsbException("Unable to write data", transferred.get(0));
 }
 printHex("Sent", buffer);

 buffer = ByteBuffer.allocateDirect(1024);
 transferred = IntBuffer.allocate(1);
 result = LibUsb.bulkTransfer(handle, EP_IN, buffer, transferred, 100);
 if (result != LibUsb.SUCCESS) {
     throw new LibUsbException("Unable to read data", result);
 }
 buffer.position(global_mem + 5);
 printHex("Recv", buffer);

 return buffer;
    }

    public static void printHex(String desc, ByteBuffer buffer) {
 System.out.print(desc + " 0x|");
 for (int i= 0; i < buffer.position() - 1; i++) {
     System.out.printf("%02X:", buffer.get(i));
 }
 System.out.printf("%02X|", buffer.get(buffer.position() - 1));
 System.out.println();
    }

    public static void main (String args[] ) {
 try {
     connectUsb();

     ByteBuffer operations = ByteBuffer.allocateDirect(1);
     operations.put(opNop);

     ByteBuffer reply = sendDirectCmd(operations, 0, 0);

     LibUsb.releaseInterface(handle, 0);
     LibUsb.close(handle);
 }
 catch (Exception e) {
     e.printStackTrace(System.err);
 }
    }
}
    

Reply-Message

If you succeed with one of the alternatives, you get the following output, which is the reply message of your direct command:

---------------- \ len \ cnt \rs\ –--------------- 0x|03:00|2A:00|02| –--------------- \ 3 \ 42 \ok\ ----------------

The first two bytes are well known, it's the little endian message length of the reply. In our case, the reply message is 3 bytes long. The next two bytes are the message counter, well known too, the footprint of the message you sent, which was 42.

The last byte is the return status, with 2 possible values:

  • DIRECT_REPLY = 0x|02|: the direct command was successfully operated
  • DIRECT_REPLY_ERROR = 0x|04|: the direct command ended with an error.

If you really got this Reply-Message, you are inside. Congratulations!

Details of the Header

Above we skipped the description of the header details. It was mentioned, that the header holds two numbers, which define the sizes of memory.

The first number is the size of the local memory, which is the address space, where you can hold intermediate information. The second number describes the size of the global memory, which is the address space of the output. In case of DIRECT_COMMAND_REPLY, the global memory will be sent back as part of the reply.

The local memory has a maximum of 63 byte, the global memory a maximum of 1019 byte. That means, that the size of the local memory needs 6 bits, the size of the global memory needs 10 bits. All together it can be hold in two bytes, if one byte is shared. Exactly this is done. If you write the header bytes in opposite order, which is the familiar big endian and in binary notation with groups of half bytes, you get: 0b LLLL LLGG GGGG GGGG. The first 6 bits hold the size of the local memory with the range 0-63. The 10 bits at the end hold the size of the global memory with the range 0-1020. In little endian you get: 0b GGGG GGGG LLLL LLGG. If for instance your global memory has the size of 6 bytes and your local memory needs the size of 16 bytes, then your header is 0b 0000 0110 0100 0000 or in hex notation 0x 06 40.

This was the descriptive version, now a second approach in declarative manner. If local_mem is the size of the local memory and global_mem the size of the global memory, then calculate: header = local_mem * 1024 + global_mem Write header as a 2-byte-integer in little endian and you get the two header bytes. If you still have questions, wait for the following lessons, you will see lots of headers and learning from examples will hopefully answer your questions.

Variations of doing nothing

Before we leave our first example and close the first chapter, we will test two variants of the header. The first attempt is a direct command with 6 bytes global memory space:

------------------------- \ len \ cnt \ty\ hd \op\ ------------------------- 0x|06:00|2A:00|00|06:00|01| ------------------------- \ 6 \ 42 \Re\ 0,6 \N \ \ \ \ \ \o \ \ \ \ \ \p \ -------------------------

We expect to get a reply with 6 bytes of low value output. Therefore you have to increase the length of the answer from 5 to 11. If you do so, you will get:

---------------------------------- \ len \ cnt \rs\ Output \ –--------------------------------- 0x|09:00|2A:00|02|00:00:00:00:00:00| –--------------------------------- \ 9 \ 42 \ok\ \ ----------------------------------

We add 16 bytes of local memory space and change the direct command to the following:

------------------------- \ len \ cnt \ty\ hd \op\ ------------------------- 0x|06:00|2A:00|00|06:40|01| ------------------------- \ 6 \ 42 \Re\16,6 \N \ \ \ \ \ \o \ \ \ \ \ \p \ -------------------------

We expect the same reply as above and indeed:

---------------------------------- \ len \ cnt \rs\ Output \ –--------------------------------- 0x|09:00|2A:00|02|00:00:00:00:00:00| –--------------------------------- \ 9 \ 42 \ok\ \ ----------------------------------

Your Homework

Before continuing with lesson 2, you should do the following homework:

  • Translate one of the small programs into your preferred programming language and integrate it in the development environment, you like.
  • Prepare some tools because it will not be a pleasure to start from scratch over and over again. I think of the following design:
    • EV3 is a class.
    • BLUETOOTH, USB, WIFI, STD, ASYNC, SYNC and opNop are public constants.
    • Connecting the EV3 is part of the initialization of an EV3 object, that says, that the selection of the protocol is done by calling the constructor of an EV3 object with specific parameters. The EV3 object needs to remember its type of protocol. socket and device are private or protected variables of the EV3 object.
    • Sending data to the EV3 is done by EV3's method send_direct_cmd. You can take the functions of the examples as a blueprint, but inside you have to distinguish between the protocols.
    • For receiving data from the EV3, we use method wait_for_reply. You have to split the code from the functions send_direct_cmd into our two new methods send_direct_cmd and wait_for_reply.
    • Add a property verbosity, which regulates the printing of the sent direct commands and the received replies.
    • Add a property sync_mode, that regulates the communication behaviour with the following values:
      • SYNC: always use type DIRECT_COMMAND_REPLY and wait for replies.
      • ASYNC: never wait for replies, set DIRECT_COMMAND_NO_REPLY, when no global memory is used, else set DIRECT_COMMAND_REPLY.
      • STD: set DIRECT_COMMAND_NO_REPLY or DIRECT_COMMAND_REPLY like ASYNC, but in case of DIRECT_COMMAND_REPLY wait for replies.
    • msg_cnt is a private variable of the EV3 object, which increments per call of send_direct_cmd. Use it for setting the message counter.
    • As in the examples, message length, message counter, message type and header are automatically added inside send_direct_cmd. So the parameter ops of method send_direct_cmd really holds the operations and nothing else.
  • Make some performance tests and compare the three communication protocols (you will see, that USB is the fastest, Bluetooth the slowest and Wifi somewhere in between, but you may bet on the absolute values and the factors between the three protocols).
    • Repeat sending opNop as DIRECT_COMMAND_REPLY and calculate the average time for one send-and-receive-cycle.
    • Separate the time for connecting from the time for sending and receiving. You will connect once but it's the performance of the send-and-receive-cycle that may limit your application.

Out of the box solution

If you prefer to cut corners, you can also use a python3 solution out of the box! Use pip and install package ev3_dc with:


python3 -m pip install --user ev3_dc
      
Then copy this program and replace the mac-address by that of your brick:

#!/usr/bin/env python3
'''the art of doing nothing'''

import ev3_dc as ev3

my_ev3 = ev3.EV3(
    protocol=ev3.BLUETOOTH,
    host='00:16:53:42:2B:99'
)
my_ev3.verbosity = 1
my_ev3.sync_mode = ev3.SYNC
ops = ev3.opNop
my_ev3.send_direct_cmd(ops)
      
Couple the two devices and run the program.

Conclusion

You started to write a class EV3, that communicates with your LEGO EV3 using direct commands. This class allows a free choice of the communication protocol and provides Bluetooth, Usb and Wifi. My language of choice is python3. I use pydoc3 to present the current state of our project. I hope, you can easily translate it to the language, you prefer. For the moment, our class EV3 has the following API:


Help on module ev3:

NAME
    ev3 - LEGO EV3 direct commands

CLASSES
    builtins.object
        EV3
    
    class EV3(builtins.object)
     |  object to communicate with a LEGO EV3 using direct commands
     |  
     |  Methods defined here:
     |  
     |  __del__(self)
     |      closes the connection to the LEGO EV3
     |  
     |  __init__(self, protocol:str, host:str)
     |      Establish a connection to a LEGO EV3 device
     |      
     |      Arguments:
     |      protocol: 'Bluetooth', 'Usb' or 'Wifi'
     |      host: mac-address of the LEGO EV3 (f.i. '00:16:53:42:2B:99')
     |  
     |  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 defined here:
     |  
     |  __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).

DATA
    BLUETOOTH = 'Bluetooth'
    USB = 'Usb'
    WIFI = 'Wifi'
    STD = 'STD'
    ASYNC = 'ASYSNC'
    SYNC = 'SYNC'
    opNop = b'\x01'
      

My class EV3 is part of module ev3_dc, the filename is ev3.py. I used the following program, to test it:


#!/usr/bin/env python3

import ev3_dc as ev3

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

ops = ev3.opNop

print("*** SYNC ***")
my_ev3.sync_mode = ev3.SYNC
my_ev3.send_direct_cmd(ops)

print("*** ASYNC (no reply) ***")
my_ev3.sync_mode = ev3.ASYNC
my_ev3.send_direct_cmd(ops)

print("*** ASYNC (reply) ***")
msg_cnt_1 = my_ev3.send_direct_cmd(ops, global_mem=1)
msg_cnt_2 = my_ev3.send_direct_cmd(ops, global_mem=1)
my_ev3.wait_for_reply(msg_cnt_1)
my_ev3.wait_for_reply(msg_cnt_2)

print("*** STD (no reply) ***")
my_ev3.sync_mode = ev3.STD
my_ev3.send_direct_cmd(ops)

print("*** STD (reply) ***")
my_ev3.send_direct_cmd(ops, global_mem=5)
      
and got this output:

*** SYNC ***
15:15:05.084275 Sent 0x|06:00|2A:00|00|00:00|01|
15:15:05.168023 Recv 0x|03:00|2A:00|02|
*** ASYNC (no reply) ***
15:15:05.168548 Sent 0x|06:00|2B:00|80|00:00|01|
*** ASYNC (reply) ***
15:15:05.168976 Sent 0x|06:00|2C:00|00|01:00|01|
15:15:05.169315 Sent 0x|06:00|2D:00|00|01:00|01|
15:15:05.212077 Recv 0x|04:00|2C:00|02|00|
15:15:05.212708 Recv 0x|04:00|2D:00|02|00|
*** STD (no reply) ***
15:15:05.213034 Sent 0x|06:00|2E:00|80|00:00|01|
*** STD (reply) ***
15:15:05.213411 Sent 0x|06:00|2F:00|00|05:00|01|
15:15:05.254032 Recv 0x|08:00|2F:00|02|00:00:00:00:00|
      
A few remarks:
  • sync_mode = SYNC sets type = DIRECT_COMMAND_REPLY and automaticly waits for the reply, ok.
  • sync_mode = ASYNC sets type = DIRECT_COMMAND_NO_REPLY and does not wait, ok.
  • sync_mode = ASYNC with global memory sets type = DIRECT_COMMAND_REPLY and does not wait. The explicit call of method wait_for_reply gets the reply, ok.
  • Please consider this variant with special respect. We sent two direct commands, both were executed and the replies were hold by the EV3 device.
  • When we later asked for replies, we first read the reply of the first command. It seems as if the EV3 works as a FIFO (first in, first out).
  • But it may also execute parallel and repeat in the sequence of finishing the execution. We will come back to this point.
  • Mode ASYNC needs some discipline. If you forget to read a reply it will come, when you wait for a different thing. We use the message counter to uncover this situation!
  • Please be carefull with protocol USB! This may be too fast, if you send, as I did, the asynchronous commands directly one after the other.
  • sync_mode = STD without global memory sets type = DIRECT_COMMAND_NO_REPLY and doesn't wait for reply, ok.
  • sync_mode = STD with global memory sets type = DIRECT_COMMAND_REPLY and per direct command waits for reply, ok.
  • The message counter increments per direct command, ok.
  • The header correctly holds the size of the global memory, ok.

If you finished your homework, you are well prepared for new adventures. I would like to see you again in the next lesson.

34 comments:

  1. Hello,

    Thanks a lot for this detailed introduction ! It really helps getting started.

    I just wanted to share a troubleshooting tip for those who might run into the same problem I did.

    I kept receiving 0x|FF:00:00:00:00| instead of the desired 0x|03:00|2A:00|02| and finally found out that it was because the option "iPhone/iPad/iPod" was checked in the Bluetooth menu !

    Looking forward to the next lessons :)

    ReplyDelete
    Replies
    1. You are welcome! Thanks for your comment, I didn't know, that EV3's option changes the content of the messages.

      Delete
  2. Hello my name is ebrahim and tried to make the porcedimento via bluetooth using windows. I created the file using Notepad and saved as teste.py, open a command prompt, I went to the folder and typed teste.py and the window asking how desire to open the file to select the appropriate program! I'll need a Python IDE?

    Another thing I've used in direct command LEGO NXT via Bluetooth (with phone) via the application "S2 Terminal for Bluetooth" and sent and received all the commands and worked perfect, but I try the same with EV3 and could not.

    Would you help me?

    ReplyDelete
    Replies
    1. Hello Ebrahim,

      I think, you first need to install python3 on your windows computer. Then you can call python programs from the command prompt. You find information on the following link:

      https://automatetheboringstuff.com/appendixb/

      My blog is based on Unix machines. I should have mentioned it. A few sentences about Windows would be good. Maybe you can help me, I don't own a Windows computer.

      You find the python downloads at:

      https://www.python.org/downloads/windows/

      When you succeeded to run python3 programs on your machine, you need to get Bluetooth running.

      EV3 and NXT, both communicate via Bluetooth, but both use different types of commands. When you write "all the commands", what does it really mean?


      I hope, this information helps,

      Christoph

      Delete
  3. Hello :)
    Yes thank you so much ! it's really help me too.
    Nico

    ReplyDelete
  4. Thank's a lot for your blog and your knowledge !!! it is beautyfull for me ! (sorry for my english i'm french)
    without your blog i won't never be able to anderstand EV3's direct command.
    :)
    Thank's a lot !!!
    Nicolas

    ReplyDelete
    Replies
    1. Hi Nicolas,
      great to have readers like you!

      Delete
  5. Many many many thanks

    ReplyDelete
  6. I have the following error:

    Traceback (most recent call last):
    File "C:\ ........._do_nothing_bluetooth.py", line 39, in
    my_ev3 = EV3('00:16:53:3d:62:24')
    File "C:\...............\Python36\EV3_do_nothing_bluetooth.py", line 10, in __init__
    socket.AF_BLUETOOTH,
    AttributeError: module 'socket' has no attribute 'AF_BLUETOOTH'

    ReplyDelete
    Replies
    1. Hi,
      i conclude from your error message, that you run this program on Windows and python 3.6. Is that correct? The functionality of socket depends on the system of your computer. Can you please tell some additional information?

      Delete
    2. Hi,
      if socket does not support bluetooth, you can install pybluez and import bluetooth instead of socket. In my case I did the following:

      1) Install pybluez

      sudo pip3 install pybluez


      2) Change the program to:

      #!/usr/bin/env python3

      import bluetooth
      import struct

      class EV3():
      def __init__(self, host: str):
      self._socket = bluetooth.BluetoothSocket(
      bluetooth.RFCOMM
      )
      self._socket.connect((host, 1))

      def __del__(self):
      if isinstance(self._socket, bluetooth.BluetoothSocket):
      self._socket.close()
      ...


      3) run the modified program with

      python3 python_do_nothing.py


      I hope, this helps!

      Yours
      Christoph

      Delete
    3. Thanks for your reply.

      You are absolutely right. I tried to RUN the code using python3.6.4 under Windows10

      Delete
    4. Hi Hazem,
      I described an alternative bluetooth socket using pybluez and I modified the text of Lesson 1. You find it under Bluetooth python. My trial was successfull and I'm curious, if it also works with Windows 10. Please give it a try and tell me if you succeeded to connect your EV3 device.

      Delete
    5. With windows 10, I have tried and succeeded to connect to my EV3 device.

      Delete
  7. Thanks
    This is great stuff!

    I got a wifi dongle, and I am switching to try the python wifi.

    I got this as a result of running the python code, which I believe is the right output:

    Sent 0x|06:00:2A:00:00:00:00:01|
    Recv 0x|03:00:2A:00:02|


    However this was possible only after I commented the line:
    ## raise ValueError('found ev3 but not ' + host)

    Prior to that, I had the following error:
    raise ValueError('found ev3 but not ' + host)
    ValueError: found ev3 but not 00:16:53:42:2B:99

    I appreciate your comment on that.

    ReplyDelete
  8. Hi Hazem,
    congratulations! You connected your EV3 device and obviously it correctly processed the direct command. Now you can start to do real things!
    I think, I understand the error, you got. 00:16:53:42:2B:99 is the mac-address of my EV3 device. Your device has a different one. You can read the mac-address from your EV3's display under Brick Info / ID. Please modify the program by resetting the mac-address, then you can uncomment the line and it should work. For the moment you ask for any EV3 device in your network, which is o.k. as long as there is only a single EV3.

    ReplyDelete
  9. Erik Karlstörm22 January 2018 at 02:05

    Fantastic tutorial, I had been trying to connect via python/USB on my own, but I was missing several parts of the puzzle. This was exactly what I needed to read, to the point and not over engineered. Thanks!

    ReplyDelete
  10. Thank you so much for your blog, Christoph!
    I am trying to send direct commands to ev3 from iOS device. I am using a simple terminal app and I was able to send "ET /target?sn=" and get a respond from ev3 ”Accept:EV340”. As I understand it means that a WiFi connection is established between the host device and the EV3 brick. But after that when I send any direct command nothing happens. No respond. I tried 0x|06:00|2A:00|00|00:00|01| and some other commands. May be I am using a wrong format? What command can send to check if it works?
    Would you please help me?
    Thanks!

    ReplyDelete
  11. Hello,
    you successfully established a connection, but then did not get an answer from your EV3-device? I suppose you code in java. Did you use a byte[] for the direct command? This is a bit tricky, because the broadcast message, the unlock message are of type String. What you want to send as your direct command is an array of 8 bytes. Please check, what type of data you did send.
    Kind regards
    Christoph

    ReplyDelete
  12. Christoph,

    I work with Swift. Could you please give me an example of direct command in utf8 array [] format? Something with playing sound or run motor.

    Thanks,

    Konstantin

    ReplyDelete
    Replies
    1. Hi Konstantin,
      I did never code in Swift, but I think, an array of UInt8 (unsigned integer 8-Bit) is the byte type in Swift. If you send this data:

      let byteArray: [UInt8] = [0x06, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x01]

      This is, what my notation 0x|06:00|2A:00|00|00:00|01| means.

      You should get the correct answer from your EV3 device, which also is an UINt8 array:

      [0x03, 0x00, 0x2A, 0x00, 0x02]
      or in my notation: 0x|03:00|2A:00|02|

      Playing sound is a bit more complicated, but you find examples in lesson 2, e.g. 0x|0E:00|2A:00|80|00:00|94|01|01|82:B8:01|82:E8:03| to play a tone of 440 Hz for one second

      Starting motors is described in lesson 3, e.g.
      0x|0D:00|2A:00|80|00:00|A4|00|0F|81:64|A6|00|0F|

      Kind regards
      Christoph

      Delete
  13. Thank you, Christoph!
    Server stars responding!! When I sent [0x06, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x01], I got * from the server. Not the correct answer, but this might be a decoding issue with server's message. I still do not quite understand how to convert sound or motor command to [UInt8] to check if it works.

    Thanks,
    Konstantin

    ReplyDelete
  14. It works! Though when I send your sound example motor starts running instead of playing sound. But anyway I can communicate with ev3 now.
    Thank you so much!
    Konstantin

    ReplyDelete
  15. Was my mistake. Now it's playing sound

    ReplyDelete
    Replies
    1. Hi Konstantin,
      congratulations!
      Christoph

      Delete
  16. This is most informative and also this post most user friendly and super navigation to all posts. OTH Gold

    ReplyDelete
  17. Thanks a lot for your so empathetic and yet so analytic blog.
    I try to connect the brick with an ev3dev.org image to a machine running macOS Catalina via WiFi, using EV3_do_nothing_wifi.py . After executing the line "UDPSock.bind(('', 3015))" there is no further output. I reckon it waits to receive data on port 3015 but does not get any. I would guess the ev3dev software sends the UDP data on a different port? How could I find out which port to bind?
    I'm using Kernel 4.14.117-ev3dev-2.3.5-ev3 on the lego brick
    Thanks so much in advance
    Carsten

    ReplyDelete
  18. Hello Carsten,

    welcome for your warm feedback.

    First attempt, set a timeout (like module ev3_dc does):

    UDPSock.settimeout(10)
    UDPSock.bind(('', 3015))
    try:
    data, addr = UDPSock.recvfrom(1024)
    except socket.timeout:
    raise ...

    I expect you will see the timeout. This indicates, v3dev does not send a broadcast message on port 3015, as you expeted too.

    What happens, when you remove the ev3dev SD-card and boot your EV3 device with its original operating system and then run your program?

    Sorry, but I don't know the details of ev3dev.

    Kind regards,
    Christoph

    ReplyDelete
  19. Hi Christoph,
    thanks a lot for your answer and hint.
    In deed, the program works fine when I remove the SD card.
    Because of my other litte projects I'd be however quite happy to use direct commands with the ev3dev os. With the timeout codelines included, as expected, I get the timeout exception. Same for a range of other port numbers, which I checked looping your code. Obviously it's not efficient to test all possible values, so I contacted the ev3dev community.

    For the time being I'll go on using direct commands with the original os. It's great to know you are out there in case I run in trouble with this.
    Thank you
    Carsten

    ReplyDelete
  20. Hi Carsten,
    please keep me informed, if and how direct commands work with ev3dev too. If this is the case and it is possible to establish a socket, then it is worth to be included into my ev3_dc module. Direct commands are more or less machine code of the EV3, therefore the chances seem to be not so bad.

    Thanks and kind regards,
    Christoph

    ReplyDelete
  21. Hello Christoph,
    it turns out that the ev3dev project does _not_ support direct commands.
    Kind regards
    Carsten

    ReplyDelete
  22. Hello Cristoph,

    Currently I am trying to connect my EV3 brick via Windows on anaconda environment. I have installed the pyusb module library in the environment. However, I keep getting error of "Operation not supported or unimplemented on this platform". I am guessing that it has something to do with the permission. Any idea about this? Any hep would be very appreciated.

    Thank you

    ReplyDelete
  23. Hello,
    I expect, you run a copy of the python program example. What happens, when you install package ev3_ dc and use this one? Can you please give me some additional information about your error situation. Which statment causes the error?
    Kind regards,
    Christoph

    ReplyDelete
    Replies
    1. Hello,

      I managed to run my code with my own environment. I believe it was permission error with my windows and I couldn't get it to work. Once I use linux, it works out just fine. And also, ev3_dc let me run my code on my own PC environment, which is what I have been looking for. Thank you so much.

      Delete