Using Python and Raspberry Pi to communicate with Lego Mindstorms EV3

Pi-EV3

The idea

We are constantly trying out new technologies and better ways to support our students. One of the recent investments is a Lego EV3 Education set. However we do not build robots, we program them and take ‘em to the NXT level. Pun intended. We also bought a dozen Raspberry Pi’s to dive into Linux. So we thought; Why not garnish our Pi with Lego Minstorms EV3. And since Pi speaks Python the idea came to life.

Prerequisites

When you plan to program Python on a Raspberry Pi, you need a Pi running your favorite flavour Linux. But that isn’t what this is about. You’ll no doubt find lots of web links that guide you trough the Raspberry Pi setup process. We are running Raspbian Wheezy.

You will also need the Lego Mindstorms EV3 Brick Bluetooth protocol description. So, not unlike every project this one starts with searching the internet. [Google Chrome | Mozilla Firefox | Internet Explorer] came up with very few hits, not surprisingly, Lego Mindstorms EV3 is quite a new product. However this one showed to be a very good starting point.

Lego Minstorms EV3 software offers the user three kinds of Bluetooth messages;

  1. Logical
  2. Text
  3. Numerical

They share the same framework however the way the payload is coded in the message differs. We had a great head start at “Sioux .NET on Track” (see link above) so transmitting and receiving text was like taking candy from a baby…

Receiving EV3 BT messages

Receiving logical

We wrote an EV3 program that transmits the logical value “true” whenever the sensor on port 1 is pressed.
Transmit Logical
Next we wrote a Python program that reads the Bluetooth interface. BTW, we are using a of the shelf Bluetooth dongle we bought at our local computer shop. Installing it on our Pi’s took no more time then the time needed to type:

   sudo apt-get update
   sudo apt-get install bluetooth bluez-utils blueman


Ok, granted, paring devices from the command line is kind a pain in the … But, when paring is initiated from within the GUI it should not be too difficult.
The Python code we started with is rather basic, and should therefore be easily understood.

#! /usr/bin/env python
import serial
import time
EV3 = serial.Serial('/dev/rfcomm0')
print "Listening for EV3 Bluetooth messages, press CTRL C to quit."
try:
   while 1:
      n = EV3.inWaiting()
      if n <> 0:
         s = EV3.read(n)
         for n in s:
            print "%02X" % ord(n),
         print
      else:
         # No data is ready to be processed
         time.sleep(0.5)
except KeyboardInterrupt:
   pass
EV3.close()


When this script is executed the hex code of every received byte is output to the terminal window, thus allowing further analysis.

pi@RPi-KK ~/python_projects $ ./receiver.py
Listening for EV3 Bluetooth messages, press CTRL C to quit.
0F 00 01 00 81 9E 07 53 74 61 74 75 73 00 01 00 01
0F 00 01 00 81 9E 07 53 74 61 74 75 73 00 01 00 01
0F 00 01 00 81 9E 07 53 74 61 74 75 73 00 01 00 01
0F 00 01 00 81 9E 07 53 74 61 74 75 73 00 01 00 01
0F 00 01 00 81 9E 07 53 74 61 74 75 73 00 01 00 01

Analysis

The first two bytes contain numerical information, little endian. They read 0x000F or 15. That is the exact amount of bytes in the remainder of the message. The next two bytes contain a message counter, however it isn’t incremented with every new message the EV3 brick sends.
The next two bytes are referred to as ID bytes. They do not change and read 0x81 and 0x9E respectively. Next is one byte depicting the number of bytes in the mailbox name, 7 in this case. That is 6 bytes for “Status” and one trailing NULL character.
The third from last are two bytes, little endian, depicting the number of bytes in the payload. When transmitting boolean values the payload consists of only one byte. The LSBit is set when transmitting “true”, when “false” is send that byte reads NULL.

Receiving text

Transmit Text
We proceeded with the same strategy for analysing text messages.

pi@RPi-KK ~/python_projects $ ./receiver.py
Listening for EV3 Bluetooth messages, press CTRL C to quit.
15 00 01 00 81 9E 07 53 74 61 74 75 73 00 07 00 49 27 6D 20 4F 6B 00
15 00 01 00 81 9E 07 53 74 61 74 75 73 00 07 00 49 27 6D 20 4F 6B 00

Analysis

The structure of the entire message is virtually the same … Except, the number of bytes in the payload now is 0x0007, exactly the amount of bytes needed for “I’m Ok” and the trailing NULL character.

Receiving numerical

Again, virtually the same setup returned:

Listening for EV3 Bluetooth messages, press CTRL C to quit.
12 00 01 00 81 9E 07 53 74 61 74 75 73 00 04 00 00 00 80 3F
12 00 01 00 81 9E 07 53 74 61 74 75 73 00 04 00 00 00 00 40
12 00 01 00 81 9E 07 53 74 61 74 75 73 00 04 00 00 00 40 40
12 00 01 00 81 9E 07 53 74 61 74 75 73 00 04 00 00 00 80 40

Analysis

The payload byte count shows that numerics are transmitted in four bytes, however the counter variable that is transmitted and incremented every cycle does not show up very transparently … That’s when we thought Lego outsmarted us. But not really, after a visit to IEEE 754 Floating point converter we discovered the data was merely send in IEEE 754 floating point format.

