Table of Contents generated with DocToc
- Introduction
- Overview of the API
- Anatomy of a program
- Building a basic example
- Go further, multiple behaviors
Moti is a spherical robotic smart toy, specially designed to help exceptional children, such as children with Autism Spectrum Disorders.
The code that runs on Moti is open source, which means that you are welcome to contribute to the project and help improve existing behaviors or create new ones.
In this tutorial, you'll learn how to develop your own program for Moti.
But before, let's take a brief look at the API.
The API has been developed to be...
- easy to use
- robust
- and wide enough to let you do whatever you'd like to do
This is just an overview, for further information, Doxygen generated documentation is available here.
The Motion API lets you control Moti's motion.
The Motion API runs in its own thread and is non-blocking: while moving, Moti can still perform other actions such as blinking a led or checking if it is stuck against a wall.
It works with instructions such as going forward, backward, turning and spinning.
Motion has several possible states:
GO: the device is going forward or backwardSPIN: the device is spinning left or rightTURN: the device is turning (one wheel goes faster than the other)STOP: the device is stoppingNONE: the device does not perform any action
So, if you need to wait until Moti has finished moving, you can basically wait for Motion::getState() to return NONE.
The Light API lets you control Moti's Leds.
The Light API runs in its own thread and is non-blocking.
All the Leds available are listed in the LedIndicator enum in ./lib/Moti/Moti.h.
Currently, only fading a Led is available. It takes the startColor, the endColor and the duration of the fading.
It uses a queue, which means that you can enqueue multiple fades, and they will occur one after the other, first in, first out.
// Fade heart led from red to blue in 3 seconds and a half
Light::fade(HEART, Light::RedPure, Light::BluePure, 3500);
// Then, fade heart led from blue to green in 1 second and a half
Light::fade(HEART, Light::BluePure, Light::GreenPure, 1500);To get the current state of a Led, you can use Light::getState(LedIndicator).
The Moti API lets you monitor Moti's state regarding the environment.
The Moti API runs in its own thread and is non-bloking. It starts when Moti is powered up, so you can use it as is.
It lets you know if Moti is stuck, shaken or free falling for example.
The Monitor API lets you send data over a Serial port, USB or Bluetooth.
The Monitor API runs in its own thread and is non-blocking.
The data sent are:
- The motors direction (FORWARD|BACKWARD) and speed (0-255)
- The Leds data (State, R, G, B)
- The Sensors data:
- Accelerometer (X, Y, Z)
- Gyroscope (Yaw, Pitch, Roll, in degrees)
You can also send all data at once, with a timestamp (the number of milliseconds since power-up), in order to know when an event occurred or for data analysis.
For example:
A,1337;M,1,255,0,255;L,0,0,0,0;S,15,-12,235,0,34,29
Explanation:
A,1337;-Afor "All data", 1337 is the number of seconds since power-upM,1,255,0,255;-Mfor "Motors",1,255means that the right motor goes forward at max speed,0,255means that the left motor goes backward at max speedL,0,0,0,0;-Lfor "Leds", the first0means that the led is inactive, and0,0,0is the color, black in this caseS,15,-12,235,0,34,29;-Sfor "Sensors", the rest of the data is quite straightforward.
If you want to plot data, you can use our moti-data-analysis, a simple Python tool to plot data received from Moti.
Before developing our own behavior, we are going to take a look at the main program running on Moti and see how things work together.
To begin with, open a Terminal window and go to moti's src directory:
$ cd /path/to/moti/src/motiThe directory structure is as follow:
.
├── lib
│ ├── Arbitrer
│ │ └── Arbitrer.h
│ └── Stabilization
│ └── Stabilization.h
├── Makefile
└── moti.cppSome explanations:
moti.cppis the main program file that will be compiled for the robot - the name can change but must the same as its directory.Makefileis the file used to compile the code../libis the folder where you want to put the differentbehaviorsneeded for your program. You can put them all inlibor create sub directories, it depends on you. The more files you have the better it is to organize things properly.
Each behavior has to run in its own thread with a NORMALPRIO. This ensures that Moti is able to run its own basic services.
Each behavior has a msg_t thread(void* arg) function and 3 procedures:
void start(void* arg=NULL, tprio_t priority=NORMALPRIO);void run(void);void stop(void);
Let's dive in each of them.
This procedure starts the thread that will run in the background. The code is basically the same for every behavior. When used, the _isStarted variable is set to true and the _isRunning boolean is set false. A static thread is then created.
void start(void* arg, tprio_t priority) {
if (!_isStarted) {
_isStarted = true;
_isRunning = false;
(void)chThdCreateStatic(stabilizationThreadArea,
sizeof(stabilizationThreadArea),
priority, thread, arg);
}
}This procedure tells the thread to perform its job normally: the behavior is active and running, and Moti is doing what has been written. When used, _isRunning is set to true.
void run(void) {
if (!_isStarted)
start(NULL, NORMALPRIO);
_isRunning = true;
}This procedure tells the thread to stop it's job: the behavior is just paused and will not be killed, so you'll be able to start it again later. When used, _isRunning is set to false.
void stop(void) {
_isRunning = false;
}The thread function contains all the code of the behavior. Once the thread _isStarted, it waits for _isRunning to be true. Here is a basic example that you can fill later with your behavior:
msg_t thread(void* arg) {
while (!chThdShouldTerminate()) {
if (_isRunning) {
/* Do your stuff there! */
}
waitMs(50); /* Prevents us from taking all the CPU */
}
return (msg_t)0;
}To better understand how it works, we are now going to write a small example, with the Stabilization behavior.
The complete example is available in test/BehaviorExample, for those who don't want to wait.
For the others, we are going to write the code step by step.
Children love to hold and play with Moti. They often try to put it upside down in the sphere or to make it spin. That's why we want Moti to be stabilized, and always have his wheels facing the floor.
Basically, we will face two case of rotation:
- Rotation around the Y axis - the
X accelerationvalue will change - Moti has to go forward or backward to find his stable position - Rotation around the X axis - the
Y accelerationvalue will change - Moti has to perform a 90° rotation before going forward or backward to find his stable position
Now that we know what we want to do, we are going to implement it. Don't worry, the maths behind that are pretty basic. :)
First we are going to create all the files we will need:
# go to moti's directory
$ cd /path/to/moti
# go to the src directory
$ cd src
# create a new directory for our example and go to this new directory
$ mkdir BehaviorExample && cd $_
# create the main program file
$ touch BehaviorExample.cpp
# create lib directory
$ mkdir -p lib/Stabilization
# create the stabilization files
$ touch lib/Stabilization/Stabilization.h lib/Stabilization/Stabilization.cppAnd that's it! We now have all the files we will use in the next steps.
We will start with the Stabilization behavior and use the basic code we saw earlier.
In your favorite text editor, open the file ./lib/Stabilization/Stabilization.h.
We want to:
- include the required headers
- create a
namespacecalledStabilization - create our functions prototypes and useful variables
You can copy/paste the following in ./lib/Stabilization/Stabilization.h:
#include <Arduino.h>
#include "ChibiOS_AVR.h"
#include "Configuration.h"
namespace Stabilization {
// Functions
static msg_t thread(void* arg);
void start(void* arg=NULL, tprio_t priority=NORMALPRIO);
void run(void);
void stop(void);
// Variables
static WORKING_AREA(stabilizationThreadArea, 256);
bool _isStarted = false;
bool _isRunning = false;
}Now we are going to write the functions definition in ./lib/Stabilization/Stabilization.cpp. Note that we could write both in .h but as your code gets bigger and more complex, it is better to keep things separated.
We start with the 3 basic procedures start(), run() and stop(). You can copy/past the following inside ./lib/Stabilization/Stabilization.cpp:
#include "Stabilization.h"
namespace Stabilization {
// Definition of the start() procedure
void start(void* arg, tprio_t priority) {
if (!_isStarted) {
_isStarted = true;
_isRunning = true;
(void)chThdCreateStatic(stabilizationThreadArea,
sizeof(stabilizationThreadArea),
priority, thread, arg);
}
}
// Definition of the run() procedure
void run(void) {
if (!_isStarted)
start(NULL, NORMALPRIO);
_isRunning = true;
}
// Definition of the stop() procedure
void stop(void) {
_isRunning = false;
}
msg_t thread(void* arg) {
while (!chThdShouldTerminate()) {
if (_isRunning) {
/* We will write the code here */
}
waitMs(50); /* Prevents the thread from using all the MCU */
}
return (msg_t)0;
}
}Now we are going to write the actual stabilization code.
If Moti is rocked back and forth, the input is the X-axis value of the accelerometer. We use it to provide a proportional output value to the motors. We want the output value to be half the input value. Therefor, the coefficient is 0.5 and outputValue = 0.5 * inputValue. If the input value is negative, the motors will go backward.
When the Y-axis exceeds a threshold of 80, we tell Moti to spin 90°.
We also need to prevent the motors from running all the time, so we add a little constraint: the absolute output value has to be greater than 100 for the motor to roll.
You can copy/past the following to replace msg_t thread (void* arg) {...}:
msg_t thread(void* arg) {
float inputValue = 0.f; // Define variables
float outputValue = 0.f;
float accY = 0.f; // The Y-axis accelerometer value
SpinDirection spinDirection; // Spin direction i.e. left or right
Direction direction;
uint8_t speed;
while (!chThdShouldTerminate()) {
if (_isRunning) {
inputValue = Sensors::getAccX(); // Get the value of the X-axis
outputValue = 0.5 * inputValue; // Compute the output value
accY = Sensors::getAccY(); // Get the value of the Y-axis
if (abs(outputValue) > 100.f) {
direction = outputValue < 0.f ? BACKWARD : FORWARD; // Select the direction
speed = (uint8_t)abs(outputValue);
Motion::go(direction, speed, 100); // Turn the motors on for 100ms at the desired speed
}
else if (abs(accY) > 80.f) {
spinDirection = accY > 0.f ? RIGHT : LEFT;
Motion::spinDeg(spinDirection, 130, 90); // Spin 90 degrees with an arbitrary speed of 130
}
}
waitMs(50); // Prevents us from taking all the CPU
}
return (msg_t)0;
}That's it! We now have our first behavior!
The mainThread is the entry point of every program made for Moti. It here that all the basic behaviors, services and API are loaded. It's also there that you decide to use or not some behaviors, and how you want them to interact.
You need three parts:
- first part to
initall the basic services - second part to
inityour new behaviors - third part to set the
workflowof your application
To show you an example, in the following code, we turn on and off the Stabilization behavior when Moti isShaken().
You can copy/past the following inside of ./BehaviorExample.cpp:
#include <Arduino.h>
#include <Wire.h>
#include "Motion.h"
#include "Moti.h"
#include "Light.h"
#include "Communication.h"
#include "Serial.h"
#include "./lib/Stabilization/Stabilization.h"
void mainThread() {
// Init part
Wire.begin();
delay(500);
Sensors::init();
Drive::start();
DriveSystem::start();
Moti::start();
Light::start();
// Init our new behavior
Stabilization::start();
// Set variables
bool stabilize = false;
while (TRUE) {
if (Moti::isShaken()) {
if (!stabilize)
Stabilization::run();
else
Stabilization::stop();
stabilize = !stabilize;
}
waitMs(50);
}
}
void setup() {
chBegin(mainThread);
while(1);
}
void loop() {
// nothing to do here
}Now that we built a stabilization behavior, let's go a bit further and add
another behavior: a LED will fade if Moti is falling (maybe to warn us it
is going to be hurt, good boy...). The behavior will be named FadeBehavior
during the rest of the example.
So, first, let's code up the behavior telling Moti to fade his heart, which is not really hard:
msg_t thread(void* arg) {
while (!chThdShouldTerminate()) {
if (_isRunning) {
/* Red to orange, for 1 sec and a half */
if (Light::getHeartState() == INACTIVE)
Light::fadeHeart(Color::RedPure, Color::Orange, 1500);
}
waitMs(50); /* Prevents us from taking all the CPU */
}
return (msg_t)0;
}
That's it, we're done with this basic behavior, now let's integrate it to the main thread. Remember, when Moti falls, the behavior must be ran, otherwise it must be stopped.
void chSetup() {
/* Init part */
Sensors::init();
Drive::start();
DriveSystem::start();
Moti::start();
Light::start();
Stabilization::start();
bool stabilize = false;
/* Start our new behavior */
FadeBehavior::start();
bool fade = false;
while (TRUE) {
if (Moti::isShaken()) {
if (!stabilize)
Stabilization::run();
else
Stabilization::stop();
stabilize = !stabilize;
}
if (Moti::isFalling()) {
FadeBehavior::run();
fade = true;
}
else {
if (fade) {
FadeBehavior::stop();
fade = false;
}
}
waitMs(50);
}
}
Here we are, Moti now has two behaviors living side by side, each one is activated when the environment changes, meaning that Moti reacts to its environment, that mainly what it has been built for :)
That's it, you now are able to build your own behaviors and have lots of fun with your Moti device!
Bye.