We hooked up Pinoccio to a Pololu 3pi Robot to create an unmanned rover that can be driven via the Web. Then we drove our little Web Rover in Nevada from 10,000 km away in Brazil.
Some of us are afraid of flying. Like me. And like our buddy, Web Rover. Sure, his cousin QuadCopter is known for her aerial derring-do. But Rover, he prefers to keep his treads planted firmly on the ground. However, just like his unmanned vehicular brethren, Web Rover is happy to take orders from you remotely. In fact, he takes his orders via the Web! Check out our video demonstration:
If you have an iPhone, iPad, iPod touch, or a Mac laptop with an accelerometer, try the following:
If you were connected to the Rover right now, this information from your accelerometer would be fed to the Web, and then to the Web Rover via Pinoccio.
The Web Rover is an example of how easily Pinoccios glue the physical to the virtual.
Skill Level: Intermediate
Just solder two resistors to the 3pi, and plug in a couple of wires.
Skill Level: Beginner
Load our code sketch onto your Pinoccio Scout via the Arduino IDE. Change your WiFi access point and password, then load up the public web page in a browser.
var mq = new Mosquitto();
var url = 'ws://test.mosquitto.org/ws';
var controlTopic = 'pinoccio/3pi-control';
var telemetryTopic = 'pinoccio/3pi-telemetry';
$(function() {
var online = false;
var armed = false;
var leftMotorArray = new Array();
var rightMotorArray = new Array();
var motorArrayLength = 5;
var leftMotor = 0;
var rightMotor = 0;
var betaSensitivity = 0.5;
var gammaSensitivity = 0.3;
// mqtt connection and message-recieve handling
mq.connect(url, true);
mq.subscribe(telemetryTopic, 0);
mq.onmessage = function(topic, payload, qos) {
if (payload == 'Still alive') {
if ($('#alive-ping').html() == ' ') {
if (!online) {
online = true;
$('#rover-messages-container').html('Pinoccio 3pi online');
}
$('#alive-ping').html('•');
} else {
$('#alive-ping').html(' ');
}
} else {
$('#rover-messages-container').append(payload);
}
};
// device accelerometer handling
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', function() {
tilt(event.beta, event.gamma);
}, true);
}
var tilt = function(forwardBack, leftRight) {
if (!armed) {
return;
}
forwardBack = forwardBack * -1;
var forward = (forwardBack * betaSensitivity).toFixed(0);
var turn = (leftRight * gammaSensitivity).toFixed(0);
$('#forward-amount').html(forward);
$('#turn-amount').html(turn);
leftMotorArray.push((+forward) + (+turn));
if (leftMotorArray.length >= motorArrayLength) {
leftMotorArray.shift();
}
rightMotorArray.push((+forward) + (+turn * -1));
if (rightMotorArray.length >= motorArrayLength) {
rightMotorArray.shift();
}
}
// interface-button handling
$('.arm-button').click(function(e) {
if (!armed) {
armed = true;
$('.arm-button').html("Driving enabled!");
} else {
armed = false;
$('.arm-button').html("Enable driving");
$('#forward-amount').html('-');
$('#turn-amount').html('-');
}
});
// only send control messages twice a second (2Hz), and use smoothing via averaging
setInterval(function() {
if (!armed) {
return;
}
for (var i=0; i < leftMotorArray.length; i++) {
leftMotor += leftMotorArray[i];
}
for (var j=0; j < rightMotorArray.length; j++) {
rightMotor += rightMotorArray[j];
}
leftMotor = (+leftMotor) / leftMotorArray.length;
rightMotor = (+rightMotor) / rightMotorArray.length;
leftMotor = +(leftMotor.toFixed());
rightMotor = +(rightMotor.toFixed());
mq.publish(controlTopic, leftMotor + ':' + rightMotor, 0);
console.log('leftMotor: ' + leftMotor + ' - rightMotor: ' + rightMotor);
}, 500);
});
#include "config.h"
#include <Pinoccio.h>
// http://www.pololu.com/docs/0J21/10.a
enum {
PI_SIGNATURE = 0x81,
PI_RAW_SENSORS = 0x86,
PI_CALIBRATED_SENSORS = 0x87,
PI_TRIMPOT = 0xB0,
PI_BATTERY_MV = 0xB1,
PI_PLAY_MUSIC = 0xB3,
PI_CALIBRATE = 0xB4,
PI_RESET_CALIBRATION = 0xB5,
PI_LINE_POSITION = 0xB6,
PI_CLEAR_LCD = 0xB7,
PI_PRINT = 0xB8,
PI_LCD_GOTO_XY = 0xB9,
PI_AUTOCALIBRATE = 0xBA,
PI_START_PID = 0xBB,
PI_STOP_PID = 0xBC,
PI_M1_FORWARD = 0xC1,
PI_M1_BACKWARD = 0xC2,
PI_M2_FORWARD = 0xC5,
PI_M2_BACKWARD = 0xC6
};
int leftMotor = 0;
int rightMotor = 0;
unsigned int safetyTimer = 50000; // start in a disabled state
void setup() {
Pinoccio.init();
initialize3Pi();
RgbLed.blinkRed(200);
RgbLed.blinkGreen(200);
RgbLed.blinkBlue(200);
Serial.println("3pi Scout ready for duty");
Pinoccio.SetAddr(APP_MESH_ADDR);
Pinoccio.SetPanId(APP_MESH_PANID);
Pinoccio.SetChannel(APP_MESH_CHANNEL);
Pinoccio.SetRxState(true);
Pinoccio.listenForMessage(receiveMessage);
}
void loop() {
Pinoccio.loop();
if (safetyTimer > 30000) {
leftMotor = rightMotor = 0;
Serial1.write(PI_STOP_PID); // stop all motors
} else {
safetyTimer++;
}
}
void initialize3Pi() {
Serial1.begin(115200);
Serial1.write(PI_SIGNATURE);
RgbLed.blinkRed(2000);
Serial1.write(PI_CLEAR_LCD);
Serial1.write(PI_PRINT);
Serial1.write(8);
Serial1.print("Pinoccio");
Serial1.write(PI_LCD_GOTO_XY);
Serial1.write(0);
Serial1.write(1);
Serial1.write(PI_PRINT);
Serial1.write(8);
Serial1.print("Lets go!");
}
static bool receiveMessage(meshFrame *ind) {
safetyTimer = 0;
getMotorPayload(ind->data, ind->size);
if (leftMotor >= 0) {
Serial1.write(PI_M1_FORWARD);
} else {
Serial1.write(PI_M1_BACKWARD);
}
Serial1.write(abs(leftMotor));
if (rightMotor >= 0) {
Serial1.write(PI_M2_FORWARD);
} else {
Serial1.write(PI_M2_BACKWARD);
}
Serial1.write(abs(rightMotor));
}
void getMotorPayload(byte* payload, unsigned int length) {
int i=0, j=0;
char buf[10];
int forward = 0;
int turn = 0;
while (payload[i] != ':') {
buf[j++] = payload[i++];
}
buf[i++] = '\0';
leftMotor = atoi(buf);
j = 0;
while (i < length) {
buf[j++] = payload[i++];
}
buf[j++] = '\0';
rightMotor = atoi(buf);
}
© 2013 Bean Labs, LLC