I was looking into a solution to send push notifications from an ESP8266 module to an Android application. I know that this is possible with the Google Cloud Messaging service.

But what I found on the internet is using an external webserver, able to run PHP (or Python) scripts and using a MySQL server to store registration IDs. So the information flow looks like:

The developer opens a project in the Google Developer Console, gets an API key and uses this API key in the Android application to register an Android phone or tablet (or watch or TV) on the Google GCM server.

In case you were not already aware, an API (application programming interface) key is a unique identifier used to authenticate a user, developer, or calling program to an API. They are typically used to authenticate a project with the API rather than a human user and different platforms may implement and use API keys in different ways.

Moreover, APIs facilitate interactions between different software programs and therefore it is crucial that regular software testing takes place. You can learn more about the importance of conducting an SOAtest and other common types of API software testing here: https://www.parasoft.com/products/parasoft-soatest/.

That said, in this case, the GCM server sends back a registration ID to the Android device. The Android app then needs to send this registration ID to (your own) external webserver where it is stored and used to send push messages to the Android device.

The device that wants to send a message to the registered Android devices then uses a PHP script to send data to the GCM server, which then creates and sends a push message to the Android devices.

So far so good, but why do I need to setup an external server with MySQL and PHP just to send messages to the Android devices. The ESP8266 has server capabilities build-in and has enough storage to save registration IDs in the Flash memory.

The solution would look like:



The registration IDs would be stored in the ESP8266 and the ESP8266 would contact the GCM server directly to request push messages to the registered Android devices.

As I could not find any tutorial or example on the Internet how to achieve this, I started to write the code by myself.

The result is not (yet) a library that you can just link to your ESP8266 code, it is more a collection of functions that you can copy into your code to use the ESP8266 to send push messages via GCM.

Requirements:

ESP8266

This code is written for the Arduino IDE, it is not usable for LUA or other coding solutions for the ESP8266 modules. But of course it can be used as a base to write something similar.

This code requires an additional library to handle JSON objects. I used ArduinoJSON, a nicely written and well working library to deal with JSON objects, JSON arrays, encoding and decoding. All credits for the ArduinoJson library go to Benoit Blanchon. You can get it from:

Blog on Good Code Smell – Github sources

Install the library as explained in the ArduinoJson Wiki: Using the library with Arduino

I used an Adafruit HUZZAH ESP8266 module to test this code. This breakout is a little bit more expensive than other available ESP8266 breakouts, but I like it because I can power it direct with 5V and it has a 5V compatible serial connection. But the functions will work on any ESP8266 module as long as you program it with the Arduino IDE.

For testing I connected the module to the local WiFi and my Android phone and tablet were connected to the same WiFi while doing the registration process. As my local internet provider I doesn’t allow to forward port 80 to a server on my local lan, I could not test it with an public domain name. But it should be working the same.

Android

Android Studio (or Eclipse) is required to build the test application.

At least one Android phone or tablet to test the functions.

The small application I used to test the functions is a slightly changed version of the application provided by Android Push Notification using Google Cloud Messaging (GCM) – Part 2

Limitations of this article

I will not explain

how to setup the IDEs for Arduino, ESP8266 or Android.

how to install the ESP8266 board in the Arduino IDE

how to program the ESP8266

how to add libraries to the Arduino IDE or Android studio

how to get the necessary API needed for GCM.

There are a lot of blogs/articles/tutorials and examples on the Internet about these basic requirements.

But I strongly recommend to read the articles Android Push Notification using Google Cloud Messaging (GCM) – Part 1 and Part 2. They are a very good explanation and tutorial how GCM works. Even in this article a external web server with PHP scripts is used, the explanations about GCM, how to get a API key and how to setup the Android test app is very easy to understand and helpful.

Function references

Beside of the webserver, only 5 functions are needed for the GCM functionality and handling of the registration ids:

boolean gcmSendMsg()

Description

prepares the JSON object holding the registration IDs and data and calls gcmSendOut to forward the request to the GCM server 1 prepares the JSON object holding the registration IDs and data and calls gcmSendOut to forward the request to the GCM server

Signatures

boolean gcmSendMsg(JsonArray& pushMessageIds, JsonArray& pushMessages); boolean gcmSendMsg(JsonObject& pushMessages); 1 2 boolean gcmSendMsg ( JsonArray & pushMessageIds , JsonArray & pushMessages ) ; boolean gcmSendMsg ( JsonObject & pushMessages ) ;

Arguments

pushMessageIds Json array with the key(s) for the message(s) pushMessages Json array with the message(s) or Json object with key:message fields 1 2 pushMessageIds Json array with the key ( s ) for the message ( s ) pushMessages Json array with the message ( s ) or Json object with key : message fields

Used global variables

Global string array *regAndroidIds[]* contains the ids Global int *regDevNum* contains number of devices 1 2 Global string array * regAndroidIds [ ] * contains the ids Global int * regDevNum* contains number of devices

Return value

true if the request was successful send to the GCM server false if sending the request to the GCM server failed 1 2 true if the request was successful send to the GCM server false if sending the request to the GCM server failed

Example

// Create messages and keys as JSON arrays DynamicJsonBuffer jsonBufferKeys; DynamicJsonBuffer jsonBufferMsgs; JsonArray& msgKeysJSON = jsonBufferKeys.createArray(); JsonArray& msgsJSON = jsonBufferMsgs.createArray(); msgKeysJSON.add("sensortype"); msgKeysJSON.add("value"); msgsJSON.add("humitity"); msgsJSON.add(95); gcmSendMsg(msgKeysJSON, msgsJSON); 1 2 3 4 5 6 7 8 9 10 // Create messages and keys as JSON arrays DynamicJsonBuffer jsonBufferKeys ; DynamicJsonBuffer jsonBufferMsgs ; JsonArray & msgKeysJSON = jsonBufferKeys . createArray ( ) ; JsonArray & msgsJSON = jsonBufferMsgs . createArray ( ) ; msgKeysJSON . add ( "sensortype" ) ; msgKeysJSON . add ( "value" ) ; msgsJSON . add ( "humitity" ) ; msgsJSON . add ( 95 ) ; gcmSendMsg ( msgKeysJSON , msgsJSON ) ;

boolean writeRegIds()

Description

reads Android registration ids from the array regAndroidIds[] and saves them to the file gcm.txt as JSON object 1 reads Android registration ids from the array regAndroidIds [ ] and saves them to the file gcm . txt as JSON object

Arguments

none 1 none

Used global variables

Global string array *regAndroidIds[]* contains the ids Global int *regDevNum* contains number of devices 1 2 Global string array * regAndroidIds [ ] * contains the ids Global int * regDevNum* contains number of devices

Return value

true if the registration ids were successfully saved false if a file error occured 1 2 true if the registration ids were successfully saved false if a file error occured

boolean getRegisteredDevices()

Description

reads Android registration ids from the file gcm.txt and stores them in the array regAndroidIds[] 1 reads Android registration ids from the file gcm . txt and stores them in the array regAndroidIds [ ]

Signatures

boolean getRegisteredDevice(); 1 boolean getRegisteredDevice ( ) ;

Arguments

none 1 none

Used global variables

Global string array *regAndroidIds[]* contains the ids Global int *regDevNum* contains number of devices 1 2 Global string array * regAndroidIds [ ] * contains the ids Global int * regDevNum* contains number of devices

Return value

true if the registration ids were successfully read false if a file error occured or if the content of the file was corrupted 1 2 true if the registration ids were successfully read false if a file error occured or if the content of the file was corrupted

Example

