How I connected our printer to Twitter – A wannabe hackers story of 1st success

This is the story of how I learned to use Python to hack my office printer.

A few weeks ago, Missy Gibbons noticed something as she went to pick something up from the printer at the Campaign Solutions office: the LCD screen said, in all caps, “NO PRINT FOR YOU.”

As it turns out, our very able developer had been using a script he found to hack the printer display to say funny things for about six months. And he never. said. a. word.  I would have (and later did) given myself away in five seconds. (When I asked him about it, he just started giggling and told me I couldn’t prove anything :))

As a hyper-competitive nerd with almost no coding experience, I wanted to one-up him, and hatched a scheme to hack the display to show any tweet with the hashtag we’d been tagging funny quotes in the office with. Talking to a friend before I started, I mentioned that I “literally have none of the skills to make this happen.”  He responded: “This is literally the most productive way you could waste your time.”

The result: Little more than a week and a lot of late nights later, I did it!

I don’t remember the last time I was so obsessed with making something work, but I hope you’ll enjoy the process of my mania by reading what it took to make it happen. I thoroughly enjoyed the experience, and hope to learn to code for real so that I can hack the web, not printers.

How I did it

For those interested, here’s an exhaustive journal of how I went from programming layman to Twitter-printer champion in the matter of little more than a week.

To make this work,  I knew I’d need to write or steal code that accomplished few basic elements:

1. Search Twitter for a hashtag
2. Return the most current tweet with the hashtag and store it somehow
3. Pass the most recent tweet to code that would interface with the printer

Given  limited experience with a Python class I dabbled in a few years ago and  the convenient availability of an existing printer hack written in  Python, I chose to use Python. It was a good decision.

The numbers above are chronological but they’re out of order. I started with Change the HP LCD Ready Message,  an unintelligible-to-me Python implementation of the Printer Command Language (yes, that exists):

import sys
import socket

printer_ip = ’192.168.0.101′
display_msg = ‘REPLACE         FLUX CAPACITOR’

b = ”
b = b + ‘\x1b%%-12345X@PJL JOB\n’
b = b + (‘@PJL RDYMSG DISPLAY=”%s”\n’ % display_msg)
b = b + ‘@PJL EOJ\n\x1b%%-12345X’

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((printer_ip, 9100))
s.send(b)
s.close()

Next, I searched for an easy way to get Twitter search results in a useable format without using the API (wasn’t sure I could handle using OAuth). At first, I tried to find a Python XML parser but couldn’t get it to work for a few hours.  Realizing  I was totally overthinking it (clearly many people use Twitter in their apps without converting it to XML), I found How to use twitter search api and was ready to go:

