Sending Pusher Client Events from an Arduino

A few weeks ago, I got my first arduino and set out to make my window air conditioner web-remote-controllable. Following some examples online, I created an Ethernet Server that displayed a simple web page with a power button. Clicking a button on the page simply reloaded it with “?status=1” in the URL, and the arduino pulsed the power button.

It was neat, and came together quickly, but was not a very elegant solution because it requires port forwarding on my router and a dynamic DNS listing in order to work.

In my search for something that would allow an Arduino to work anywhere without port forwarding or additional configuration, I came across Websockets and Pusher.com. Kevin Rohling has written an awesome library for connecting an Arduino to pusher.

Once I created a pusher.com account, grabbed their PHP server library and created a basic web page with an on and off button. Each button loads a separate PHP script via AJAX when clicked, sending an “on” or “off” event to pusher on the channel “onoff”.

Once the events were arriving in the Pusher debug window, I wired up an LED to the Arduino and used the Pusher Library to bind functions for the on and off events, and subscribe to the “onoff” channel.

It’s quite fulfilling to click the button on a web page, watch the event pop up on the Pusher debug window, then see the LED turn on and off… but what about sending data back to Pusher?

Pusher lets servers initiate events, but clients are meant to listen only, unless you enable “Client Events”. Here’s the catch: For a client to send events, it must be authenticated and subscribed to a private channel. To authenticate, the client must provide a SHA256 HEX digest that is created from the session id and the name of the private channel (and your pusher account’s secret key). Sounds like fun!

Two options for getting this to work are as follows:

1 – Generate the SHA256 HEX digest on the Arduino, by embedding the secret key into the program and using an encryption library.

2 – Pass the channel name and session id to a webserver that already has the secret key, and can quickly return the HEX digest.

I tried both, but option 1 kept crashing, presumably due to overloading the memory on the Arduino… it tends to act up when dealing with large strings, especially when you’re including multiple large libraries. There are a lot of moving parts in option 2, and I’ll explain each step in detail below.

This method requires two simultaneous Ethernet connections, which I read wasn’t possible with the Arduino Ethernet Shield, though the documentation says it can support up to 4.

1. First we connect to pusher using our API key.

2. Pusher returns the following JSON, which contains the socket id we need for authentication.

{“event”:”pusher:connection_established”,”data”:”{\”socket_id\”:\”9472.287403\”}”}
We parse through this to isolate the socket id, and are ready to send it to the authentication server.

