Modelling a biscuit factory robot with Apama, Lego Mindstorms EV3 and a BrickPi

There have been a few new starters in the Apama team over the last 12 months and the idea for “EPL Club” came up where us newbies would get together with coffee and biscuits to develop our EPL knowledge with a ‘real’ project. As part of Software AG’s Helix transformation and associated empowerment budget, we were able to purchase a Lego Mindstorms EV3 kit and BrickPi3 for the Cambridge office which we decided to use to give this this project a more physical/visual aspect than some bytes on a disk or characters on a screen. And of course, all big kids still like playing with Lego!

The project was started before the COVID-19 pandemic, and while the pandemic may have slowed progress a little it hasn’t stopped us. Using the Visual Studio Code Remote – SSH and Live Share extensions makes it easy to collaborate on the project remotely.

Lego Mindstorms EV3 Kit

The kit we have is Lego #45544. This kit includes three servo motors, two touch sensors, a gyro sensor, a colour sensor and an ultrasonic sensor. There are a number of robots you can build with this kit and Lego provide downloadable instructions for them. We decided to build the rotating robot arm to simulate an imaginary robot in a biscuit factory, linking in to Apama and Cumulocity IoT’s use in industrial IoT applications.

Dexter Industries BrickPi3

The BrickPi3 is an add-on for Raspberry Pis providing connectors for the Mindstorms sensors and motors and a clear acrylic case that is compatible with Lego technic bricks. Using the Raspian for Robots OS and Dexter Industries’ code on GitHub, you can interface with the EV3 sensors/motors from the Raspberry Pi using a number of programming languages. The possibilities are endless!

The Raspberry Pi we’re using for this is a model 3 B+.

Early steps

The first steps were to carefully assemble the robot arm, the BrickPi case and to install and set up the Raspian for Robots OS on the RaspberryPi. The physical assembly was straightforward and we used the EV3 Intelligent Brick to test out the motors and sensors without needing to write any code.

After following the instructions on Dexter Industries’ website, we had our Raspberry Pi up and running with Raspian for Robots. Once connected to the WiFi, we cloned the BrickPi3 repo onto our Pi and also downloaded the Apama Community Edition. Dexter Industries provide an interface to the BrickPi3 in Python and you can also write EPL Plugins using Python, so by combining those two we should be able to read sensors and control motors via Apama EPL. Before jumping straight into that we first wanted to be sure that we could run the BrickPi3 Python examples and that we could get a simple EPL Python plugin to work.

When trying to get the BrickPi3 examples to work we encountered an error message “No SPI Response”. After scratching our heads and a bit of internet searching, we found this post on Dexter Industries’ forum which recommended a couple of commands to update the software/firmware on the BrickPi 3. After following these steps we were able to run the examples and write our own small programs to print sensor readings out to the console and to control the motors.

The next step was to make sure we could write a Python plugin for EPL. This turned out to be a bit more tricky because Apama requires Python 3.7 but the latest Python version available via apt-get on the Raspberry Pi is 3.5. To overcome this this we had to download Python 3.7 for the Pi and build it from source. More information on this can be found here.

Considerations

