This is part two of a three part Series to explain how I control my BotTwo semi-Autonomous Robot from a webpage on the Internet.
In Part One, we talked about the Interaction and Communications between the end user device (laptop/tablet/phone), the Web Server presenting the control panel, and the socket service listening for commands on the Robot itself.
This series will allow you to operate your Robot like an RC Car via the Internet.
This is the mode described here, with code examples for each programming platform. As part of my learning process, I chose a very simple DIY approach, as opposed to a framework like node.js
For the sake of completion, here is how the Raspberry Pi and Arduino are connected via I2C
In this posting, we will discuss how to use python on the Raspberry Pi to initialise a tcp socket listener for incoming commands from the Web Server.
We will take the incoming message, and repeat it via I2C to the Arduino that is managing the DC motors and wheel encoders.
Note: typically, there would be a validation step between receiving the command, and issuing it to the motor controllers.
You would want to ensure that the command made sense, but also ensure that it was not going to put the robot into danger. This would include proximity / obstacle detection, as well as "cliff detection". In more advanced robots, you may also have environmental sensors that could ensure that the path chosen was safe to travel.
Ok... so... in my implementation, I rely heavily on the Adafruit Raspberry Pi python library for I2C communications both for existing I2C sensors, as well as for communicating with my Arduino's. I fully admit to replicating an existing I2C sensor library, and then making it work with my motor controller Arduino.
Adafruit_I2C.py provides a number of methods for sending and receiving data via I2C, in 8bit bytes, 16bit integers, or character array (python list) form.
I'm not going to explain how to prepare your Pi for I2C...
please go here -> Adafruit: Configuring the Pi for I2C
I had looked at Quick2Wire, and WiringPi, but their implementations is based on python3, whereas it appears that more I2C connectivity libraries exist for python 2.x via Adafruit.
Here is my stripped down "library" for sending a command string to the Arduino Motor Controller via I2C.
Motion.py
#!/usr/bin/python
# Motion.py Python library for Arduino as I2C Motor Controller.
# Copyright 2014 Michael Ball unix_guru at hotmail dot com
from Adafruit_I2C import Adafruit_I2C
class Motion(Adafruit_I2C):# Minimal constants carried over from Arduino library
MOTION_ADDRESS = 0x33 # I2C Device Address
MOTION_DATA = 0x00 # Sensor data
def __init__(self, busnum=-1, debug=False):self.move = Adafruit_I2C(self.MOTION_ADDRESS, busnum, debug)
def getMotionData(self):return self.move.readU8(self.MOTION_DATA) # & 0x0F
# Read the Motion sensors
def read_sensors(self):
raw = self.move.readList(self.MOTION_DATA, 11)
res = []
for i in range(0, 10, 2):
g = raw[i] | (raw[i+1] << 8)
res.append(g)
return res
# Send Commands to Motion Controllerdef write_command(self, command):
err = self.move.writeList(self.MOTION_DATA, map(ord,command))
return err
# Simple example prints wheel encoder data and then sends a# "Forward 200mm" command every 2 seconds:
# Please note that sending a string via I2C bus is not the most
# efficient use of this bus.
# Data register method will be shown in an upcoming article
if __name__ == '__main__':from time import sleep
motion = Motion()print '[Motion lpulse/rpulse]'
while True:
print motion.getMotionData()
print motion.read_sensors()
sleep(.5)
print motion.write_command("f, 200 \n\r");
sleep(2)
With no further ado... here is my python "command repeater" aka tcp socket listener-I2C Master.
#! /usr/bin/python
#
# Process_Socket_Commands.py Michael Ball Feb 2014
# Manage command/response between Arduino Motor/Sensor controller,
# and Raspberry Pi intelligent controller.
# Using tcp socket listener to receive commands from end user,
# and I2C to communicate with Arduino
import socket # Import socket moduleimport time
import thread
from Motion import Motion
##################################################################### Global Variables
cmd_received = 0
controlCommand = ""
##################################################################### Declare functions
# Retrieve commands from tcp socket if available, and send to I2Cdef get_next_command():
while True:# Inside processing loop we wait for a connection
client_socket, address = server_socket.accept()
print 'Got connection from', address
client_socket.send('Thank you for connecting')
controlCommand = client_socket.recv(1024)
# controlCommand is a comma separated string from the Webserver# that consists of a single character Command, plus a Parameter
# Move forward 200mm would be represented as "f,200\n\r"
print controlCommand
# Normally, you would insert validation checking here,# like proximty / obstacle avoidance, or cliff checking
#send command to arduinomotion.write_command(controlCommand)
# Read Wheel Encoder info from arduinoprint motion.getMotionData()
print motion.read_sensors()
client_socket.close() # Close the connection
##########################################################################
#
#This is where all the good stuff starts...
#
print 'Arduino Bot Command Processor - Feb 2014'
# Create and open the socket will be listening onserver_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("192.168.0.105", 5000))
server_socket.listen(5)
print "Running Socket Server"
# initialize I2C Arduino motor controller -# Note there are no condition checks here
# a robust implementation would ensure that
# the motor controller was actually online
motion = Motion()
print "Arduino I2C Motor Controller Initialized"
try:running = True
# Start up a Command tcp socket listener thread
thread.start_new_thread(get_next_command, ())
while 1: # Continuous looppass
except (KeyboardInterrupt, SystemExit): #when you press ctrl+c
print "\nKilling Thread..."
db.close()
print "Done.\nExiting."
Please read the comments. This is a bare-bones implementation that takes a command string, and forwards it to The arduino via I2C for processing.
You will need to add your proximity and safety testing to this. As well, sending a string via I2C is not the most efficient use of the bus. I simply did this so that I had both options available on the Arduino.
A proper Data Register method will be shown in an upcoming article.
As you will see in the next segment of this series, I share the Command Interpreter / processor between the serial port and the I2C. Commands can be received via either.
References:
http://dsscircuits.com/index.php/articles/78-arduino-i2c-slave-guide
Adafruit: Configuring the Pi for I2C
http://blog.oscarliang.net/raspberry-pi-arduino-connected-i2c/
https://wiki.python.org/moin/TcpCommunication
https://docs.python.org/3/howto/sockets.html
https://docs.python.org/2/library/socketserver.html
http://www.lucidtronix.com/tutorials/16
http://www.instructables.com/id/How-To-Make-an-Obstacle-Avoiding-Arduino-Robot/
https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code
http://en.wikipedia.org/wiki/I%C2%B2C
http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html
http://www.penguintutor.com/linux/raspberrypi-webserver
http://www.instructables.com/id/Raspberry-Pi-I2C-Python/?ALLSTEPS
http://raspberrypi4dummies.wordpress.com/2013/07/18/raspberry-pi-master-controls-arduino-uno-slaves-via-i2c/
Also read:
http://quick2wire.com/category/python/
https://github.com/quick2wire/trackbot
http://blog.chris.tylers.info/index.php?/archives/274-New-Pidora-Package-quick2wire-python-api.html
http://think-bowl.com/raspberry-pi/installing-the-think-bowl-i2c-libraries-for-python/
https://projects.drogon.net/raspberry-pi/wiringpi/i2c-library/