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|
- 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 operatedDIRECT_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
andopNop
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. TheEV3
object needs to remember its type of protocol.socket
anddevice
are private or protected variables of theEV3
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 functionssend_direct_cmd
into our two new methodssend_direct_cmd
andwait_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 typeDIRECT_COMMAND_REPLY
and wait for replies.ASYNC
: never wait for replies, setDIRECT_COMMAND_NO_REPLY
, when no global memory is used, else setDIRECT_COMMAND_REPLY
.STD
: setDIRECT_COMMAND_NO_REPLY
orDIRECT_COMMAND_REPLY
likeASYNC
, but in case ofDIRECT_COMMAND_REPLY
wait for replies.
msg_cnt
is a private variable of theEV3
object, which increments per call ofsend_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 parameterops
of methodsend_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
asDIRECT_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.
- Repeat sending
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
setstype = DIRECT_COMMAND_REPLY
and automaticly waits for the reply, ok.sync_mode = ASYNC
setstype = DIRECT_COMMAND_NO_REPLY
and does not wait, ok.sync_mode = ASYNC
with global memory setstype = DIRECT_COMMAND_REPLY
and does not wait. The explicit call of methodwait_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 setstype = DIRECT_COMMAND_NO_REPLY
and doesn't wait for reply, ok.sync_mode = STD
with global memory setstype = 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.
Hello,
ReplyDeleteThanks 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 :)
You are welcome! Thanks for your comment, I didn't know, that EV3's option changes the content of the messages.
DeleteHello 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?
ReplyDeleteAnother 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?
Hello Ebrahim,
DeleteI 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
Hello :)
ReplyDeleteYes thank you so much ! it's really help me too.
Nico
Thank's a lot for your blog and your knowledge !!! it is beautyfull for me ! (sorry for my english i'm french)
ReplyDeletewithout your blog i won't never be able to anderstand EV3's direct command.
:)
Thank's a lot !!!
Nicolas
Hi Nicolas,
Deletegreat to have readers like you!
Many many many thanks
ReplyDeleteI have the following error:
ReplyDeleteTraceback (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'
Hi,
Deletei 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?
Hi,
Deleteif 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
Thanks for your reply.
DeleteYou are absolutely right. I tried to RUN the code using python3.6.4 under Windows10
Hi Hazem,
DeleteI 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.
With windows 10, I have tried and succeeded to connect to my EV3 device.
DeleteThanks
ReplyDeleteThis 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.
Hi Hazem,
ReplyDeletecongratulations! 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.
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!
ReplyDeleteThank you so much for your blog, Christoph!
ReplyDeleteI 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!
Hello,
ReplyDeleteyou 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
Christoph,
ReplyDeleteI 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
Hi Konstantin,
DeleteI 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
Thank you, Christoph!
ReplyDeleteServer 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
It works! Though when I send your sound example motor starts running instead of playing sound. But anyway I can communicate with ev3 now.
ReplyDeleteThank you so much!
Konstantin
Was my mistake. Now it's playing sound
ReplyDeleteHi Konstantin,
Deletecongratulations!
Christoph
This is most informative and also this post most user friendly and super navigation to all posts. OTH Gold
ReplyDeleteThanks a lot for your so empathetic and yet so analytic blog.
ReplyDeleteI 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
Hello Carsten,
ReplyDeletewelcome 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
Hi Christoph,
ReplyDeletethanks 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
Hi Carsten,
ReplyDeleteplease 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
Hello Christoph,
ReplyDeleteit turns out that the ev3dev project does _not_ support direct commands.
Kind regards
Carsten
Hello Cristoph,
ReplyDeleteCurrently 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
Hello,
ReplyDeleteI 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
Hello,
DeleteI 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.