if (!getRegisteredDevice()) { Serial.println("Failed to read IDs"); } else { if (regDevNum != 0) { // Any devices already registered? for (int i=0; i<regDevNum; i++) { Serial.println("Device #"+String(i)":"); Serial.println(regAndroidIds[i]); } } } 1 2 3 4 5 6 7 8 9 10 if ( ! getRegisteredDevice ( ) ) { Serial . println ( "Failed to read IDs" ) ; } else { if ( regDevNum != 0 ) { // Any devices already registered? for ( int i = 0 ; i < regDevNum ; i ++ ) { Serial . println ( "Device #" + String ( i ) ":" ) ; Serial . println ( regAndroidIds [ i ] ) ; } } }

boolean addRegisteredDevice()

Description

adds a new Android registration id to the file gcm.txt 1 adds a new Android registration id to the file gcm . txt

Signatures

boolean addRegisteredDevice(String newDeviceID ); 1 boolean addRegisteredDevice ( String newDeviceID ) ;

Arguments

newDeviceID String containing the registration id 1 newDeviceID String containing the registration id

Used global variables

Global string array *regAndroidIds[]* contains the ids Global int *regDevNum* contains number of devices 1 2 Global string array * regAndroidIds [ ] * contains the ids Global int * regDevNum* contains number of devices

Return value

true if the registration id was successfully added false if the registration id was invalid, if the max number of ids was reached or if a file error occured 1 2 true if the registration id was successfully added false if the registration id was invalid , if the max number of ids was reached or if a file error occured

Example

String newID = "XXX91bFZIZMVJPeWjfEfaqMOWctyfAOifSl6Tz52BpCVHIsGmJnq7dr8XIAueSV2SsjkTTW_vlhDGOS8t-uuITk3jAe-d8NnYuuzhGdS3jGiXpgJYFAfz1gqndx_yz0zo3cWcLsJ0Usx"; if (!addRegisteredDevice(newID)) { Serial.println("Failed to save ID"); } else { Serial.println("Successful saved ID"); } 1 2 3 4 5 6 String newID = "XXX91bFZIZMVJPeWjfEfaqMOWctyfAOifSl6Tz52BpCVHIsGmJnq7dr8XIAueSV2SsjkTTW_vlhDGOS8t-uuITk3jAe-d8NnYuuzhGdS3jGiXpgJYFAfz1gqndx_yz0zo3cWcLsJ0Usx" ; if ( ! addRegisteredDevice ( newID ) ) { Serial . println ( "Failed to save ID" ) ; } else { Serial . println ( "Successful saved ID" ) ; }

boolean delRegisteredDevice()

Description

deletes one or all Android registration id(s) from the file gcm.txt 1 deletes one or all Android registration id ( s ) from the file gcm . txt

Signatures

boolean delRegisterDevice(); boolean delRegisterDevice(String delRegId); boolean delRegisterDevice(int delRegIndex); 1 2 3 boolean delRegisterDevice ( ) ; boolean delRegisterDevice ( String delRegId ) ; boolean delRegisterDevice ( int delRegIndex ) ;

Arguments

delRegId String with the registration id to be deleted delRegIndex Index of the registration id to be deleted 1 2 delRegId String with the registration id to be deleted delRegIndex Index of the registration id to be deleted

Used global variables

Global string array *regAndroidIds[]* contains the ids Global int *regDevNum* contains number of devices 1 2 Global string array * regAndroidIds [ ] * contains the ids Global int * regDevNum* contains number of devices

Return value

true if the registration id(s) was/were successfully deleted false if the registration id was not found, if the index was invalid or if a file error occured 1 2 true if the registration id ( s ) was / were successfully deleted false if the registration id was not found , if the index was invalid or if a file error occured

Examples

// Delete registration id by the id itself String delID = "XXX91bFZIZMVJPeWjfEfaqMOWctyfAOifSl6Tz52BpCVHIsGmJnq7dr8XIAueSV2SsjkTTW_vlhDGOS8t-uuITk3jAe-d8NnYuuzhGdS3jGiXpgJYFAfz1gqndx_yz0zo3cWcLsJ0Usx"; if (!delRegisteredDevice(delID)) { Serial.println("Failed to delete ID"); } else { Serial.println("Successful deleted ID"); } // Delete registration id at index int delIndex = 1; if (!delRegisteredDevice(delIndex)) { Serial.println("Failed to delete ID"); } else { Serial.println("Successful deleted ID"); } // Delete all saved registration ids if (!delRegisteredDevice()) { Serial.println("Failed to delete IDs"); } else { Serial.println("Successful deleted all IDs"); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // Delete registration id by the id itself String delID = "XXX91bFZIZMVJPeWjfEfaqMOWctyfAOifSl6Tz52BpCVHIsGmJnq7dr8XIAueSV2SsjkTTW_vlhDGOS8t-uuITk3jAe-d8NnYuuzhGdS3jGiXpgJYFAfz1gqndx_yz0zo3cWcLsJ0Usx" ; if ( ! delRegisteredDevice ( delID ) ) { Serial . println ( "Failed to delete ID" ) ; } else { Serial . println ( "Successful deleted ID" ) ; } // Delete registration id at index int delIndex = 1 ; if ( ! delRegisteredDevice ( delIndex ) ) { Serial . println ( "Failed to delete ID" ) ; } else { Serial . println ( "Successful deleted ID" ) ; } // Delete all saved registration ids if ( ! delRegisteredDevice ( ) ) { Serial . println ( "Failed to delete IDs" ) ; } else { Serial . println ( "Successful deleted all IDs" ) ; }

boolean gcmSendOut()

Description

sends message to https://android.googleapis.com/gcm/send to request a push notification to all registered Android devices used internal only 1 2 sends message to https : //android.googleapis.com/gcm/send to request a push notification to all registered Android devices used internal only

Signatures

boolean gcmSendOut(String data); 1 boolean gcmSendOut ( String data ) ;

Arguments

data String with the Json object containing the reg IDs and data 1 data String with the Json object containing the reg IDs and data

Used global variables

none 1 none

Return value

true if the request was successful send to the GCM server false if sending the request to the GCM server failed 1 2 true if the request was successful send to the GCM server false if sending the request to the GCM server failed

Examples

used internal only 1 used internal only

ESP8266 code

I splitted the ESP8266 code into 4 files

wifiAPinfo.h -> contains the name and login credentials for the local WiFi (not shared for privacy reasons)

gcmInfo.h -> contains the API key for the GCM (not shared for privacy reasons)

gcm_esp.ino -> functions for GCM and handling the registration IDs

gcm_esp_test.ino -> web server function, setup and main loop

wifiAPinfo.h

This file contains the name and login credentials for my local WiFi and of course I am not sharing this information here. You have to create this file and enter your own WiFi informations:

/** SSID of local WiFi network */ const char* ssid = "===YOUR_AP_SSID_HERE==="; /** Password for local WiFi network */ const char* password = "===YOUR_WIFI_PASSWORD_HERE==="; 1 2 3 4 /** SSID of local WiFi network */ const char * ssid = "===YOUR_AP_SSID_HERE===" ; /** Password for local WiFi network */ const char * password = "===YOUR_WIFI_PASSWORD_HERE===" ;

gcmInfo.h

This file contains the API key given by Google Developer Console. Obviously I am not sharing this information here. You have to create this file and enter your own API key:

/** Google API key */ const String API_key = "===YOUR_GOOGLE_API_KEY_HERE==="; 1 2 /** Google API key */ const String API_key = "===YOUR_GOOGLE_API_KEY_HERE===" ;

gcm_esp.ino

This file contains the necessary includes, global variables and functions for the GCM functions. THIS IS THE PART YOU WANT TO INCLUDE INTO YOUR OWN CODE TO USE THE GCM FUNCTIONALITY!

Includes:

#include <ESP8266WiFi.h> // Needed for the client connection to the GCM server #include <ArduinoJson.h> // Needed to handle the JSON objects (see page 2 how to install it) #include <FS.h> // Needed to save/read/delete the Android registration IDs in a file #include "gcmInfo.h" // Holds the API key</span> 1 2 3 4 #include <ESP8266WiFi.h> // Needed for the client connection to the GCM server #include <ArduinoJson.h> // Needed to handle the JSON objects (see page 2 how to install it) #include <FS.h> // Needed to save/read/delete the Android registration IDs in a file #include "gcmInfo.h" // Holds the API key</span>

Defines:

#define DEBUG_OUT // Enable to send debug information over serial connection #define MAX_DEVICE_NUM 5 // Maximum number of Android devices that can register to the push messages 1 2 #define DEBUG_OUT // Enable to send debug information over serial connection #define MAX_DEVICE_NUM 5 // Maximum number of Android devices that can register to the push messages

For the testing I limited the maximum number of Android devices that can be served to 5. Of course this value can be changed to your personal needs.

Global variables:

/** Google Cloud Messaging server address */ char serverName[] = "http://android.googleapis.com"; /** Google Cloud Messaging host */ String hostName = "android.googleapis.com"; /** WiFiClient class to communicate with GCM server */ WiFiClient gcmClient; /** Reason of failure of received command */ String failReason = "unknown"; /** Array to hold the registered Android device IDs (max 5 devices, extend if needed)*/ String regAndroidIds[MAX_DEVICE_NUM]; /** Number of registered devices */ byte regDevNum = 0; 1 2 3 4 5 6 7 8 9 10 11 12 13 /** Google Cloud Messaging server address */ char serverName [ ] = "http://android.googleapis.com" ; /** Google Cloud Messaging host */ String hostName = "android.googleapis.com" ; /** WiFiClient class to communicate with GCM server */ WiFiClient gcmClient ; /** Reason of failure of received command */ String failReason = "unknown" ; /** Array to hold the registered Android device IDs (max 5 devices, extend if needed)*/ String regAndroidIds [ MAX_DEVICE_NUM ] ; /** Number of registered devices */ byte regDevNum = 0 ;

serverName[] and hostName only differ in the additional “http://”. But saving it as a char array and as a string makes it easier to handle in the function gcmSendMsg().

The array regAndroidIds holds the Android registration IDs. Here the IDs are stored as Strings for easy use in the function gcmSendMsg(). As the registration IDs have a fixed length of 140 characters this could be as well defined as an array of character arrays.

The byte regDevNum holds the number of currently registered devices.

The functions:

boolean gcmSendMsg()

This function can be found two times, but with different arguments.

gcmSendMsg (JsonArray& pushMessageIds, JsonArray& pushMessages) Message keys and message values are received as 2 Json arrays.

gcmSendMsg (JsonObject& pushMessages) Message keys and messages are already combined in an Json object

gcmSendMsg can be used with separate arguments for the keys and the messages itself. First one is a Json array with the keys (aka names of the messages), second one is a Json array with the messages itself. I chose to use Json arrays here to make it possible to have mixed type messages. e.g. some messages can be string, others be integer or float. Possible message formats:

boolean, float, double, signed char, signed long, signed int, signed short, unsigned char, unsigned long, unsigned int, unsigned short, const char *, const String & 1 boolean , float , double , signed char , signed long , signed int , signed short , unsigned char , unsigned long , unsigned int , unsigned short , const char * , const String &

The following example would send a push notification with three messages the “power” (as a float), “light” (as an integer) and “status” (as a string) to the registered Android devices:

// Create messages and keys as JSON arrays DynamicJsonBuffer jsonBufferKeys; DynamicJsonBuffer jsonBufferMsgs; JsonArray& msgKeysJSON = jsonBufferKeys.createArray(); JsonArray& msgsJSON = jsonBufferMsgs.createArray(); msgKeysJSON.add("power"); msgKeysJSON.add("light"); msgKeysJSON.add("status"); msgsJSON.add(720.45); msgsJSON.add(7391); msgsJSON.add("Solar panel active"); gcmSendMsg(msgKeysJSON, msgsJSON); 1 2 3 4 5 6 7 8 9 10 11 12 13 // Create messages and keys as JSON arrays DynamicJsonBuffer jsonBufferKeys ; DynamicJsonBuffer jsonBufferMsgs ; JsonArray & msgKeysJSON = jsonBufferKeys . createArray ( ) ; JsonArray & msgsJSON = jsonBufferMsgs . createArray ( ) ; msgKeysJSON . add ( "power" ) ; msgKeysJSON . add ( "light" ) ; msgKeysJSON . add ( "status" ) ; msgsJSON . add ( 720.45 ) ; msgsJSON . add ( 7391 ) ; msgsJSON . add ( "Solar panel active" ) ; gcmSendMsg ( msgKeysJSON , msgsJSON ) ;

gcmSendMsg will send the push notification to all registered devices!

gcmSendMsg can be used as well with only one argument which must be a correct formatted Json object which is directly used to build up the request to the GCM server.

The first signature of the function:

First step in the function is to update the list with the registered devices and do some basic checks.

boolean gcmSendMsg(JsonArray& pushMessageIds, JsonArray& pushMessages) { // Update list of registered devices getRegisteredDevices(); if (regDevNum == 0) { // Any devices already registered? failReason = "No registered devices"; return false; } if (pushMessageIds.size() != pushMessages.size()) { failReason = "Different number of keys and messages"; return false; } int numData = pushMessageIds.size(); String data; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 boolean gcmSendMsg ( JsonArray & pushMessageIds , JsonArray & pushMessages ) { // Update list of registered devices getRegisteredDevices ( ) ; if ( regDevNum == 0 ) { // Any devices already registered? failReason = "No registered devices" ; return false ; } if ( pushMessageIds . size ( ) != pushMessages . size ( ) ) { failReason = "Different number of keys and messages" ; return false ; } int numData = pushMessageIds . size ( ) ; String data ;

Next we build up the Json object that needs to be sent to the GCM server. Inside the Json object is one Json array which holds the IDs we want the message to be sent to, the message itself is hold within another Json object that can contain The format is:

{"registration_ids":["--ID1--","--ID2--",...],"data":{"--KEY1--":"--VALUE1--","--KEY2--":"--VALUE2--",...}} 1 { "registration_ids" : [ "--ID1--" , "--ID2--" , . . . ] , "data" : { "--KEY1--" : "--VALUE1--" , "--KEY2--" : "--VALUE2--" , . . . } }

The two key “registration_ids” and “data” are mandatory, the ids must be valid 140 character strings, the key-value pairs must be string:value, where value can be any type as listed above. It is task of the Android app that receives the push notification to parse this key-value pairs.

// Create JSON object with registration_ids and data if (numData != 0) { DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonDataBuffer; // Prepare JSON object JsonObject& msgJSON = jsonBuffer.createObject(); // Add registration ids to JsonArray regIdArray in the JsonObject msgJSON JsonArray& regIdArray = msgJSON.createNestedArray("registration_ids"); for (int i=0; i<regDevNum; i++) { regIdArray.add(regAndroidIds[i]); } // Add message keys and messages to JsonObject dataArray JsonObject& dataArray = jsonDataBuffer.createObject(); for (int i=0; i<numData; i++) { String keyStr = pushMessageIds.get(i); dataArray[keyStr] = pushMessages.get(i); } // Add JsonObject dataArray to JsonObject msgJSON msgJSON["data"] = dataArray; msgJSON.printTo(data); } else { // No data to send failReason = "No data to send"; return false; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // Create JSON object with registration_ids and data if ( numData != 0 ) { DynamicJsonBuffer jsonBuffer ; DynamicJsonBuffer jsonDataBuffer ; // Prepare JSON object JsonObject & msgJSON = jsonBuffer . createObject ( ) ; // Add registration ids to JsonArray regIdArray in the JsonObject msgJSON JsonArray & regIdArray = msgJSON . createNestedArray ( "registration_ids" ) ; for ( int i = 0 ; i < regDevNum ; i ++ ) { regIdArray . add ( regAndroidIds [ i ] ) ; } // Add message keys and messages to JsonObject dataArray JsonObject & dataArray = jsonDataBuffer . createObject ( ) ; for ( int i = 0 ; i < numData ; i ++ ) { String keyStr = pushMessageIds . get ( i ) ; dataArray [ keyStr ] = pushMessages . get ( i ) ; } // Add JsonObject dataArray to JsonObject msgJSON msgJSON [ "data" ] = dataArray ; msgJSON . printTo ( data ) ; } else { // No data to send failReason = "No data to send" ; return false ; }

After the Json object with the registration IDs and the data is built successfully gcmSendOut is called to connect to the GCM server and post the push notification request:

return gcmSendOut(data); } 1 2 return gcmSendOut ( data ) ; }

The second signature of the function:

First step in the function is to update the list with the registered devices and do some basic checks.

boolean gcmSendMsg(JsonObject& pushMessages) { // Update list of registered devices getRegisteredDevices(); if (regDevNum == 0) { // Any devices already registered? failReason = "No registered devices"; return false; } String data; 1 2 3 4 5 6 7 8 9 10 boolean gcmSendMsg ( JsonObject & pushMessages ) { // Update list of registered devices getRegisteredDevices ( ) ; if ( regDevNum == 0 ) { // Any devices already registered? failReason = "No registered devices" ; return false ; } String data ;

Next we build up the Json object that needs to be sent to the GCM server. Inside the Json object is one Json array which holds the IDs we want the message to be sent to, the message itself is hold within another Json object that can contain The format is:

{"registration_ids":["--ID1--","--ID2--",...],"data":{"--KEY1--":"--VALUE1--","--KEY2--":"--VALUE2--",...}} 1 { "registration_ids" : [ "--ID1--" , "--ID2--" , . . . ] , "data" : { "--KEY1--" : "--VALUE1--" , "--KEY2--" : "--VALUE2--" , . . . } }

The two key “registration_ids” and “data” are mandatory, the ids must be valid 140 character strings, the key-value pairs must be string:value, where value can be any type as listed above. It is task of the Android app that receives the push notification to parse this key-value pairs.

// Create JSON object with registration_ids and data DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonDataBuffer; // Prepare JSON object JsonObject& msgJSON = jsonBuffer.createObject(); // Add registration ids to JsonArray regIdArray in the JsonObject msgJSON JsonArray& regIdArray = msgJSON.createNestedArray("registration_ids"); for (int i=0; i<regDevNum; i++) { regIdArray.add(regAndroidIds[i]); } // Add JsonObject pushMessages to JsonObject msgJSON msgJSON["data"] = pushMessages; msgJSON.printTo(data); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Create JSON object with registration_ids and data DynamicJsonBuffer jsonBuffer ; DynamicJsonBuffer jsonDataBuffer ; // Prepare JSON object JsonObject & msgJSON = jsonBuffer . createObject ( ) ; // Add registration ids to JsonArray regIdArray in the JsonObject msgJSON JsonArray & regIdArray = msgJSON . createNestedArray ( "registration_ids" ) ; for ( int i = 0 ; i < regDevNum ; i ++ ) { regIdArray . add ( regAndroidIds [ i ] ) ; } // Add JsonObject pushMessages to JsonObject msgJSON msgJSON [ "data" ] = pushMessages ; msgJSON . printTo ( data ) ;

After the Json object with the registration IDs and the data is built successfully gcmSendOut is called to connect to the GCM server and post the push notification request:

return gcmSendOut(data); } 1 2 return gcmSendOut ( data ) ; }

