Updated EV3g Mailbox Messaging in Python

Previously I posted an article on handling EV3g binary Mailbox messages under Python3. Since then I have carried on working on this class, along with adding a handler class.

Improved Mailbox Handling

One of the things I wasn’t so keen on with my implementation was the need to specify the type of Mailbox value, i.e. BOOL, NUMBER, or TEXT. Python’s variables have their own type, so the code has been adjusted to use the value’s own type to determine the binary payload format. It is still possible to coerce the type:

from ev3mailbox import EV3Mailbox

float_msg  = EV3Mailbox("Pi", 3.1415)

# Coerce to a string
string_msg = EV3Mailbox("Pie", 3.1415, str)

These changes have made the use of this side of the code much cleaner.

Mailbox I/O Handler

Whilst working on my use case for the original code, I had been working on the principle that I’d be using it in a simple synchronous send/receive pattern. This worked well, until I started using threads at both sides of the ev3dev <-> EV3g link. Once threads were in the mix, there’s a risk that the bt_socket.recv(…) call could actually receive a message that wasn’t destined for that particular call, but for another area of the program.

The solution to the above problem was to implement a receiving thread that deals with all the socket.recv(…) calls. Each message is decoded, and then each Mailbox name has its own FIFO of message objects. It’s a deliberate choice to maintain the list of objects, rather than just their values, so that they can be forced to floats if it’s known they may be very small – see my previous post about that problem.

The new class implements a handler that will deal with all the Bluetooth and thread side of things. All that’s then required to do is call send(…), get(…), or stop() on the class instance:

from ev3messages import EV3Messages

handler = EV3Message(bt_mac_address)

handler.send("Name", value)
msg = handler.get("ANOther")
value = msg.value

handler.stop()

The calls to send(…) and get(…) should (!) be thread safe, so calls to get(…) wait on receiving a message of the requested name.

Code Repo

The repo is available from: https://gitlab.com/Jander/ev3-mailbox-python and is released under the GPLv3.

EV3 Mailboxes in Python

Recently I wanted to enter the Alexa / LEGO MINDSTORMS challenge:

https://www.hackster.io/contests/alexa-lego-voice-challenge

My idea required being able to send EV3 Mailbox messages via Bluetooth between ev3dev and a stock EV3 running the EV3g language – from Python. I’m new to Python, I’m a Perl programmer at heart, so this was somewhat of a learning curve moment. I had to get to grips with Bluetooth (not that difficult as I’m used the IP networking) and Python at the same time.

I figured that one of the major selling points of Python was its extensive library of support functions, so set to looking for something providing EV3 Mailbox handling. My research wasn’t as fruitful as I’d hoped for. I could find various chunks of code but either they were flawed in their behaviour, or much more heavyweight than I wanted. So I decided to jump in feet first and write my own library.

I’d written a library for App Inventor 2 [0] [1] [2] [3] [4] that would encode & decode EV3 Mailbox payloads before, so this wasn’t too daunting a task. One aspect of Mailbox messages is that there isn’t an identifier within the payload that identifies the content: String, Float (IEE754 32 bit), Boolean. Normally this is handled in EV3g by expecting a specific type relating to the message name – i.e. a message called “status” would be defined to always be a Boolean, but “command” would always be a String – the code forces the type to remain constant. However, the type of the payload can be deduced to some extent, so I decide that I’d implement that within the Python class:

  • Payload length = 1 byte => Boolean
  • Payload length = 4 bytes
    • Last byte != NULL or NULL in the other bytes => Float
    • Otherwise => String
  • All other payloads => String

The only issue with the logic above is that really, really, small numbers may get decoded as strings, e.g. “@@@\x00” would get seen as a string, not 5.90052e-39 which is also a valid decode of it. As such I also implemented a .force_float() method which will re-decode the payload.

The git repo for this library can be found at:

https://gitlab.com/Jander/ev3-mailbox-python

To use it do something like:

from ev3mailbox import EV3Mailbox as Mailbox

message = EV3Mailbox.encode("Name", "Message value", Mailbox.Type.TEXT)
print(message)

# Data from Bluetooth
 mailbox = EV3Mailbox.decode(payload)
 print(mailbox)

Hopefully this will prove useful to others. The code has been released under the GPLv3: https://www.gnu.org/licenses/gpl-3.0.txt