3. Next we do an HTTP GET to a web site that computes the SHA256 hex digest authentication string (this was set up beforehand, and uses built-in functions in Pushers’ PHP server library. (of course, the idea is that the client would actually be logged in to access this, but for my purposes it’s wide open!)

http://www.domain.com/ac/auth/?channel_name=private-onoff&socket_id=9472.287403

4. Once the script does its magic and incorporates our secret key, it returns

{“auth”:”00dfb436277c95617ca7:96575e12cc197cdea4fe97f0c83a41fa0c88f8b1682eab3e0b4a3540f08a82e1″}

All we need is what’s between the 2nd set of quotes, so we’ll get rid of the rest with some String functions. (The second part of the result includes our API key and the 64 character hex digest)

For troubleshooting, I set up a simple form that does the same request, allowing me to manually enter the variables. Be careful here, a single mistake will result in a different authentication string!

5. Next, we subscribe to the private channel we generated the authentication string for, sending the following subscription event to Pusher:

{“event”: “pusher:subscribe”, “data”: {“channel”: “private-onoff”, “auth”: “00dfb436277c95617ca7:82dd590ed4ee9cc006abc449c8c41b0571eea338680cd369e94b760672977f07″} }

6. Pusher returns a pusher:subscription_succeeded event, which we could use for to verify the subscription (but I chose not to).

7. Next, we can load the web site that has our on and off buttons. Each button is linked to a PHP script that (8) sends the events “on” and “off” on the channel “private-onoff”.

9. Because we’re subscribed, pusher then sends the events to our Arduino!

{“event”:”off”,”data”:”\”hello world\””,”channel”:”private-onoff”}
Because we ran the bind function on “on” and “off”, the Arduino executes a function for each event as it is received.
10. Our very first client event is triggered when “on” or “off” is received, to provide verification that the command made it to the arduino.

{“event”:”client-status”,”channel”:”private-onoff”,”data”:{“status”:”on”}}

11 and 12 don’t exist yet, but the idea is to get the verification message back to the browser. Pusher could talk directly to a compatible browser, or the server could subscribe to the channel and update a web site through another method.

As I mentioned before, I had some difficulty with memory problems, especially with dealing with the long strings and concatenation necessary to communicate with pusher. A lot of the built-in string.replace() functions in Kevin Rohling’s library stopped working when the giant auth string was introduced, so I had to write couple of new functions in the PusherClient and WebsocketClient class that dealt with all the concatenation through char arrays instead of Strings.

 

#include <SPI.h>
#include <Ethernet.h>
#include <PusherClient.h>

byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
PusherClient client;
EthernetClient authClient;
int ledPin = 7;
String results;
String data;
void setup() {

pinMode(ledPin,OUTPUT);
Serial.begin(9600);

if (Ethernet.begin(mac) == 0) {
Serial.println("Init Ethernet failed");
for(;;)
;
}

if(client.connect("YOURAPIKEY")){ //Connect to Pusher using API Key
results = client.firstParse(); //firstParse returns Pusher's reply, we want that socket ID!
}
Serial.println(results);
//Replace leader and trailer text, leaving just the socket ID
String leader = "{\"event\":\"pusher:connection_established\",\"data\":\"{\\\"socket_id\\\":\\\"";
if (results.startsWith(leader)) {
String nothing = NULL;

results.replace(leader,nothing);
String trailer = "\\\"}\"}";
results.replace(trailer,nothing) ;
}

//Create 2nd connection my webserver where pusher.php awaits
//We will pass the name of a private channel and the socket ID from above, and it will return an HMAC SHA256 hex digest
char serverName[] = "www.domain.com";
if(authClient.connect(serverName, 80)){

authClient.print("GET /ac/auth/get.php?channel_name=private-onoff&socket_id="); //HTTP GET, passing channel_name and socket_id
authClient.print(results);
authClient.println(" HTTP/1.1");
authClient.println("Host: www.domain.com");

authClient.println();
delay(500);
boolean startRead = false;
//Only read the stuff between the curly brackets
for(int i=0; i<300; i++){
if (authClient.available()) {
char c = authClient.read();

if (c == '{'){
startRead = true;
}
else if(startRead){

if(c != '}'){
data += c ;
}
else{
startRead = false;
authClient.stop();

}

}

// Serial.println(data);

}
}

Serial.println();

//Strip out the beginning and end characters, leaving only APIKEY:SHA256HASH
String leader = "\"auth\":\"";
if (data.startsWith(leader)) {
String nothing = NULL;

data.replace(leader,nothing);
String trailer = "\"";
data.replace(trailer, nothing);

}

}

delay(3000);

//Bind to events
client.bind("on", on);
client.bind("off", off);
//convert our authorization String to char array
char auth[86];
for(int i=0;i<86;i++){
auth[i] = data[i];
}

//Call subscribePrivate, passing the channel name and the auth string
//You should see a subscribe event pop up on Pusher debug window
client.subscribePrivate("private-onoff",auth);

}

void loop() {
if (client.connected()) {
client.monitor();

}
}

void on(String data) { //Stuff to do when "on" is received
Serial.println("I got an ON command");
digitalWrite(ledPin, HIGH);
client.clientEvent("client-status", "private-onoff","\"status\":\"on\""); //Send a client event!

}

void off(String data) { //Stuff to do when "off" is received

digitalWrite(ledPin, LOW);
client.clientEvent("client-status", "private-onoff","\"status\":\"off\""); //Send a client event!
}

 

 

void PusherClient::subscribePrivate(String channel, char authKey[86]) {

_client.sendPrivate(channel,authKey);
}

String PusherClient::firstParse() {
String fp = _client.firstParse();
return fp;
}

void PusherClient::clientEvent(String eventName, String channel, String eventData){
_client.clientEvent(eventName, channel, eventData);

}

void WebSocketClient::sendPrivate (String channel, char authKey[86]) {

char chan[channel.length()+1];
channel.toCharArray(chan,channel.length()+1);

delay(1000);
char string1[] = "{\"event\": \"pusher:subscribe\", \"data\": {\"channel\": \"";
char string2[] = "\", \"auth\": \"";

char string3[] = "\"} }";

_client.print((char)0);
_client.print(string1);
_client.print(chan);
_client.print(string2);
_client.print(authKey);
_client.print(string3);
_client.print((char)255);

}
String WebSocketClient::firstParse() {
char character;

if (_client.available() > 0 && (character = _client.read()) == 0) {
String data = "";
bool endReached = false;
while (!endReached) {
character = _client.read();

endReached = character == -1;

if (!endReached) {
data += character;

}
}

return data;
}
}
void WebSocketClient::clientEvent(String eventName, String channel, String eventData){
char string1[] = "{\"event\": \"";

char string2[] = "\",\"channel\":\"";
char string3[] = "\",\"data\":{";
char string4[] = "}}";
_client.print((char)0);
_client.print(string1);
_client.print(eventName);
_client.print(string2);
_client.print(channel);
_client.print(string3);
_client.print(eventData);
_client.print(string4);
_client.print((char)255);

}

8 Responses to “Sending Pusher Client Events from an Arduino”

  1. josh July 3, 2012 at 11:28 am #

    Thanks so much for this post.

    I tried the first way a couple of months ago and couldn’t figure out why it just kept failing. Maybe it was the long string wonkiness. Can’t wait to give the second solution a try.

  2. Chris July 3, 2012 at 11:49 am #

    Do comments work?

  3. Phil Leggetter July 3, 2012 at 2:44 pm #

    Hi Chris,

    Really good write-up, thanks. I’ll need to chase up the customer who is finding that they can only open 1 TCP connection. I’ll get details of the hardware they’re using and update the request discussion.

    Thanks again,

    Phil @leggetter

  4. Louis Beetge July 30, 2012 at 9:53 pm #

    Hi Chris

    Do you have any example of how to parse the JSON string that’s returned from pusher so that you can use it in the callback functions?

  5. Michael Trouw September 17, 2012 at 6:06 am #

    I read this article a while back, while trying to use arduino to send private channel events. I then did not want to spend so much time to implement this solution, as i find it very devious to go this way (although it IS an ingenius solution to the problem).

    If Pusher.com would be smart and see that giving the IoT community a helping hand would increase their popularity amongst people who strongly believe in this, they would either implement this in an arduino library, or, they would implement a side-case where you can ask them to not have to authenticate while using arduino (or use a simpler authentication method, just like Twitter also has use cases where the regular authentication process is not possible, and they allow it.). So i really hope someone at pusher will read this and start thinking about making this way simpler :)

  6. Nicolas March 24, 2014 at 8:04 pm #

    Hi Chris, I have an issue with Arduino and Pusher.com connections. I don’t know if it is an on-purpose behavior from WebSockets library or Pusher.com service, but the conenction between Arduino & Pusher lasts for about 5/6 minutes, then the socket is closed and the Arduinos gets “offline” permanently, until I press the reset button and it connects for another 5 or 6 minutes.

    For proof-of-concepts this way works, but what if I want a persistent connection for hours, or a re-connection from Arduino when the connection drops?

    What I mean is, in a real life situation, where I want to turno on my AC from work on my smartphone, but I left my Arduino turned on 8 or 10 hours ago (when I left home in the morning), if it gets offline after 5 minutes, there’s no way I could get a nice cold room when I get back.

    Any Ideas on this?

    Thanks,
    Nicolas

Trackbacks/Pingbacks

  1. Arduino & Pusher-Powered Remote Control Hexbug | Charm City Networks | IT Services & Computer Repair in Baltimore Maryland - July 20, 2012

    [...] success with the Arduino Air Conditioner Remote Control, and getting Pusher authentication and client events working from an Arduino, I was in search of something a bit more fun and interactive to connect to the web. I looked at [...]

  2. Hexbug IR Remote Control using Arduino and Pusher.com - April 3, 2013

    [...] success with the Arduino Air Conditioner Remote Control, and getting Pusher authentication and client events working from an Arduino, I was in search of something a bit more fun and interactive to connect to the web. I looked at [...]

Leave a Reply

css.php