The event publish and subscribe feature of the Particle Cloud is easy-to-use and very effective in most cases. But sometimes you need to transmit more data than is allowed for publishing events. Or maybe you need to do some specialized processing. A server on your home or office network implemented in node.js is a useful tool in some cases.
This sample implements:
- Server discovery. The Photon uses publish to locate the server IP address and port to connect to, so you don’t need to hardcode it in your Photon firmware.
- A HTTP POST TCP connection from the Photon to the local server, kept open for sending data in real time.
- Authentication of the HTTP connection using a nonce (number used once).
- A small web server so you can easily host HTML, Javascript, CSS, etc. for a web-based application.
- A SSE (server-sent events) server to allow data to be streamed in real time into a web browser.
By using the combination of the HTTP/TCP connection and SSE, you can stream large amounts of data from a Photon in real time, right into a web browser, where it can be handled by Javascript code. Even better, any number of web browsers on your home network can connect at the same time and view the same live data, limited by the capacity of your node.js server, not the Photon.
Also, unlike using webhooks, your local server does not need to be open for incoming connections from the Internet if your Photon is also on your local network. It can stay safely behind your home firewall and you don’t need to worry about things like firewall/router port forwarding or dynamic DNS.
These are the three basic steps to connecting:
- The Photon publishes a private devicesRequest to the Particle cloud
- The server subscribes to these requests and responds by calling the devices function on the Photon with the server IP address, server port, and a nonce (number used once)
- The Photon issues a HTTP POST to the server IP address and port, with an Authorization header containing the nonce. It then keeps the connection open for sending data.
In the examples below we also use another computer web browser to connect to the server. It loads HTML, CSS, and Javascript from that server, and also opens a SSE channel. This channel is hooked to the HTTP POST channel from the Photon, basically allowing the Photon to send data in real time directly to the browser, via the server.
The server is just a computer running node.js. It could be running Windows, Mac OS X or Linux. It could even be something like a Raspberry Pi.
There are two examples here:
- livegraph, which uses a simple potentiometer to graph values
- liveimu, which uses an accelerometer (IMU, Inertial Measurement Unit) and prints the location data to a web browser window in a scrolling table
A video of it in action:
This is what the Particle code looks like for server discovery and connection management:
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
int devicesHandler(String data); // forward declaration
void sendData(void);
const unsigned long REQUEST_WAIT_MS = 10000;
const unsigned long RETRY_WAIT_MS = 30000;
const unsigned long SEND_WAIT_MS = 20;
enum State { STATE_REQUEST, STATE_REQUEST_WAIT, STATE_CONNECT, STATE_SEND_DATA, STATE_RETRY_WAIT };
State state = STATE_REQUEST;
unsigned long stateTime = 0;
IPAddress serverAddr;
int serverPort;
char nonce[34];
TCPClient client;
void setup() {
Serial.begin(9600);
Particle.function("devices", devicesHandler);
}
void loop() {
switch(state) {
case STATE_REQUEST:
if (Particle.connected()) {
Serial.println("sending devicesRequest");
Particle.publish("devicesRequest", WiFi.localIP().toString().c_str(), 10, PRIVATE);
state = STATE_REQUEST_WAIT;
stateTime = millis();
}
break;
case STATE_REQUEST_WAIT:
if (millis() - stateTime >= REQUEST_WAIT_MS) {
state = STATE_RETRY_WAIT;
stateTime = millis();
}
break;
case STATE_CONNECT:
if (client.connect(serverAddr, serverPort)) {
client.println("POST /devices HTTP/1.0");
client.printlnf("Authorization: %s", nonce);
client.printlnf("Content-Length: 99999999");
client.println();
state = STATE_SEND_DATA;
}
else {
state = STATE_RETRY_WAIT;
stateTime = millis();
}
break;
case STATE_SEND_DATA:
// In this state, we send data until we lose the connection to the server for whatever
// reason. We'll to the server again.
if (!client.connected()) {
Serial.println("server disconnected");
client.stop();
state = STATE_RETRY_WAIT;
stateTime = millis();
break;
}
if (millis() - stateTime >= SEND_WAIT_MS) {
stateTime = millis();
sendData();
}
break;
case STATE_RETRY_WAIT:
if (millis() - stateTime >= RETRY_WAIT_MS) {
state = STATE_REQUEST;
}
break;
}
}
void sendData(void) {
// Called periodically when connected via TCP to the server to update data.
// Unlike Particle.publish you can push a very large amount of data through this connection,
// theoretically up to about 800 Kbytes/sec, but really you should probably shoot for something
// lower than that, especially with the way connection is being served in the node.js server.
// In this simple example, we just send the value of A0. It's connected to the center terminal
// of a potentiometer whose outer terminals are connected to GND and 3V3.
int value = analogRead(A0);
// Use printf and manually added a \n here. The server code splits on LF only, and using println/
// printlnf adds both a CR and LF. It's easier to parse with LF only, and it saves a byte when
// transmitting.
client.printf("%d\n", value);
}
// This is the handler for the Particle.function "devices"
// The server makes this function call after this device publishes a devicesRequest event.
// The server responds with an IP address and port of the server, and a nonce (number used once) for authentication.
int devicesHandler(String data) {
Serial.printlnf("devicesHandler data=%s", data.c_str());
int addr[4];
if (sscanf(data, "%u.%u.%u.%u,%u,%32s", &addr[0], &addr[1], &addr[2], &addr[3], &serverPort, nonce) == 6) {
serverAddr = IPAddress(addr[0], addr[1], addr[2], addr[3]);
Serial.printlnf("serverAddr=%s serverPort=%u nonce=%s", serverAddr.toString().c_str(), serverPort, nonce);
state = STATE_CONNECT;
}
return 0;
}
The project is here on github: https://github.com/rickkas7/localserver