boolean writeRegIds()

This function is writing the content of the array with the registration IDs to the file gcm.txt in the file system of the ESP8266. The registration IDs are written as a Json formatted string to the text file.

First we try to open the file for writing

boolean writeRegIds() { // Open config file for writing. File devicesFile = SPIFFS.open("/gcm.txt", "w"); if (!devicesFile) { failReason = "Failed to open gcm.txt for writing"; return false; } 1 2 3 4 5 6 7 boolean writeRegIds ( ) { // Open config file for writing. File devicesFile = SPIFFS . open ( "/gcm.txt" , "w" ) ; if ( ! devicesFile ) { failReason = "Failed to open gcm.txt for writing" ; return false ; }

Then we prepare the Json object for the registration IDs and add the content of the regAndroidIds array to the Json object

// Create new device ids as JSON DynamicJsonBuffer jsonBuffer; // Prepare JSON object JsonObject& regIdJSON = jsonBuffer.createObject(); // Add existing devices to JSON object (if any) for (int i=0; i<regDevNum; i++) { regIdJSON[String(i)] = regAndroidIds[i]; } 1 2 3 4 5 6 7 8 9 10 // Create new device ids as JSON DynamicJsonBuffer jsonBuffer ; // Prepare JSON object JsonObject & regIdJSON = jsonBuffer . createObject ( ) ; // Add existing devices to JSON object (if any) for ( int i = 0 ; i < regDevNum ; i ++ ) { regIdJSON [ String ( i ) ] = regAndroidIds [ i ] ; }

Before writing to the file we convert the Json object into a string

// Convert JSON object to String String jsonTxt; regIdJSON.printTo(jsonTxt); // Save status to file devicesFile.println(jsonTxt); devicesFile.close(); return true; } 1 2 3 4 5 6 7 8 9 // Convert JSON object to String String jsonTxt ; regIdJSON . printTo ( jsonTxt ) ; // Save status to file devicesFile . println ( jsonTxt ) ; devicesFile . close ( ) ; return true ; }

boolean getRegisteredDevices()

This function reads the IDs from the file gcm.txt and stores them into the global variables regAndroidIds[]. The number of IDs is stored in regDevNum.

First step is to open the file for reading

boolean getRegisteredDevices() { // First get registered devices from the files // open file for reading. File devicesFile = SPIFFS.open("/gcm.txt", "r"); 1 2 3 4 boolean getRegisteredDevices ( ) { // First get registered devices from the files // open file for reading. File devicesFile = SPIFFS . open ( "/gcm.txt" , "r" ) ;

If successful we read the content into a string and parse it to a Json object

if (devicesFile) // Found file { // Read content from config file. String content = devicesFile.readString(); devicesFile.close(); // Convert file content into JSON object DynamicJsonBuffer jsonBuffer; JsonObject& regIdJSON = jsonBuffer.parseObject(content); 1 2 3 4 5 6 7 8 9 if ( devicesFile ) // Found file { // Read content from config file. String content = devicesFile . readString ( ) ; devicesFile . close ( ) ; // Convert file content into JSON object DynamicJsonBuffer jsonBuffer ; JsonObject & regIdJSON = jsonBuffer . parseObject ( content ) ;

If the parsing was successful the elements of the Json object are stored in the variable regAndroidIds[]

if (regIdJSON.success()) // Found some entries { regDevNum = 0; for (int i=0; i<MAX_DEVICE_NUM; i++) { if (regIdJSON.containsKey(String(i))) { // Found an entry regAndroidIds[i] = regIdJSON[String(i)].asString(); regDevNum++; } } return true; 1 2 3 4 5 6 7 8 9 10 if ( regIdJSON . success ( ) ) // Found some entries { regDevNum = 0 ; for ( int i = 0 ; i < MAX_DEVICE_NUM ; i ++ ) { if ( regIdJSON . containsKey ( String ( i ) ) ) { // Found an entry regAndroidIds [ i ] = regIdJSON [ String ( i ) ] . asString ( ) ; regDevNum ++ ; } } return true ;

And some error handling at the end of the function

} else { // File content is not a JSON string failReason = "File content is not a JSON string"; return false; } } else { // File not found failReason = "File does not exist"; return false; } } 1 2 3 4 5 6 7 8 9 } else { // File content is not a JSON string failReason = "File content is not a JSON string" ; return false ; } } else { // File not found failReason = "File does not exist" ; return false ; } }

boolean addRegisteredDevice()

This function adds a new ID to the array regAndroidIds and then calls writeRegIds to save them in the file system. In the example I limited the number of devices to 5, but with 1Mb storage in the file system a much higher number of registered devices could be handled.

First three checks are done

check if the ID has the correct length

available space to store the ID

ID is not yet registered

boolean addRegisteredDevice(String newDeviceID) { // Basic check if newDeviceID is valid if (newDeviceID.length() != 140) { failReason = "Invalid device id"; return false; } // First get registered devices from the files if (!getRegisteredDevices) { return false; } // Number of devices < MAX_DEVICE_NUM ??? if (regDevNum == MAX_DEVICE_NUM) { failReason = "Maximum number of devices registered"; return false; } // Check if id is already in the list for (int i=0; i<regDevNum; i++) { if (regAndroidIds[i] == newDeviceID) { failReason = "Devices already registered"; return false; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 boolean addRegisteredDevice ( String newDeviceID ) { // Basic check if newDeviceID is valid if ( newDeviceID . length ( ) != 140 ) { failReason = "Invalid device id" ; return false ; } // First get registered devices from the files if ( ! getRegisteredDevices ) { return false ; } // Number of devices < MAX_DEVICE_NUM ??? if ( regDevNum == MAX_DEVICE_NUM ) { failReason = "Maximum number of devices registered" ; return false ; } // Check if id is already in the list for ( int i = 0 ; i < regDevNum ; i ++ ) { if ( regAndroidIds [ i ] == newDeviceID ) { failReason = "Devices already registered" ; return false ; } }

If all checks are ok we add the new ID to the array regAndroidIds and call writeRegIds to update the stored file

// Add new ID to regAndroidIds[] regDevNum += 1; regAndroidIds[regDevNum-1] = newDeviceID; if (writeRegIds()) { // Refresh list with registered devices getRegisteredDevices(); return true; } else { // Refresh list with registered devices getRegisteredDevices(); return false; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Add new ID to regAndroidIds[] regDevNum += 1 ; regAndroidIds [ regDevNum - 1 ] = newDeviceID ; if ( writeRegIds ( ) ) { // Refresh list with registered devices getRegisteredDevices ( ) ; return true ; } else { // Refresh list with registered devices getRegisteredDevices ( ) ; return false ; } }

boolean delRegisteredDevice()

This function can be found three times, but with different arguments.

delRegisteredDevice() deletes all stored registration IDs

delRegisterDevice(String delRegId) deletes only the ID given in the string delRegId

delRegisterDevice(int delRegIndex) deletes only the ID at the position (index) delRegIndex

boolean delRegisteredDevice()

First refresh the list of registered IDs

boolean delRegisteredDevice() { // First get registered devices from the files if (!getRegisteredDevices) { return false; } 1 2 3 4 5 boolean delRegisteredDevice ( ) { // First get registered devices from the files if ( ! getRegisteredDevices ) { return false ; }

Then we delete the file gcm.txt and set the number of registered devices to 0

if (SPIFFS.remove("/gcm.txt")) { regDevNum = 0; return true; } else { failReason = "Error while deleting registration file"; return false; } return false; } 1 2 3 4 5 6 7 8 9 if ( SPIFFS . remove ( "/gcm.txt" ) ) { regDevNum = 0 ; return true ; } else { failReason = "Error while deleting registration file" ; return false ; } return false ; }

boolean delRegisteredDevice(String delRegId)

First refresh the list of registered IDs

boolean delRegisteredDevice(String delRegId) { int delRegIndex; // First get registered devices from the files if (!getRegisteredDevices) { return false; } 1 2 3 4 5 6 boolean delRegisteredDevice ( String delRegId ) { int delRegIndex ; // First get registered devices from the files if ( ! getRegisteredDevices ) { return false ; }

Then we are searching the ID we want to delete in the regAndroidIds array

// Search for id entry boolean foundIt = false; for (int i=0; i<regDevNum; i++) { if (regAndroidIds[i] == delRegId) { foundIt = true; delRegIndex = i; break; } } 1 2 3 4 5 6 7 8 9 // Search for id entry boolean foundIt = false ; for ( int i = 0 ; i < regDevNum ; i ++ ) { if ( regAndroidIds [ i ] == delRegId ) { foundIt = true ; delRegIndex = i ; break ; } }

If the ID was in the array we delete and fill the gap by shifting the other IDs towards the top of the array. Then we store the new list in the file system

if (foundIt) { // delete id entry regAndroidIds[delRegIndex] = ""; for (int i=delRegIndex; i<regDevNum-1; i++) { // Shift other ids regAndroidIds[i] = regAndroidIds[i+1]; } regDevNum -= 1; if (!writeRegIds()) { return false; } else { return true; } 1 2 3 4 5 6 7 8 9 10 11 12 13 if ( foundIt ) { // delete id entry regAndroidIds [ delRegIndex ] = "" ; for ( int i = delRegIndex ; i < regDevNum - 1 ; i ++ ) { // Shift other ids regAndroidIds [ i ] = regAndroidIds [ i + 1 ] ; } regDevNum -= 1 ; if ( ! writeRegIds ( ) ) { return false ; } else { return true ; }

And some error handling at the end

} else { failReason = "Could not find registration id " + delRegId; return false; } return false; } 1 2 3 4 5 6 } else { failReason = "Could not find registration id " + delRegId ; return false ; } return false ; }