During these early stages of setting up the foundations for the project, we thought of a number of important things that need careful consideration.

  1. (Lack of) thread safety of the BrickPi3 library.

    • The communication to the sensors & motors is via SPI (Serial Peripheral Interface) and having two or more threads (or contexts in EPL) attempting SPI communications at the same time is likely to corrupt the bus. To avoid any concurrency issues around this, we plan to incorporate this into the design by having only one EPL monitor import the Python plugin and then having all requests to the plugin being executed in one context.
  2. Lego hardware.

    • While Lego is quick and easy to assemble, it’s not precision industrial machinery. Gear systems have play in them and plastic parts and joints flex. Initial experiments with the motors showed that the rotation and up/down movement of the arm was quite jerky. To be mechanically sympathetic to the Lego and have as precise control as possible, I think we will need to limit the speed of the motors so that we have a good compromise. This should be quite easy to do via trial and error and is something that we could easily make configurable in the software.
  3. Self-testing and calibration.

    • When the system is powered on, the position of the arm is not known (and cannot always be known) in the software. While the system was off, the motors could have been moved by hand. The physical assembly could have changed in some way. Motors or sensors could have been disconnected.
    • A start-up calibration routine is desirable for the following reasons:
      • Identify hardware faults – if the motor doesn’t move when asked, maybe there is an obstruction. If the gyro sensor’s value is out of the expected range then maybe it has been removed or is faulty.
      • Identify motor limits – the rotation limit is reached when the touch sensor is depressed, the vertical limit could be reached either when the motor stalls or when the gyro sensor reaches a certain value.
    • Many real industrial systems have switches and sensors as part of closed-loop control systems. These can be crucial for reliable operation and fault detection.

Next steps

Now that we have got the first couple of pieces of the jigsaw together, the next task is to start designing and developing our Python plugin for the BrickPi. We discussed a few possible ways of achieving this:

  1. Create a fork of the BrickPi3 GitHub repository and using Python’s multiple inheritance, make the BrickPi class an EPL plugin by inheriting from the EPLPluginBase class.
  2. Create a class that inherits from EPLPluginBase and keep an instance of the BrickPi3 class as a member, then provide methods that wrap all of the BrickPi methods.
  3. As per option 2 but just provide one method that invokes methods on the BrickPi3 instance by name.

After weighing up all the pros and cons, we decided to go for option 3 for a few key reasons:

  • Should be a lower amount of boilerplate code. We can implement an EPL Event which contains the method name, parameters and a callback for the return value. We can then abstract the details of the method and parameters by either layering events or creating builder actions in EPL. This will become more clear in the next blog post.
  • If the BrickPi3 interface changes we should only need to change our EPL and not the Python plugin as well.
  • We can implement functionality in EPL as we need it – once written and tested we shouldn’t need to make any further modifications to the Python plugin.
  • By needing to write less Python, we’ll need to write slightly more EPL which supports the main goal of ‘EPL Club’.

Here’s a Python snippet to demonstrate this approach:

# Class that we want to invoke methods on by name
class MockBrickPi:
    def testOneParam(self, arg):
        return arg
 
    def testArgs(self, *args):
        return args
 
    def get42(self):
        return 42
 
    def getList(self):
        return [self.get42()]
 
    def add(self, x, y):
        return x + y
 
 
# Class to test invoking methods by name on a MockBrickPi instance.
class TestClass:
    def __init__(self):
        self.mbp = MockBrickPi()
        # Print available method names for this object (filtering out 'magic' objects)
        methodNames = [m for m in dir(self.mbp) if not m.endswith('__')]
        print("Available method names: " + ", ".join(methodNames))
 
    def doBPMethod(self, methodname, *args):
        isMethodNameSafe = not methodname.endswith('__')  # filter out 'magic' objects
        method = getattr(self.mbp, methodname, None)
        isCallable = callable(method)
        if isMethodNameSafe and isCallable:
            if args:
                return method(*args)
            else:
                return method()
        elif not isMethodNameSafe:
            raise Exception("Method name not permitted: " + methodname)
        else:
            raise Exception("Unknown method name: " + methodname)

The doBPMethod of TestClass allows us to call any named methods on the MockBrickPi instance with any number of arguments and also handles errors – if we try to access a method name that we shouldn’t (special attributes) or a method that doesn’t exist. Python will also throw an exception if we try to call method() with the incorrect number of arguments.

By adding some test code to create an instance of TestClass we were able to call methods by name with any number of arguments. For the full source code which can be run with Python 3+, please see here.

Keep an eye out for our next blog post where we will integrate the above snippet with our plugin and start on the EPL implementation for the Biscuit-Pi robot!

Links