def searchTweets(query):
 search = urllib.urlopen(“http://search.twitter.com/search…)
 dict = simplejson.loads(search.read())
 for result in dict["results"]: # result is a list of dictionaries
  print result["text"]

searchTweets(“#liverpoolrpp=25″)

With those two scripts, I had the basic building blocks of what I was trying to accomplish: one to find the tweets I wanted and the other to send them to the printer. Now I had to put them together.

The first thing I did after finding the Twitter script was modify it to fit my specific needs. Changing #liverpool to #csquotes, I also changed the “rpp” parameter to “1,” so that it would only return the latest results. Then, I shifted focus to figure out exactly how I could pass what the Twitter function returned to the printer script. After some trial, error, and thought, I applied two basic principles I’d learned in my half-class on Python:

1. Instead of printing the result of the searchTweets function, I returned it.

2. Instead of making the display_msg variable equal a pre-defined string (ie “REPLACE FLUX CAPACITOR,” as the script’s author had imagined), I changed it to equal the output of the searchTweets function.

With these two changes, I had connected the two scripts. As a bit of housekeeping, knowing that that the printer display could only handle 64 characters, I also found a short bit of code that shortened strings to a predefined width and placed it between them.

With a little trial and error in getting the syntax right, I ran the script and IT WORKED. I was happy. But it wasn’t over.

Getting the script to repeat on a server was WAY harder than I’d thought.

OH. MY. GOD.

So, you write code, it works, you press save, and you’re awesome. End of story, right?

It’s not. While I knew that I could run my script by typing “python app.py” into Terminal, the point of this project was to update the printer anytime someone tweeted with our hashtag. And I certainly wasn’t going to sit around running the script on my laptop running it every minute.  No, this needed to be automatic. It had to run when my laptop was off. And it had to repeat, on its own, ad infinitum until I told it not to, on a server.  I had no idea what I need to do to make that happen.

First, I researched installing the script and finding some code to repeat it on one of our servers. But, after going through the official Python tutorial with no results, a suspicion that our server was incorrectly configured, and a really unhelpful web host, I moved onto researching free cloud solutions already configured for what I was trying to do. Because my script was written in Python, and I knew that Google was too (twinsies! … right?),  I started with the Google App Engine. After extensive Googling, it seemed to have the most support for Python of any platform as a service (a term that I know now, but didn’t then).

Diving in, I immediately faced a critical question that would introduce me to the basic architecture of web applications:  the tutorial that Google provided involved setting up Django—why would I need to install a framework just to run a script I could run on my laptop? Soon, I discovered that I didn’t, and that Google provided a way for me to repeat scripts without building a webapp. This was my introduction to the Cron job.

YES! I FOUND A TECHNOLOGY THING THAT LETS ME DO WHAT I WANT TO DO.

After many minor code changes and a lot of trial and error, I finally got a working (sort of) Cron job set up on Google’s servers. The code ran, on schedule, but it didn’t work. Looking at the logs, the error I kept facing was related to the code pushing the tweet to the printer (see above). After even more Googling,  I discovered that the error I was receiving wasn’t because my code was bad, but because App Engine didn’t support “sockets.”

I didn’t (and still don’t) fully understand what most of that meant, but in the language that I did understand a few StackOverflow searches revealed the truth: Google App Engine wouldn’t allow me to use the socket method to communicate with the printer’s IP address. And that meant that Google was a no-go.

So where next?

I left the office at 8PM on a Friday night disappointed that hours on Google App Engine had yielded nothing, but determined to figure out a way forward. There’s a tipping point on challenges like this where you no longer view time invested as a sunk cost, but as a mortgage you can only pay off by reaching your goal (however ridiculously silly it may be).

My next step was to research other options like App Engine, preferably platforms that had built-in scheduling engines that could easily and intuitively run repeating tasks. This led me to Wikipedia,  which gave me a few options for Platforms as a service.

Since much of the web is built on it, I chose to pursue AWS next. That was a mistake. It was really confusing, I spent a lot of time on it, and couldn’t figure it out. I was frustrated. Onto the next one.

Cloudsourcing to Japan?

My next attempt used Heroku, another web app platform that supported Python and, crucially, had a built-in scheduling function.

This time, after a few stumbles during a somewhat confusing setup tutorial (where I learned a ton about Git, VirtualEnv, and the command prompt in general), I got the script to run, but it kept timing out. After spending a ton of time on Google and StackOverflow, I realized that I’d run into a barrier that I couldn’t get around: the IP I was using was private, and I was hitting our firewall.

Clearly not a very good hacker.

So what could I do? Absent asking our IT guy, who I didn’t think would have a sense of humor about this, to open some ports and whitelist the IP, I’d hit a wall. I couldn’t get it to work on our servers, couldn’t install it in the cloud, and installing it on my laptop would only have worked when I was in the office. The goal here was to create something that would work forever. None of my options were working.

I went home again, still obsessed, at 10PM, still without an answer. But the next morning, I had an epiphany: we have a Mac Mini, always running, in our common room that we only use for videoconferencing. It was basically a server. If I could find code that would make the script repeat, I could install it on the Mac, run it, and it would rinse and repeat as long as I wanted (later, one of my colleagues asked when he could shut it down. I said never–it’s an upgrade to the office infrastructure).

Turns out it was right in front of me

Saving the script, I emailed it to myself and opened it on the Mac Mini. I tried to run it, but quickly realized that SimpleJSON, the module I’d needed to use the Twitter script, wasn’t installed on that computer. Neither, as I discovered, was Pip, the program I’d found earlier which made it so easy to install things on my laptop. I ran into some brief issues installing them (wherein I found out about the “sudo” command), but soon had it behaving exactly like it was on my laptop. This left one last task: finding something that would repeat it without blowing up the computer.

After some searching, trial and error, I found Advanced Python Scheduler, an extension of the Sched module which created Cron-style jobs from within scripts. After rearranging some of the code so that the meat of it worked as a single function that was called by the scheduler (something which took way longer than I’d like to admit), I got it to work, set it to run every thirty seconds, pressed enter in terminal, and that’s when this happened:

@EllenFarino I did it!

The script is still running right now and, if you want to send a message to our office, you can reach us at #CSQuotes.

The final code

For those who are interested, the final, re-arranged code is below with our printer’s ip address redacted (sorry, would-be hijackers). Please be nice, I’m a noob:

import sys
import socket
import urllib
import simplejson
import sched
from apscheduler.scheduler import Scheduler
from datetime import datetime
import time

def searchTweets(query):
 search = urllib.urlopen(“http://search.twitter.com/search…)
 dict = simplejson.loads(search.read())
 for result in dict["results"]: # result is a list of dictionaries
  return result["text"]

def printer():

    tweet = searchTweets(“#csquotespp=1″)

    tweet2 = (tweet[:64] + ‘…’) if len(tweet) > 64 else tweet

    printer_ip = ‘insert ip address here’
   
    display_msg = tweet2

    b = ”
    b = b + ‘\x1b%%-12345X@PJL JOB\n’
    b = b + (‘@PJL RDYMSG DISPLAY=”%s”\n’ % display_msg)
    b = b + ‘@PJL EOJ\n\x1b%%-12345X’
   
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((printer_ip, 9100))
    s.send(b)
    s.close()
   
    print tweet2
   
sched = Scheduler()
sched.daemonic = False
sched.start()
sched.add_interval_job(printer, seconds = 30,  start_date= ’2010-10-10 09:30′)
while True:
    time.sleep(10)
sched.shutdown()

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s