Fun with the AWS IoT Button

Last week, Amazon announced the limited availability of the AWS IoT Button, the general-purpose version of the Dash buttons used for single-click ordering of laundry detergent, toilet paper, coconut water—all the home essentials. According to the product page that I half-skimmed before mashing “Buy Now,” virtually anything could be automated. I bought one immediately, with no particular idea about what I might want to use it for.

While it was in transit, I started to think about what parts of my life could benefit from single-click automation. Where was I wasting time that could be scripted away? And then it hit me—a substantial portion of my day was frittered away with coming up with novel ways to annoy a co-worker, one Eric Shanks. What if, each time I felt the urge, I could simply click a button and move on to something more productive?

So I got to thinking about implementation. I wanted to send him some sort of insulting message, in a easy to accomplish but hard to ignore way. I’d love to write something that generated custom insults, but that would be a bigger project, more suitable for a later version. Tried to be mindful of time to value and minimally viable prank here. Other people have written perfectly suitable insult generators, it would just be a matter of interfacing with them. So that’s the content of the message, what about the delivery method? I wanted something that would actively alert him, with a phone notification or pop up or something. We use Slack a good bit at work, so that felt right. A direct message there would be private, ensuring no one else could get offended, and and obtrusive—it’d show up right on his lock screen. Perfect.

The button arrived, I got cranking, and a couple hours later I had this:

ShanksPicture

Read on for the step-by-step walkthrough of how to build something similar. If you make it to the end, there’s an opportunity to get in on the fun.

Getting Started

You’ll need a few things to get started:

  • An AWS Account
  • An AWS IoT Button
  • Admin access to your Slack account to add an echo channel and an incoming web hook integration
  • A negligible amount of programming ability

I’ll hit these high-level.

First, go create an AWS Account, if you don’t have access to one already. It’s free for a year, and there’s a ton of fun stuff you can play with there. I hear the cloud is a big thing these days.

Then, go buy an IoT Button, when they’re back in stock. When it arrives, run through the excellent Getting Started Guide to get your button on your wireless network, register it, create your certificates and policies, and create a sample rule.

Then, configure your Slack bits. I recommend creating a private channel to use for initial testing, and then to echo my direct messages to. Then move on to configuring the WebHook. From your Slack page, click your organization -> Customize Slack, then Configure Apps, then Custom Integrations. Click Incoming WebHooks, then Add Configuration. Select your test channel, then click Add Incoming WebHooks Integration. The next page will list your Webhook URL. Go ahead and record that in a safe place. The sample curl request listed under Example is a handy thing to record, too. You change #general to #yourtestchannel and give that a run from a command line to test your integration.

With those three things in place, we can get to the busy business of writing the code. To grab our insults and post them to Slack, we’re going to create AWS Lambda functions to execute appropriate code, and then associate those functions with IoT Button presses. Lambda supports Java 8, Node.js 0.10, Node.js 4.3, and Python 2.7. If those words don’t mean anything to you, stick with Python. There’s an excellent Intro to Python course available at edX, I’m about halfway through Week 2 and managed to get this going with only minimal assistance from my favorite programming resource:

StackOverflow

So, get yourself some Python installed (Guide Here), and add the Requests library (Guide Here). We’re going to use the requests library to scrape the web insult generator, and post the results to Slack.

Disclaimer

I just want to pause here and make abundantly clear that I am not a developer and know just barely enough about all the services and technologies discussed here to get by. There are quite likely better ways of doing all of this, and I’m eager to learn them in time. Again, minimally viable prank here.

Writing Terrible Code

Next step is to find an insult source, and write some code. In my research, I found two that I quite liked, and ultimately couldn’t decide between. So I did both, and tied each to a different button action. I’ll reference the tamer of the two throughout here, and that’s AutoInsult by JJ Berry, at http://datahamster.com/autoinsult/. This one is nice because there are a few styles to choose from:

  • Arabian: May 1,000 Catholic elephant herders make love whilst you’re trying to work
  • Mediterranean: Your mother was an embarrassing exotic dancer who attracted crowds with her act with a group of train spotters
  • Modern: You selfish plethora of ridiculous used toilet paper
  • Shakespearean: Thou fitful paper-faced younker

Neat, huh? I went with Modern. The direct URL for that generator is http://datahamster.com/autoinsult/index.php?style=3. Load that up, go to view page source, and find the insult text. You’ll notice that it’s bounded by a couple tags—“<div class=”insult” id=”insult”>” and “</div><br>.” We’re going to use requests to grab the page, and regular expressions to filter out the string between those tags.

So let’s write something that does that:

import requests #Need this for the requests library to get the source page, and post the message to Slack
import re #This does the regular expression magic to grab the insult from the page source
import json #This is for dumping our payload JSON into the Slack message – We’ll get there later