boolean delRegisteredDevice(int delRegIndex)

First refresh the list of registered IDs and make a

boolean delRegisteredDevice(int delRegIndex) { // First get registered devices from the files if (!getRegisteredDevices) { return false; } 1 2 3 4 5 boolean delRegisteredDevice ( int delRegIndex ) { // First get registered devices from the files if ( ! getRegisteredDevices ) { return false ; }

Then we check if the index is within the limits, delete the entry at the index delRegIndex and fill the gap by shifting the other IDs towards the top of the array. Then we store the new list in the file system

// delete id entry String delRegId = regAndroidIds[delRegIndex]; regAndroidIds[delRegIndex] = ""; for (int i=delRegIndex; i<regDevNum-1; i++) { // Shift other ids regAndroidIds[i] = regAndroidIds[i+1]; } regDevNum -= 1; if (!writeRegIds()) { return false; 1 2 3 4 5 6 7 8 9 10 // delete id entry String delRegId = regAndroidIds [ delRegIndex ] ; regAndroidIds [ delRegIndex ] = "" ; for ( int i = delRegIndex ; i < regDevNum - 1 ; i ++ ) { // Shift other ids regAndroidIds [ i ] = regAndroidIds [ i + 1 ] ; } regDevNum -= 1 ; if ( ! writeRegIds ( ) ) { return false ;

And some error handling at the end

} else { return true; } return false; } 1 2 3 4 5 } else { return true ; } return false ; }

boolean gcmSendOut(String data)

gcmSendOut is used by gcmSendMsg to send the prepared data to the GCM server. gcmSendOut is used internally only,

First we stop eventually still running clients