A smart(er) receiver

The code above might do great for analysis, the message received however is not very “readable”, nor is it robust in terms synchronization with the transmitter. Therefore we’ve written a new receiver.

#! /usr/bin/env python

import serial
import time
import struct

EV3 = serial.Serial('/dev/rfcomm0')
print "Listening on rfcomm0 for EV3 Bluetooth messages, press CTRL C to quit."
try:
    while 1:
        n = EV3.inWaiting()
        if n >= 2:
            # Get the number of bytes in this message
            s = EV3.read(2)
            # struct.unpack returns a tuple unpack using []
            [numberOfBytes] = struct.unpack("<H", s)
            print numberOfBytes,

            # Wait for the message to complete
            while EV3.inWaiting() < numberOfBytes:
                time.sleep(0.01)

            s = s + EV3.read(numberOfBytes)
            [messageCounter] = struct.unpack("<H", s[2:4])
            print messageCounter,

            ID1 = ord(s[4])
            ID2 = ord(s[5])
            print ID1, ID2,
            
            s = s[6:]
            # Get the mailboxNameLength and the mailboxName
            mailboxNameLength = ord(s[0])
            mailboxName = s[1:1+mailboxNameLength-1]
            print mailboxNameLength, mailboxName,
            
            s = s[mailboxNameLength+1: ]
            # Get the payloadLength and the payload 
            [payloadLength] = struct.unpack("<H", s[0:2])
            payload = s[2:2+payloadLength]
            print payloadLength, payload
        else:
            # No data is ready to be processed yield control to system
            time.sleep(0.01)
except KeyboardInterrupt:
    print "Bye"
    pass

EV3.close()

This software only reads two bytes from the RFCOMM device and determines from them the number of bytes to retrieve in the message. Then waits for the entire message to complete. This is by the way a great example of how to use the python struct.
It allows to treat the data that was received in plain ascii code as integers, shorts or even float data.

Tagged with: , , , , , , ,
Geplaatst in Uncategorized
11 comments on “Using Python and Raspberry Pi to communicate with Lego Mindstorms EV3
  1. Hans Wilmers schreef:

    Hi, is it ok for you if we use your code in a GPLed project?

  2. […] 2 is a C# project for bluetooth communication from windows pc to ev3 brick. Reference 3 is a python script to receive ev3 bluetooth messages on the raspberry pi. Reference 4 is a blog post giving a summary […]

  3. Riyaan Bakhda schreef:

    This article only talks about recieving from the brick but what if one wants to send variables to the brick?
    Is there a way to send variables over bluetooth?

    • gipprojects schreef:

      Yes you can. If you pack your variable in exactly the same manner the received variables are packed, the EV3 will receive the text, logical or numerical variables. EV3 ignores any BlueTooth transmission that is not correctly formatted.

      • volanskyi schreef:

        Hey,

        First thank you very much for your tutorial, it helped me a lot.
        I’m trying to create BT communication using HC-06/05 between an arduino Uno to the EV3 unit but can’t succeed for reasons beyond me, I know this is an old post and not necesserily relevent but after 4 days of failed connection I decided it’s time to ask for some help and was wandering if maybe you could point me to the right direction.

        Im using an Uno, HC-05 and HC-06, I can easily read data sent from the EV3 but was able to send a logic message to it only 2/3 times and the connection lasted only a few seconds, most of the time it just dosen’t work.

        This is the transmitter code – http://codepaste.net/ioucf3

        Any help will be greatly appreciated.
        Thanks for your time and have a great week.
        Ido

      • gipprojects schreef:

        Hi Ido,

        Lets break your array apart bit by bit …


        char myArray[] = { 15 & 0xFF, 0 & 0xFF, 1 & 0xFF, 0 & 0xFF, 129 & 0xFF, 158 & 0xFF, 7 & 0xFF, 83 & 0xFF, 116 & 0xFF, 97 & 0xFF, 116 & 0xFF, 117 & 0xFF, 115 & 0xFF, 0 & 0xFF, 1 & 0xFF, 0 & 0xFF , 1 & 0xFF};

        First I’d loose the & 0xFF, that actually provokes integer promotion rules and renders you with an array of integers in stead of the char array you’d like to create. But that is no problem for the remainder of the program.

        Then lets take a look at the message …

        Length of the message (first two bytes): 15 bytes.
        Message counter (next two bytes): 1
        Device ID bytes are 129, 158.
        Next you send the data for the mailbox “Status”

        That should work great. But I remember that when I tried to send data EV3 i did increment the message counter.

  4. volanskyi schreef:

    By increment the message counter you mean to send zero instead of one?
    Looking at other posts most say the message counter dosen’t really matter..

    • gipprojects schreef:

      Indeed, the message counter doesn’t really do anything. But, in EV3 you wait for a NEW message … If the message content doesn’t change how can EV3 distinguish new from old message?

      Also … Going over your code again. You send the same array over and over again, even twice in the loop() function. I do not own a HC05 nor HC06 but i seem to remember from a student project that you are supposed to introduce a small delay after your array is send.

      I’ll try your code later this afternoon on my hardware and get back to you.

Geef een reactie op Hans Wilmers Reactie annuleren