r = requests.get(‘http://datahamster.com/autoinsult/index.php?style=3′) #Grabs the page
scrape = r.text #Stores the source text
x = re.findall(r'<div class=”insult” id=”insult”>(.*?)</div><br>’,scrape) #Uses RegEx to find everything between those tags we found earlier. Stores each occurrence in a list
y = x[0] #Takes the first result in the list returned above.
y = y.replace(‘&#44;’, ‘,’) #Cleans up our insult string. Commas, spaces, and apostrophes sometimes get converted to those codes.
y = y.replace(‘&nbsp;’, ‘ ‘)
y = y.replace(‘&#039;’, ‘\”)

print(y) #Prints our resulting insult string

Give that a run, and you should get something like this:

NewImage

Cool! Now, let’s add in the Slack part.

import requests
import re
import json

r = requests.get(‘http://datahamster.com/autoinsult/index.php?style=3′)
scrape = r.text
x = re.findall(r'<div class=”insult” id=”insult”>(.*?)</div><br>’,scrape)
y = x[0]
y = y.replace(‘&#44;’, ‘,’)
y = y.replace(‘&nbsp;’, ‘ ‘)
y = y.replace(‘&#039;’, ‘\”)

url = ’SLACK-WEBHOOK-URL-WE-RECORDED-EARLIER’
payload = {“channel”: “@shanks”, “username”: “ShanksBot”, “text”: y, “icon_emoji”: “:shanks:”}
echopayload = {“channel”: “#iot-button-testing”, “username”: “ShanksBot-Echo”, “text”: y, “icon_emoji”: “:shanks:”}
s = requests.post(url, data=json.dumps(payload))
s = requests.post(url, data=json.dumps(echopayload))

So payload and echopayload there are just two different option sets for the call we’re making to Slack to post the message. ShanksBot posts directly to Shanks, and ShanksBot-Echo posts to my (private) test channel. I’ve uploaded a custom Slack emoji mapped to :shanks:, which is used as the picture for the bot.

Customize that to your liking, give it a run, and you should see something like the following:

NewImage

Now we have working Python, so we need to package it to create a lambda function. You’ll need to make a couple tweaks to the Python code to add a handler for Lambda, something like this:

import requests
import re
import json

def my_hander(event, context):
r = requests.get(‘http://datahamster.com/autoinsult/index.php?style=3′)
scrape = r.text
x = re.findall(r'<div class=”insult” id=”insult”>(.*?)</div><br>’,scrape)
y = x[0]
y = y.replace(‘&#44;’, ‘,’)
y = y.replace(‘&nbsp;’, ‘ ‘)
y = y.replace(‘&#039;’, ‘\”)

url = ’SLACK-WEBHOOK-URL-WE-RECORDED-EARLIER’
payload = {“channel”: “@shanks”, “username”: “ShanksBot”, “text”: y, “icon_emoji”: “:shanks:”}
echopayload = {“channel”: “#iot-button-testing”, “username”: “ShanksBot-Echo”, “text”: y, “icon_emoji”: “:shanks:”}
s = requests.post(url, data=json.dumps(payload))
s = requests.post(url, data=json.dumps(echopayload))
return y

Everything after “def my_hander(event, context):” should be indented. WP ate my formatting.

So we added the handler definition, and set a return value so that when the Lambda function is executed, we’ll return the insult string to the lambda console, just for funsies.

Now, package your python code for upload to AWS. Follow the process here to create a project directory, copy your source, and install your library. The example helpfully shows requests already, which is the only library we need to package.

Create Your Lambda Function

Log into your AWS Console, and click Lambda.

Click Create a Lambda Function.

On the Select blueprint screen, click Skip. We don’t need no stinkin’ blueprints.

Give your function a name. I used the same name as my python script—I don’t know if that matters. Barely competent, like I said.

Pick the Python 2.7 runtime

Select upload a .ZIP file, browse and select your package.

For Handler, I used <scriptname>.my_hander. Again, no idea if this matters.

For Role, use the Basic Execution Role, and follow the prompts to create it.

Leave everything else default and click Next.

Hit Create function

You now have the option of testing your function. Give it a shot. You’ll be prompted to input test events:

NewImage

If our function did anything useful, this might matter. It doesn’t and doesn’t. Hit Save and test.

You should see something like:

NewImage

…and your message should post to Slack.

Create Your IoT Button Rule

This assumes you’ve run through the IoT Getting Started Guide. If you haven’t, go do it.

Got to the AWS Console, and hit IoT.

On the IoT Console, click Create a Rule:

NewImage

Give your Rule a catchy name.

For Attribute, enter *.

For Topic Filter, enter your iotbutton filter. “iotbutton/<buttonserialnumber>” without quotes.

For condition, you have options. You can leave it blank, or specify a click type:

clickType = “SINGLE”
clickType = “DOUBLE”
clickType = “LONG”

For Choose an action, select “Insert this message into a code function and execute it (Lambda)”

For Resource, select your lambda function name.

Click Add action, then Create.

That should do it! Go nuts and click your button!

Getting in on the Fun

This is all well and good if you have an AWS Button, or access to the Ahead office to go and click mine, but what if you want to auto generate an insult to Shanks from outside of our organization?

Well, I setup an AWS API Gateway to allow folks to trigger the functions externally.

If you want to send Shanks an insult from the generator referenced herein, click here.

If you want to send a much, much more vulgar insult, click here.

If for some reason you want to send him a randomly-generated compliment instead, click here. Weirdo.