boolean gcmSendOut(String data) { gcmClient.stop(); // Just to be sure String resultJSON; // For later use 1 2 3 boolean gcmSendOut ( String data ) { gcmClient . stop ( ) ; // Just to be sure String resultJSON ; // For later use

Second we connect to the GCM server and send the POST header and body

if (gcmClient.connect(serverName, 80)) { String postStr = "POST /gcm/send HTTP/1.1\r

"; postStr += "Host: " + hostName + "\r

"; postStr += "Accept: */"; postStr += "*\r

"; postStr += "Authorization: key=" + API_key + "\r

"; // Here the application specific API key is inserted postStr += "Content-Type: application/json\r

"; postStr += "Content-Length: "; postStr += String(data.length()); postStr += "\r

\r

"; postStr += data; // Here the Json object with the IDs and data is inserted postStr += "\r

\r

"; gcmClient.println(postStr); // Here we send the complete POST request to the GCM server 1 2 3 4 5 6 7 8 9 10 11 12 13 14 if ( gcmClient . connect ( serverName , 80 ) ) { String postStr = "POST /gcm/send HTTP/1.1\r

" ; postStr += "Host: " + hostName + "\r

" ; postStr += "Accept: */" ; postStr += "*\r

" ; postStr += "Authorization: key=" + API_key + "\r

" ; // Here the application specific API key is inserted postStr += "Content-Type: application/json\r

" ; postStr += "Content-Length: " ; postStr += String ( data . length ( ) ) ; postStr += "\r

\r

" ; postStr += data ; // Here the Json object with the IDs and data is inserted postStr += "\r

\r

" ; gcmClient . println ( postStr ) ; // Here we send the complete POST request to the GCM server

Then we wait for the response from the GCM server to check if the request was accepted:

// Get response from GCM server String inString = ""; while (gcmClient.available()) { char inChar = gcmClient.read(); inString += (char)inChar; } if (inString.indexOf("200 OK") >= 0) { int startOfJSON = inString.indexOf("{\"multicast_id"); int endOfJSON = inString.indexOf("}]}"); resultJSON = inString.substring(startOfJSON,endOfJSON+3); failReason = resultJSON; } else { failReason = "Sending not successful"; } gcmClient.stop(); } else { failReason = "Connection to GCM server failed!"; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Get response from GCM server String inString = "" ; while ( gcmClient . available ( ) ) { char inChar = gcmClient . read ( ) ; inString += ( char ) inChar ; } if ( inString . indexOf ( "200 OK" ) >= 0 ) { int startOfJSON = inString . indexOf ( "{\"multicast_id" ) ; int endOfJSON = inString . indexOf ( "}]}" ) ; resultJSON = inString . substring ( startOfJSON , endOfJSON + 3 ) ; failReason = resultJSON ; } else { failReason = "Sending not successful" ; } gcmClient . stop ( ) ; } else { failReason = "Connection to GCM server failed!" ; }

The result is somewhere hidden in the response. Best thing is to first check if the response contains “200 OK”, because that means that the GCM server accepted our request. The detailed response from the GCM server is a Json object which looks like

{"multicast_id":5923791905605145654,"success":3,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1454298410810777%adf4b72ff9fd7ecd"},{"message_id":"0:1454298410810780%adf4b72ff9fd7ecd"},{"message_id":"0:1454298410810779%cbe2ca3ff9fd7ecd"}]} 1 { "multicast_id" : 5923791905605145654 , "success" : 3 , "failure" : 0 , "canonical_ids" : 0 , "results" : [ { "message_id" : "0:1454298410810777%adf4b72ff9fd7ecd" } , { "message_id" : "0:1454298410810780%adf4b72ff9fd7ecd" } , { "message_id" : "0:1454298410810779%cbe2ca3ff9fd7ecd" } ] }

Important here are the keys “success” and “failure”. They tell us to how many Android devices the message was successfully transferred (or not!).

For now I do not parse this result in the code but I return it to the calling code as “failReason”.

Some final householding:

if (failReason != "success") { failReason = resultJSON; return false; } else { failReason = resultJSON; return true; } } 1 2 3 4 5 6 7 8 if ( failReason != "success" ) { failReason = resultJSON ; return false ; } else { failReason = resultJSON ; return true ; } }

gsm_esp_test.ino

This code is only an example for the implementation of the GCM functions into your own code.

Two parts are required to get the GCM functions to work:

within setup() you mount the file system by adding

// Initialize file system. SPIFFS.begin(); 1 2 // Initialize file system. SPIFFS . begin ( ) ;

you need to start the server to handle incoming registration messages

// Start the web server to serve incoming requests server.begin(); 1 2 // Start the web server to serve incoming requests server . begin ( ) ;

Therefor I will only go into details of the webServer function which will handle the registration, listing and deletion of IDs.

void webServer()

Description

receives commands from web client or Android device 1 receives commands from web client or Android device

Arguments

_httpClient_ WiFiClient of the connecting client 1 _httpClient_ WiFiClient of the connecting client

Used global variables

Global string array *regAndroidIds[]* contains the ids Global int *regDevNum* contains number of devices 1 2 Global string array * regAndroidIds [ ] * contains the ids Global int * regDevNum* contains number of devices

Return value

none 1 none

Example

/** setup Initialize application called once after reboot */ void setup() { WiFi.disconnect(); WiFi.mode(WIFI_STA); WiFi.config(ipAddr, ipGateWay, ipSubNet); WiFi.begin(ssid, password); int connectTimeout = 0; while (WiFi.status() != WL_CONNECTED) { delay(500); connectTimeout++; if (connectTimeout > 60) { //Wait for 30 seconds to connect ESP.reset(); } } // Initialize the file system SPIFFS.begin(); // Start the web server to serve incoming requests server.begin(); } /** loop main program loop wait for connection to web server for testing purpose sends every minute a GCM notification to registered Android devices */ void loop() { // Handle new client request on HTTP server if available WiFiClient client = server.available(); if (client) { webServer(client); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /** setup Initialize application called once after reboot */ void setup ( ) { WiFi . disconnect ( ) ; WiFi . mode ( WIFI_STA ) ; WiFi . config ( ipAddr , ipGateWay , ipSubNet ) ; WiFi . begin ( ssid , password ) ; int connectTimeout = 0 ; while ( WiFi . status ( ) != WL_CONNECTED ) { delay ( 500 ) ; connectTimeout ++ ; if ( connectTimeout > 60 ) { //Wait for 30 seconds to connect ESP . reset ( ) ; } } // Initialize the file system SPIFFS . begin ( ) ; // Start the web server to serve incoming requests server . begin ( ) ; } /** loop main program loop wait for connection to web server for testing purpose sends every minute a GCM notification to registered Android devices */ void loop ( ) { // Handle new client request on HTTP server if available WiFiClient client = server . available ( ) ; if ( client ) { webServer ( client ) ; } }

URL arguments

Commands are send together with the URL. Lets assume your ESP8266 is connected with the IP address 192.168.0.148. Then the URL together with a command would look like http://192.168.0.148/?l for the command to list all registered device IDs. Supported commands are:

Register a new id /?regid= with length 140 letter, no leading/trailing

List registered ids /?l

Delete all registered ids /?da

Delete specific registered id by 140 letter registration id /?di= with length 140 characters, no leading/trailing

Delete specific registered index /?dx=

Send broadcast message to all registered devices /?s={“msgkeys”:[“key1″,”key2″],”msgs”:[“msg1″,”msg2”]}



URL examples

192.168.0.148/?l returns {"result":"success","0":"***ANDROID_REG_ID***","num":1} 192.168.0.148/?regid=APA91bHq3iaH0UqYadNegxi5lP0Li0lvumQvvKJS7GxtT9P0zkU-...-S6EOpbS returns {"result":"success","0":"***ANDROID_REG_ID***","num":1} 192.168.0.148/?da returns {"result":"success"} 192.168.0.148/?s={"msgkeys":["message","timestamp"],"msgs":["Hello world","Christmas 2015"]} sends {"message":"Hello world","timestamp":"Christmas 2015"} to all registered devices returns {"result":"success","response":"{\"multicast_id\":6404311408057644811, \"success\":3,\"failure\":0,\"canonical_ids\":0,\"results\": [{\"message_id\":\"0:1453965670295623%adf4b72ff9fd7ecd\"}, {\"message_id\":\"0:1453965670295621%adf4b72ff9fd7ecd\"}, {\"message_id\":\"0:1453965670295907%cbe2ca3ff9fd7ecd\"}]}"} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 192.168.0.148 / ? l returns { "result" : "success" , "0" : "***ANDROID_REG_ID***" , "num" : 1 } 192.168.0.148 / ? regid = APA91bHq3iaH0UqYadNegxi5lP0Li0lvumQvvKJS7GxtT9P0zkU - . . . - S6EOpbS returns { "result" : "success" , "0" : "***ANDROID_REG_ID***" , "num" : 1 } 192.168.0.148 / ? da returns { "result" : "success" } 192.168.0.148 / ? s = { "msgkeys" : [ "message" , "timestamp" ] , "msgs" : [ "Hello world" , "Christmas 2015" ] } sends { "message" : "Hello world" , "timestamp" : "Christmas 2015" } to all registered devices returns { "result" : "success" , "response" : "{\"multicast_id\":6404311408057644811, \"success\":3,\"failure\":0,\"canonical_ids\":0,\"results\": [{\"message_id\":\"0:1453965670295623%adf4b72ff9fd7ecd\"}, {\"message_id\":\"0:1453965670295621%adf4b72ff9fd7ecd\"}, {\"message_id\":\"0:1453965670295907%cbe2ca3ff9fd7ecd\"}]}" }

In the example code I implemented a simple webserver that handles all 6 commands, here are some comments about the code:

The declaration:

/** webServer receives commands from web client or Android device returns result as JSON string to the client "result":"success" ==> command successful "result":"timeout" ==> communication between client and server was interrupted "result":"invalid" ==> unknown command or invalid Android ID "result":"failed" ==> command unsuccessful because e.g. ID storage full, file error =================== Available commands: =================== -- Register a new id <url>/?regid=<android registration id> with length 140 character, no leading/trailing <"> -- List registered ids <url>/?l -- Delete all registered ids <url>/?da -- Delete specific registered id by 140 character registration id <url>/?di=<android registration id> with length 140 letter, no leading/trailing <"> -- Delete specific registered index <url>/?dx=<index> -- Send broadcast message to all registered devices <url>/?s={"msgkeys":["key1","key2"],"msgs":["msg1","msg2"]} ========= Examples: ========= List all registered devices 192.168.0.148/?l returns {"result":"success","0":"***ANDROID_REG_ID***","num":1} Register a new device 192.168.0.148/?regid=APA91bHq3iaH0UqYadNegxi5lP0Li0lvumQvvKJS7GxtT9P0zkU-...-S6EOpbS returns {"result":"success","0":"***ANDROID_REG_ID***","num":1} Delete all registered IDs 192.168.0.148/?da returns {"result":"success"} Send a message with 2 keys (message and timestamp) and the content "Hello world" and "Christmas 2015" to all registered Android devices 192.168.0.148/?s={"msgkeys":["message","timestamp"],"msgs":["Hello world","Christmas 2015"]} sends {"message":"Hello world","timestamp":"Christmas 2015"} to all registered devices returns {"result":"success","response":"{\"multicast_id\":6404311408057644811,\"success\":3,\"failure\":0,\"canonical_ids\":0,\"results\":[{\"message_id\":\"0:1453965670295623%adf4b72ff9fd7ecd\"},{\"message_id\":\"0:1453965670295621%adf4b72ff9fd7ecd\"},{\"message_id\":\"0:1453965670295907%cbe2ca3ff9fd7ecd\"}]}"} */ void webServer(WiFiClient httpClient) { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 /** webServer receives commands from web client or Android device returns result as JSON string to the client "result":"success" ==> command successful "result":"timeout" ==> communication between client and server was interrupted "result":"invalid" ==> unknown command or invalid Android ID "result":"failed" ==> command unsuccessful because e.g. ID storage full, file error =================== Available commands: =================== -- Register a new id <url>/?regid=<android registration id> with length 140 character, no leading/trailing <"> -- List registered ids <url>/?l -- Delete all registered ids <url>/?da -- Delete specific registered id by 140 character registration id <url>/?di=<android registration id> with length 140 letter, no leading/trailing <"> -- Delete specific registered index <url>/?dx=<index> -- Send broadcast message to all registered devices <url>/?s={"msgkeys":["key1","key2"],"msgs":["msg1","msg2"]} ========= Examples: ========= List all registered devices 192.168.0.148/?l returns {"result":"success","0":"***ANDROID_REG_ID***","num":1} Register a new device 192.168.0.148/?regid=APA91bHq3iaH0UqYadNegxi5lP0Li0lvumQvvKJS7GxtT9P0zkU-...-S6EOpbS returns {"result":"success","0":"***ANDROID_REG_ID***","num":1} Delete all registered IDs 192.168.0.148/?da returns {"result":"success"} Send a message with 2 keys (message and timestamp) and the content "Hello world" and "Christmas 2015" to all registered Android devices 192.168.0.148/?s={"msgkeys":["message","timestamp"],"msgs":["Hello world","Christmas 2015"]} sends {"message":"Hello world","timestamp":"Christmas 2015"} to all registered devices returns {"result":"success","response":"{\"multicast_id\":6404311408057644811,\"success\":3,\"failure\":0,\"canonical_ids\":0,\"results\":[{\"message_id\":\"0:1453965670295623%adf4b72ff9fd7ecd\"},{\"message_id\":\"0:1453965670295621%adf4b72ff9fd7ecd\"},{\"message_id\":\"0:1453965670295907%cbe2ca3ff9fd7ecd\"}]}"} */ void webServer ( WiFiClient httpClient ) {

Preparation of JSON object that will be used to send a response to the client. Here we wait 3 seconds to get the request body from the client. If there is no request body sent we respond with a timeout failure and close the connection

String s = "HTTP/1.1 200 OK\r

Access-Control-Allow-Origin: *\r

Content-Type: application/json\r

\r

"; int waitTimeOut = 0; DynamicJsonBuffer jsonBuffer; // Prepare json object for the response JsonObject& root = jsonBuffer.createObject(); // Wait until the client sends some data while (!httpClient.available()) { delay(1); waitTimeOut++; if (waitTimeOut > 3000) { // If no response for 3 seconds return root["result"] = "timeout"; root["reason"] = "Timeout while waiting for data from client"; String jsonString; root.printTo(jsonString); s += jsonString; httpClient.print(s); httpClient.flush(); httpClient.stop(); return; } } root["result"] = "success"; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 String s = "HTTP/1.1 200 OK\r

Access-Control-Allow-Origin: *\r

Content-Type: application/json\r

\r

" ; int waitTimeOut = 0 ; DynamicJsonBuffer jsonBuffer ; // Prepare json object for the response JsonObject & root = jsonBuffer . createObject ( ) ; // Wait until the client sends some data while ( ! httpClient . available ( ) ) { delay ( 1 ) ; waitTimeOut ++ ; if ( waitTimeOut > 3000 ) { // If no response for 3 seconds return root [ "result" ] = "timeout" ; root [ "reason" ] = "Timeout while waiting for data from client" ; String jsonString ; root . printTo ( jsonString ) ; s += jsonString ; httpClient . print ( s ) ; httpClient . flush ( ) ; httpClient . stop ( ) ; return ; } } root [ "result" ] = "success" ;

Read the request body from the client, which should have the command and parameters.

// Read the first line of the request String req = httpClient.readStringUntil('\r'); 1 2 // Read the first line of the request String req = httpClient . readStringUntil ( '\r' ) ;

We reduce the received data to the command and parameters only by finding the first occurence of “/” which is the start of the command and deleting the last 9 characters.

req = req.substring(req.indexOf("/"),req.length()-9); 1 req = req . substring ( req . indexOf ( "/" ) , req . length ( ) - 9 ) ;

Check if the command is “Register new device”. If yes, then we isolate the Android registration ID from the string and save it in regID

// Registration of new device if (req.substring(0, 8) == "/?regid=") { String regID = req.substring(8,req.length()); 1 2 3 // Registration of new device if ( req . substring ( 0 , 8 ) == "/?regid=" ) { String regID = req . substring ( 8 , req . length ( ) ) ;

If the length of the Android registration ID is correct we add it to the list of registered devices. If addRegisterDevice() was successfull we return “success” together with an updated list of registered devices. If addRegisterDevice() returns with a failure, the failure description is hold in the variable failReason. This could be one of the following reasons:

Maximum number of devices already registered

Device ID was already registered

File IO error (which would be a fatal error and the sign of a hardware problem with your ESP module)

// Check if length of ID is correct if (regID.length() != 140) { root["result"] = "invalid"; root["reason"] = "Length of ID is wrong"; } else { // Try to save ID if (!addRegisteredDevice(regID)) { root["result"] = "failed"; root["reason"] = failReason; } else { root["result"] = "success"; getRegisteredDevices(); for (int i=0; i<regDevNum; i++) { root[String(i)] = regAndroidIds[i]; } root["num"] = regDevNum; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Check if length of ID is correct if ( regID . length ( ) != 140 ) { root [ "result" ] = "invalid" ; root [ "reason" ] = "Length of ID is wrong" ; } else { // Try to save ID if ( ! addRegisteredDevice ( regID ) ) { root [ "result" ] = "failed" ; root [ "reason" ] = failReason ; } else { root [ "result" ] = "success" ; getRegisteredDevices ( ) ; for ( int i = 0 ; i < regDevNum ; i ++ ) { root [ String ( i ) ] = regAndroidIds [ i ] ; } root [ "num" ] = regDevNum ; } }

Next we check if the command is “List registered devices” and return the list of registered devices or an error message. An error could be:

No devices registered yet

File IO error (which would be a fatal error and the sign of a hardware problem with your ESP module)

// Send list of registered devices } else if (req.substring(0, 3) == "/?l"){ if (getRegisteredDevices()) { if (regDevNum != 0) { // Any devices already registered? for (int i=0; i<regDevNum; i++) { root[String(i)] = regAndroidIds[i]; } } root["num"] = regDevNum; root["result"] = "success"; } else { root["result"] = "failed"; root["reason"] = failReason; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Send list of registered devices } else if ( req . substring ( 0 , 3 ) == "/?l" ) { if ( getRegisteredDevices ( ) ) { if ( regDevNum != 0 ) { // Any devices already registered? for ( int i = 0 ; i < regDevNum ; i ++ ) { root [ String ( i ) ] = regAndroidIds [ i ] ; } } root [ "num" ] = regDevNum ; root [ "result" ] = "success" ; } else { root [ "result" ] = "failed" ; root [ "reason" ] = failReason ; }

Next we check if the command is “Delete one or all registered devices” and which type of delete request it is. The first letter after the command “/?d” is the type of delete command

a – delete all registered IDs

i – delete specific ID by given ID

x – delete ID at specific index

// Delete one or all registered device } else if (req.substring(0, 3) == "/?d"){ String delReq = req.substring(3,4); 1 2 3 // Delete one or all registered device } else if ( req . substring ( 0 , 3 ) == "/?d" ) { String delReq = req . substring ( 3 , 4 ) ;

If the request is “delete all”, we simple call delRegisteredDevice() with the first parameter set to true.

if (delReq == "a") { // Delete all registered devices if (delRegisteredDevice(true, "", 9999)) { root["result"] = "success"; } else { root["result"] = "failed"; root["reason"] = failReason; } 1 2 3 4 5 6 7 if ( delReq == "a" ) { // Delete all registered devices if ( delRegisteredDevice ( true , "" , 9999 ) ) { root [ "result" ] = "success" ; } else { root [ "result" ] = "failed" ; root [ "reason" ] = failReason ; }

If the request is “delete specific ID”, we extract the ID from the command line and then call delRegisteredDevice() with the second parameter containing the ID we want to delete

} else if (delReq == "i") { String delRegId = req.substring(5,146); delRegId.trim(); if (delRegisteredDevice(false, delRegId, 9999)) { root["result"] = "success"; } else { root["result"] = "failed"; root["reason"] = failReason; } 1 2 3 4 5 6 7 8 9 } else if ( delReq == "i" ) { String delRegId = req . substring ( 5 , 146 ) ; delRegId . trim ( ) ; if ( delRegisteredDevice ( false , delRegId , 9999 ) ) { root [ "result" ] = "success" ; } else { root [ "result" ] = "failed" ; root [ "reason" ] = failReason ; }

If the request is “delete ID at index”, we extract the index from the command line, check if it is valid and then call delRegisterDevice() with the third parameter containing the index we want to delete

} else if (delReq == "x") { int delRegIndex = req.substring(5,req.length()).toInt(); if ((delRegIndex < 0) || (delRegIndex > MAX_DEVICE_NUM-1)) { root["result"] = "invalid"; root["reason"] = "Index out of range"; } else { if (delRegisteredDevice(false, "", delRegIndex)) { root["result"] = "success"; } else { root["result"] = "failed"; root["reason"] = failReason; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 } else if ( delReq == "x" ) { int delRegIndex = req . substring ( 5 , req . length ( ) ) . toInt ( ) ; if ( ( delRegIndex < 0 ) || ( delRegIndex > MAX_DEVICE_NUM - 1 ) ) { root [ "result" ] = "invalid" ; root [ "reason" ] = "Index out of range" ; } else { if ( delRegisteredDevice ( false , "" , delRegIndex ) ) { root [ "result" ] = "success" ; } else { root [ "result" ] = "failed" ; root [ "reason" ] = failReason ; } } }

And at last we add the updated list of devices to the response

// Send list of registered devices if (getRegisteredDevices()) { if (regDevNum != 0) { // Any devices already registered? for (int i=0; i<regDevNum; i++) { root[String(i)] = regAndroidIds[i]; } } root["num"] = regDevNum; } 1 2 3 4 5 6 7 8 9 // Send list of registered devices if ( getRegisteredDevices ( ) ) { if ( regDevNum != 0 ) { // Any devices already registered? for ( int i = 0 ; i < regDevNum ; i ++ ) { root [ String ( i ) ] = regAndroidIds [ i ] ; } } root [ "num" ] = regDevNum ; }

The last part of the webServer function handles requests to send push messages to the registered devices. I chose a Json formatted string for the key names and messages. Key names must be always strings, but the messages can be all kind of variables. You can use Boolean, Integer, Float, Char, String, … for a message.

// Send broadcast message to all registered devices // Message must be a string in valid JSON format // e.g. {"msgkeys":["key1","key2"],"msgs":["msg1","msg2"]} } else if (req.substring(0, 3) == "/?s"){ 1 2 3 4 // Send broadcast message to all registered devices // Message must be a string in valid JSON format // e.g. {"msgkeys":["key1","key2"],"msgs":["msg1","msg2"]} } else if ( req . substring ( 0 , 3 ) == "/?s" ) {

Extract the JSON formatted string from the request, reformat if necessary and parse the string into a Json object

String jsonMsgs = req.substring(4,req.length()); jsonMsgs.trim(); jsonMsgs.replace("%22","\""); // " might have been replaced with %22 by the browser jsonMsgs.replace("%20"," "); // <space> might have been replaced with %22 by the browser jsonMsgs.replace("%27","'"); // ' might have been replaced with %22 by the browser // Convert message into JSON object DynamicJsonBuffer jsonBuffer; JsonObject& regJSON = jsonBuffer.parseObject(jsonMsgs); 1 2 3 4 5 6 7 8 String jsonMsgs = req . substring ( 4 , req . length ( ) ) ; jsonMsgs . trim ( ) ; jsonMsgs . replace ( "%22" , "\"" ) ; // " might have been replaced with %22 by the browser jsonMsgs . replace ( "%20" , " " ) ; // <space> might have been replaced with %22 by the browser jsonMsgs . replace ( "%27" , "'" ) ; // ' might have been replaced with %22 by the browser // Convert message into JSON object DynamicJsonBuffer jsonBuffer ; JsonObject & regJSON = jsonBuffer . parseObject ( jsonMsgs ) ;

Check if the parsing was successful and if the Json object has the required keys “msgkeys” and “msgs”.

if (regJSON.success()) {// Found some entries root["result"] = "invalid"; if (regJSON.containsKey("msgkeys")) { if (regJSON.containsKey("msgs")) { 1 2 3 4 if ( regJSON . success ( ) ) { // Found some entries root [ "result" ] = "invalid" ; if ( regJSON . containsKey ( "msgkeys" ) ) { if ( regJSON . containsKey ( "msgs" ) ) {

If all is ok create the Json arrays with the key names and the message values, then call gcmSendMsg to send the push request to the GCM server.

JsonArray& msgKeysJSON = regJSON["msgkeys"]; JsonArray& msgsJSON = regJSON["msgs"]; if (gcmSendMsg(msgKeysJSON, msgsJSON)) { root["result"] = "success"; } else { root["response"] = failReason; } 1 2 3 4 5 6 7 JsonArray & msgKeysJSON = regJSON [ "msgkeys" ] ; JsonArray & msgsJSON = regJSON [ "msgs" ] ; if ( gcmSendMsg ( msgKeysJSON , msgsJSON ) ) { root [ "result" ] = "success" ; } else { root [ "response" ] = failReason ; }

Finally some error handling:

} else { root["reason"] = "JSON key msgs is missing"; } } else { root["reason"] = "JSON key msgkeys is missing"; } } else { root["result"] = "invalid"; root["reason"] = "Invalid JSON message"; } 1 2 3 4 5 6 7 8 9 10 } else { root [ "reason" ] = "JSON key msgs is missing" ; } } else { root [ "reason" ] = "JSON key msgkeys is missing" ; } } else { root [ "result" ] = "invalid" ; root [ "reason" ] = "Invalid JSON message" ; }

If we couldn’t find any valid command we respond with an error

} else { root["result"] = "invalid"; root["reason"] = "Invalid command"; } 1 2 3 4 } else { root [ "result" ] = "invalid" ; root [ "reason" ] = "Invalid command" ; }

Last step is to send the response back to the connected client and close the connection.

String jsonString; root.printTo(jsonString); s += jsonString; httpClient.print(s); httpClient.flush(); httpClient.stop(); } 1 2 3 4 5 6 7 String jsonString ; root . printTo ( jsonString ) ; s += jsonString ; httpClient . print ( s ) ; httpClient . flush ( ) ; httpClient . stop ( ) ; }

