Each module does a specific job, and is accessed through a few simple functions which describe the operation being performed. For example the Sensors module contains a function readDistance(sensor) which returns the distance measured by one of the TOF (time of flight) sensors. The parameter (sensor) specifies which sensor (by number) to read. The module also provides the required methods to configure and initialise the sensors, and the i2c multiplexer board they are connected to. So all the complexity of switching i2c buses, turning on sensors and reading them is wrapped up in a module with a very simple code interface.
Another advantage of a modular approach is that you can put all the code which is specific to a set of hardware (e.g. motor controllers) in a single module. The module then presents an interface with simple methods like setMotorSpeed(motor, speed). If you need to change the motor controllers used in your robot, or want to reuse the code on another robot with a similar arrangement of twin motors then you only need to replace this one module and the rest of the code can be reused without changes.
The hardware interface module is called from a module which represents the movement capabilities of the robot. In this case my robot has steerable wheels, and multiple motors wired up in two groups. So the movement control of the robot is wrapped up and controlled through a few simple functions:
The LED Matrices module handles setting up and connecting a pair of rgb 5x5 LED matrix breakouts, and displaying patterns on them. A series of patterns are stored in the module and can be called by name. There is also a frame buffer which allows series of patterns to be queued up to show on the LEDs one after the other. Methods can be added to queue up patterns by name. e.g. showBlueEyes() or eyesBlink(). Animations cycle through the frames in the buffer for each display when a nextFrame() method is called.
The main program module handles output onto the main display screen. It also contains all the event handler functions for controller input. A separate module handles detecting different game controllers and mapping their buttons, sticks, hats and triggers to the same named events. So a range of game controllers can be used on the robot and they will all call the same code when their 'triangle button' is pressed.
The main module also contains the code for reacting to touchscreen input to display and navigate through graphical menus, and the main program loop which takes actions based on the mode the robot is in. In this main loop, sensors reading calls can be made, there is the call to display the next frame on the LED matrices, and trigger calls to any event handlers for the connected game controller.
Developing all the code for menus, modes and linking controller input to the robot can be very time consuming when trying to test on the robot itself. But by having the modular structure we can create a virtual copy of our robot, or Digital Twin. By writing a mock copy of each module which interfaces to the actual hardware we can work on the code without the robot being present at all. I did a lot of the coding on a laptop using Raspbian x86 running in a VirtualBox VM. Coding anytime, anywhere. Only the laptop was needed to write, debug and test much of the code.
Just 3 modules (coloured blue above) needed mock versions to the written for the robot to be virtualised. Each of these mock modules has the same name and functions in it as the real version. But the code inside the functions captures the display object from pygame and renders a representation of itself onto the display.
|Digital Twin: A virtual representation of the robot|
(Background Mars image from Hubble Space Telescope. Credit: NASA)
The mock hardware interface module renders the shape of the robot, and shows the motor speeds and steering leg angles. The mock sensors module generates randomly varying distance readings and renders yellow triangles to indicate the sensor values. The rgb LED matrix mock module renders a representation of each LED matrix onto the screen. I also added an option to display numerical information to aid debugging and diagnosing problems. You can see the virtual robot running in this video clip I posted on Twitter:
Coding my robot using a digital twin is so much easier. Just added a data debugging mode which overlays status informational the screen (the white text). The graphical robot is my digital twin mock for my hardware. I’ll upload the working code to the real robot later. #PiWars pic.twitter.com/hWdX5cMObw— Dr Footleg (@drfootleg) February 20, 2019
The graphical representation of the digital twin of my robot and the sensor output was so useful for debugging issues that I moved it from the mock module into the main robot module. All the graphics slow down the main loop so that it does impair the responsiveness of the robot. So I made it possible to switch on and off via the controller. In normal running the display shows a static graphic of a solar panel, as you can see in this video clip.
(Post Pi Wars 2019 competition I published the full source code for my digital twin robot on github. You can play with my virtual robot using the code on the 'mock' branch. Full instructions and code here: https://github.com/Footleg/PiWars2019/tree/mock )