The response is again a Json formatted string. It always has the key “result”, which tells whether the command was successful or failed. If there was a problem the key “reason” will give more information about the failure.

The Android application to test the GCM notification system.

For testing purposes I slightly changed the code from Android Push Notification using Google Cloud Messaging (GCM) – Part 2, an excellent tutorial how to setup the GCM notification system with an external web server.

The changed code is available on my Github repository.

The Android code is split into 3 classes

MainActivity – handles the UI and buttons

GCMBroadCastReceiver – receives the push notifications from the GCM server

GCMIntentService – handles the received push notifications and displays them as notifications in the top bar of the device

The changes I did to the original code are

in MainActivity add more buttons to test the unregister function and to list all registered devices

in MainActivity added handling of the response from the ESP8266 to display error messages

in GCMIntentService changed the way the received push notifications are shown

Broadcast sender and receiver to display GCM push notifications on the main UI

When the application is freshly installed and started it shows only a button to register to the GCM service:

When the button is pressed, the device registers itself at the GCM service and sends his registration ID to the ESP8266. There it is stored.

From this moment on the Android device will receive all push notifications that are initiated by the ESP8266 and displays them.

If the button “GET LIST OF REGISTERED DEVICES” is pressed the list is sent from the ESP8266:

Android code

I will not go into every detail of the Android code and focus only on a few code parts that are important to the functionality.

AndroidManifest.xml

It is important to add here the permissions that the application needs to connect to the Internet and receive the GCM messages:

<!-- For accessing Internet --> <uses-permission android:name="android.permission.INTERNET"/> <!-- For checking current network state --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <!-- For waking device from sleep for showing notification --> <uses-permission android:name="android.permission.WAKE_LOCK"/> <!-- For vibrating device --> <uses-permission android:name="android.permission.VIBRATE"/> <!-- For receiving GCM messages --> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <!-- For protecting GCM messages so that only your app can receive them --> <permission android:name="com.androidsrc.gcmsample.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="com.androidsrc.gcmsample.permission.C2D_MESSAGE" /> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < ! -- For accessing Internet -- > < uses - permission android : name = "android.permission.INTERNET" / > < ! -- For checking current network state -- > < uses - permission android : name = "android.permission.ACCESS_NETWORK_STATE" / > < ! -- For waking device from sleep for showing notification -- > < uses - permission android : name = "android.permission.WAKE_LOCK" / > < ! -- For vibrating device -- > < uses - permission android : name = "android.permission.VIBRATE" / > < ! -- For receiving GCM messages -- > < uses - permission android : name = "com.google.android.c2dm.permission.RECEIVE" / > < ! -- For protecting GCM messages so that only your app can receive them -- > < permission android : name = "com.androidsrc.gcmsample.permission.C2D_MESSAGE" android : protectionLevel = "signature" / > < uses - permission android : name = "com.androidsrc.gcmsample.permission.C2D_MESSAGE" / >

The receiver class for the GCM push notifications needs to be registered here as well:

<receiver android:name=".GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" > <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="tk.giesecke.gcm_esp8266" /> </intent-filter> </receiver> 1 2 3 4 5 6 7 8 9 < receiver android : name = ".GCMBroadcastReceiver" android : permission = "com.google.android.c2dm.permission.SEND" > < intent - filter > < action android : name = "com.google.android.c2dm.intent.RECEIVE" / > < action android : name = "com.google.android.c2dm.intent.REGISTRATION" / > < category android : name = "tk.giesecke.gcm_esp8266" / > < / intent - filter > < / receiver >

MainActivity.java

In onCreate() we ask for permission to access the internet and check if the device is already registered to the GCM service:

// Enable access to internet if (Build.VERSION.SDK_INT > 9) { /** ThreadPolicy to get permission to access internet */ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); } ..... // Read saved registration id from shared preferences. gcmRegId = getSharedPreferences().getString(PREF_GCM_REG_ID, ""); if (TextUtils.isEmpty(gcmRegId)) { showHideButtons(false); }else{ showHideButtons(true); regIdView.setText(gcmRegId); Toast.makeText(getApplicationContext(), "Already registered with GCM", Toast.LENGTH_SHORT).show(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Enable access to internet if ( Build . VERSION . SDK_INT > 9 ) { /** ThreadPolicy to get permission to access internet */ StrictMode . ThreadPolicy policy = new StrictMode . ThreadPolicy . Builder ( ) . permitAll ( ) . build ( ) ; StrictMode . setThreadPolicy ( policy ) ; } . . . . . // Read saved registration id from shared preferences. gcmRegId = getSharedPreferences ( ) . getString ( PREF_GCM_REG_ID , "" ) ; if ( TextUtils . isEmpty ( gcmRegId ) ) { showHideButtons ( false ) ; } else { showHideButtons ( true ) ; regIdView . setText ( gcmRegId ) ; Toast . makeText ( getApplicationContext ( ) , "Already registered with GCM" , Toast . LENGTH_SHORT ) . show ( ) ; }

In onResume() we register to the broadcast message from the GCM push receiver :

// Register the receiver for messages from UDP & GCM listener if (activityReceiver != null) { //Create an intent filter to listen to the broadcast sent with the action "ACTION_STRING_ACTIVITY" IntentFilter intentFilter = new IntentFilter(GCMIntentService.BROADCAST_RECEIVED); //Map the intent filter to the receiver registerReceiver(activityReceiver, intentFilter); } 1 2 3 4 5 6 7 // Register the receiver for messages from UDP & GCM listener if ( activityReceiver != null ) { //Create an intent filter to listen to the broadcast sent with the action "ACTION_STRING_ACTIVITY" IntentFilter intentFilter = new IntentFilter ( GCMIntentService . BROADCAST_RECEIVED ) ; //Map the intent filter to the receiver registerReceiver ( activityReceiver , intentFilter ) ; }

In onPause() we unregister from the broadcast messages:

// Unregister the receiver for messages from GCM listener unregisterReceiver(activityReceiver); 1 2 // Unregister the receiver for messages from GCM listener unregisterReceiver ( activityReceiver ) ;

In onClick I added the handling of the different buttons. The communication itself is handled in an AsyncTask in the background by calling

new WebServerTask().execute("/?di=" + gcmRegId); 1 new WebServerTask ( ) . execute ( "/?di=" + gcmRegId ) ;

This example is to delete the registration ID from the ESP8266. The command is given to the AsyncTask as an string argument. For listing the registered devices the command looks like

new WebServerTask().execute("/?l"); 1 new WebServerTask ( ) . execute ( "/?l" ) ;

The webServerTask contacts the ESP8266 (or the GCM server for the initial registration) in the background without blocking the UI. As the AsyncTask cannot update the UI directly it calls activityUpdate which will run on the UI thread and can do the updates.

private class WebServerTask extends AsyncTask<String, Void, JSONObject> { @Override protected JSONObject doInBackground(String... params) { URL url = null; // Build url to register GCM ID String espURL = WEB_SERVER_URL + params[0]; try { url = new URL(espURL); } catch (MalformedURLException e) { e.printStackTrace(); handler.sendEmptyMessage(MSG_REGISTER_WEB_SERVER_FAILURE); } JSONObject jsonResult = null; HttpURLConnection conn = null; try { assert url != null; conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestMethod("GET"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); int status = conn.getResponseCode(); String response = conn.getResponseMessage(); if (status == 200) { // Request success if (params[0].startsWith("/?regid=")) { handler.sendEmptyMessage(MSG_REGISTER_WEB_SERVER_SUCCESS); } response = readStream(conn.getInputStream()); } try { jsonResult = new JSONObject(response); } catch (JSONException e) { jsonResult = null; } } catch (IOException io) { io.printStackTrace(); handler.sendEmptyMessage(MSG_REGISTER_WEB_SERVER_FAILURE); } finally { if (conn != null) { conn.disconnect(); } } return jsonResult; } protected void onPostExecute(JSONObject result) { activityUpdate(result); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 private class WebServerTask extends AsyncTask < String , Void , JSONObject > { @ Override protected JSONObject doInBackground ( String . . . params ) { URL url = null ; // Build url to register GCM ID String espURL = WEB_SERVER_URL + params [ 0 ] ; try { url = new URL ( espURL ) ; } catch ( MalformedURLException e ) { e . printStackTrace ( ) ; handler . sendEmptyMessage ( MSG_REGISTER_WEB_SERVER_FAILURE ) ; } JSONObject jsonResult = null ; HttpURLConnection conn = null ; try { assert url != null ; conn = ( HttpURLConnection ) url . openConnection ( ) ; conn . setDoOutput ( true ) ; conn . setUseCaches ( false ) ; conn . setRequestMethod ( "GET" ) ; conn . setRequestProperty ( "Content-Type" , "application/x-www-form-urlencoded;charset=UTF-8" ) ; int status = conn . getResponseCode ( ) ; String response = conn . getResponseMessage ( ) ; if ( status == 200 ) { // Request success if ( params [ 0 ] . startsWith ( "/?regid=" ) ) { handler . sendEmptyMessage ( MSG_REGISTER_WEB_SERVER_SUCCESS ) ; } response = readStream ( conn . getInputStream ( ) ) ; } try { jsonResult = new JSONObject ( response ) ; } catch ( JSONException e ) { jsonResult = null ; } } catch ( IOException io ) { io . printStackTrace ( ) ; handler . sendEmptyMessage ( MSG_REGISTER_WEB_SERVER_FAILURE ) ; } finally { if ( conn != null ) { conn . disconnect ( ) ; } } return jsonResult ; } protected void onPostExecute ( JSONObject result ) { activityUpdate ( result ) ; } }

In activityUpdate the response from the ESP8266 (which is a JSON object) is analyzed and the results are shown on the UI below the buttons.

private void activityUpdate(final JSONObject result) { runOnUiThread(new Runnable() { @SuppressWarnings("deprecation") @Override public void run() { String jsonValue = ""; String uiText; if (result != null) { uiText = "Result: "; try { jsonValue = result.getString("result"); uiText += jsonValue + "

"; } catch (JSONException e) { uiText += "missing

"; } if (jsonValue.equalsIgnoreCase("success")) { if (result.has("num")) { // We got a list of registered devices uiText += "List of registered devices:

"; int numDevices = 0; try { numDevices = result.getInt("num"); } catch (JSONException e) { } for (int i=0; i<numDevices; i++) { try { jsonValue = result.getString(Integer.toString(i)); uiText += "Device index " + Integer.toString(i) + " :

" + jsonValue + "

"; } catch (JSONException e) { } } } } else { try { jsonValue = result.getString("reason"); uiText += "Reason: " + jsonValue + "

"; } catch (JSONException e) { uiText += "unknown

"; } } } else { uiText = "No response"; } regIdView.setText(uiText); } }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private void activityUpdate ( final JSONObject result ) { runOnUiThread ( new Runnable ( ) { @ SuppressWarnings ( "deprecation" ) @ Override public void run ( ) { String jsonValue = "" ; String uiText ; if ( result != null ) { uiText = "Result: " ; try { jsonValue = result . getString ( "result" ) ; uiText += jsonValue + "

" ; } catch ( JSONException e ) { uiText += "missing

" ; } if ( jsonValue . equalsIgnoreCase ( "success" ) ) { if ( result . has ( "num" ) ) { // We got a list of registered devices uiText += "List of registered devices:

" ; int numDevices = 0 ; try { numDevices = result . getInt ( "num" ) ; } catch ( JSONException e ) { } for ( int i = 0 ; i < numDevices ; i ++ ) { try { jsonValue = result . getString ( Integer . toString ( i ) ) ; uiText += "Device index " + Integer . toString ( i ) + " :

" + jsonValue + "

" ; } catch ( JSONException e ) { } } } } else { try { jsonValue = result . getString ( "reason" ) ; uiText += "Reason: " + jsonValue + "

" ; } catch ( JSONException e ) { uiText += "unknown

" ; } } } else { uiText = "No response" ; } regIdView . setText ( uiText ) ; } } ) ; }

activityReceiver is a broadcast receiver which is triggered by GCMIntentService if a GCM push notification has arrived.

private final BroadcastReceiver activityReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String message = "Received from " + intent.getStringExtra("sender") + " the message

" + intent.getStringExtra("message"); updateUIHandler.post(new updateUI(message)); } }; 1 2 3 4 5 6 7 8 9 10 11 12 private final BroadcastReceiver activityReceiver = new BroadcastReceiver ( ) { @ Override public void onReceive ( Context context , Intent intent ) { String message = "Received from " + intent . getStringExtra ( "sender" ) + " the message

" + intent . getStringExtra ( "message" ) ; updateUIHandler . post ( new updateUI ( message ) ) ; } } ;

Similar to the webServerTask the broadcast receiver cannot update the UI directly. To do this the updateUIHandler is used to call updateUI.

class updateUI implements Runnable { private final String msg; public updateUI(String str) { this.msg = str; } @Override public void run() { regIdView.setText(msg); } } 1 2 3 4 5 6 7 8 9 10 11 12 class updateUI implements Runnable { private final String msg ; public updateUI ( String str ) { this . msg = str ; } @ Override public void run ( ) { regIdView . setText ( msg ) ; } }

GCMBroadcastReceiver.java

This broadcast receiver is called by the Android OS when a GCM push notification arrives. It activates the GCMIntentService to handle the incoming notification. I used here the original code from Android Push Notification using Google Cloud Messaging (GCM) – Part 2 without any changes.

GCMIntentService.java

This intent service handles the incoming push notifications. I added to the original code a broadcast sender to inform the MainActivity.

//send broadcast from activity to all receivers listening to the action "ACTION_STRING_ACTIVITY" private void sendGCMBroadcast(String msgReceived) { Intent broadCastIntent = new Intent(); broadCastIntent.setAction(BROADCAST_RECEIVED); broadCastIntent.putExtra("sender", "GCM"); broadCastIntent.putExtra("message", msgReceived); sendBroadcast(broadCastIntent); } 1 2 3 4 5 6 7 8 //send broadcast from activity to all receivers listening to the action "ACTION_STRING_ACTIVITY" private void sendGCMBroadcast ( String msgReceived ) { Intent broadCastIntent = new Intent ( ) ; broadCastIntent . setAction ( BROADCAST_RECEIVED ) ; broadCastIntent . putExtra ( "sender" , "GCM" ) ; broadCastIntent . putExtra ( "message" , msgReceived ) ; sendBroadcast ( broadCastIntent ) ; }

That’s it. Hope this small code example helps you to get the Google Cloud Messaging service to work directly with your ESP8266 module without the need of an external webserver, PHP and MySQL databases.

Just as a reminder, the complete code is available on my Github repository.