![](/media/cache/img/default_profile.jpg.50x50_q85.jpg)
Pubnub demo for AT&T IoT Starter Kit. Functionally similar to the Flow demo.
Dependencies: FXOS8700CQ MODSERIAL mbed
Pubnub demo for AT&T IoT Starter Kit
This demo is functionally similar to the Flow demo, so you can find general information here: https://developer.mbed.org/users/JMF/code/Avnet_ATT_Cellular_IOT/.
The only difference is that we use Pubnub to publish the measurements and subscribe to receiving the instructions to set the LED.
Settings
Pubnub related settings are:
Pubnub settings in `config_me.h`
PUBNUB_SUBSCRIBE_KEY PUBNUB_PUBLISH_KEY PUBNUB_CHANNEL
All are documented in their respective comments.
Pubnub context class
Similar to Pubnub SDKs, we provide a Pubnub context class. It is defined in pubnub.h
header file and implemented in pubnub.cpp
.
It provides only the fundamental "publish" and "subscribe" methods. They are documented in the header file.
This class is reusable in other code (it is not specific to this demo), it has a very narrow interface to the AT&T IoT cellular modem code. For example of use, you can look at the main()
(in main.c
).
Sample of published data
Published message w/measurement data
{"serial":"vstarterkit001","temp":89.61,"humidity":35,"accelX":0.97,"accelY":0.013,"accelZ":-0.038}
Don't worry, nobody got burnt, the temperature is in degrees Fahrenheit. :)
Publish a message (from, say, the Pubnub console http://pubnub.com/console) of the form {"LED":<name-of-the-color>}
on the channel that this demo listens to (default is hello_world
) to turn the LED to that color on the Starter Kit:
Turn LED to red
{"LED":"Red"}
Turn LED to green
{"LED":"Green"}
Turn LED to blue
{"LED":"Blue"}
Revision 81:a5df87708b9a, committed 2016-09-02
- Comitter:
- sveljko
- Date:
- Fri Sep 02 17:44:55 2016 +0000
- Parent:
- 80:d635c0eddd6e
- Commit message:
- First version that works, forked from official AT&T IoT starter kit repository.
Changed in this revision
--- a/config_me.h Tue Aug 16 13:55:59 2016 +0000 +++ b/config_me.h Fri Sep 02 17:44:55 2016 +0000 @@ -23,23 +23,31 @@ #define BUF_SIZE_FOR_N_MAX_SOCKREAD (10) #define MAX_WNC_SOCKREAD_PAYLOAD (1500) -// This is the server's base URL name. Example "www.google.com" -// Note that when you Fork a FLOW, it will typically assign either -// "run-east.att.io" or "run-west.att.io", so be sure to check this. -static const char * MY_SERVER_URL = "run-west.att.io"; +// This is the server's base URL name. Pubnub SDKs refer to this as "the origin". +// In general, you should leave this to "pubsub.pubnub.com", but in some cases +// you may want to user a different origin. +static const char * MY_SERVER_URL = "pubsub.pubnub.com"; -// These are FLOW fields from the Endpoints tab: -#define FLOW_BASE_URL "/1e464b19cdcde/774c88d68202/86694923d5bf28a/in/flow" -#define FLOW_INPUT_NAME "/climate" + +/** Put your publish key here. If you don't have one, "demo" is the default + public key. But, it has limitations. + */ +#define PUBNUB_PUBLISH_KEY "demo" -// Unless you want to use a different protocol, this field should be left as is: -#define FLOW_URL_TYPE " HTTP/1.1\r\nHost: " +/** Put your subscribe key here. If you don't have one, "demo" is the default + public key. + */ +#define PUBNUB_SUBSCRIBE_KEY "demo" -// This identifier specifies with which FLOW device you are communicating. -// If you only have one devive there then you can just leave this as is. -// Once your FLOW device has been initialized (Virtual Device Initialize clicked), -// the Virtual Device will show up in M2X. This is its "DEVICE SERIAL" field -#define FLOW_DEVICE_NAME "vstarterkit001" +/** Define the Pubnub channel to use here. For the "demo/demo" keys, a well-known + channel is "hello_world", but you can use any you like, channels are dynamic + on Pubnub +*/ +#define PUBNUB_CHANNEL "hello_world" + +// This identifier specifies a "device name" to be sent in the JSON message. +// You can also use it as the UUID, if you wish. +#define THE_DEVICE_NAME "vstarterkit001" // This constant defines how often sensors are read and sent up to FLOW #define SENSOR_UPDATE_INTERVAL_MS 5000; //5 seconds
--- a/main.cpp Tue Aug 16 13:55:59 2016 +0000 +++ b/main.cpp Fri Sep 02 17:44:55 2016 +0000 @@ -23,6 +23,8 @@ #include "cell_modem.h" #include "hardware.h" +#include "pubnub.h" + I2C i2c(PTC11, PTC10); //SDA, SCL -- define the I2C pins being used MODSERIAL pc(USBTX, USBRX, 256, 256); // tx, rx with default tx, rx buffer sizes MODSERIAL mdm(PTD3, PTD2, 4096, 4096); @@ -71,42 +73,69 @@ PUTS("\r\n\r\nApp Firmware: Release 1.0 - built: "__DATE__" "__TIME__"\r\n\r\n"); } -void GenerateModemString(char * modem_string) + +static void GeneratePubnubJSON(char *s, unsigned n) { - switch(iSensorsToReport) - { - case TEMP_HUMIDITY_ONLY: - { - sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, FLOW_URL_TYPE, MY_SERVER_URL); - break; - } - case TEMP_HUMIDITY_ACCELEROMETER: - { - sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s&accelX=%s&accelY=%s&accelZ=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ, FLOW_URL_TYPE, MY_SERVER_URL); - break; - } - case TEMP_HUMIDITY_ACCELEROMETER_GPS: - { - sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s&accelX=%s&accelY=%s&accelZ=%s&gps_satellites=%s&latitude=%s&longitude=%s&altitude=%s&speed=%s&course=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ,SENSOR_DATA.GPS_Satellites,SENSOR_DATA.GPS_Latitude,SENSOR_DATA.GPS_Longitude,SENSOR_DATA.GPS_Altitude,SENSOR_DATA.GPS_Speed,SENSOR_DATA.GPS_Course, FLOW_URL_TYPE, MY_SERVER_URL); - break; - } - case TEMP_HUMIDITY_ACCELEROMETER_PMODSENSORS: - { - sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s&accelX=%s&accelY=%s&accelZ=%s&proximity=%s&light_uv=%s&light_vis=%s&light_ir=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ, SENSOR_DATA.Proximity, SENSOR_DATA.UVindex, SENSOR_DATA.AmbientLightVis, SENSOR_DATA.AmbientLightIr, FLOW_URL_TYPE, MY_SERVER_URL); - break; - } - case TEMP_HUMIDITY_ACCELEROMETER_PMODSENSORS_VIRTUALSENSORS: - { - sprintf(modem_string, "GET %s%s?serial=%s&temp=%s&humidity=%s&accelX=%s&accelY=%s&accelZ=%s&proximity=%s&light_uv=%s&light_vis=%s&light_ir=%s&virt_sens1=%s&virt_sens2=%s&virt_sens3=%s&virt_sens4=%s&virt_sens5=%s&virt_sens6=%s&virt_sens7=%s&virt_sens8=%s %s%s\r\n\r\n", FLOW_BASE_URL, FLOW_INPUT_NAME, FLOW_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ, SENSOR_DATA.Proximity, SENSOR_DATA.UVindex, SENSOR_DATA.AmbientLightVis, SENSOR_DATA.AmbientLightIr, SENSOR_DATA.Virtual_Sensor1, SENSOR_DATA.Virtual_Sensor2, SENSOR_DATA.Virtual_Sensor3, SENSOR_DATA.Virtual_Sensor4, SENSOR_DATA.Virtual_Sensor5, SENSOR_DATA.Virtual_Sensor6, SENSOR_DATA.Virtual_Sensor7, SENSOR_DATA.Virtual_Sensor8, FLOW_URL_TYPE, MY_SERVER_URL); - break; - } - default: - { - sprintf(modem_string, "Invalid sensor selected\r\n\r\n"); - break; - } - } //switch(iSensorsToReport) -} //GenerateModemString + switch (iSensorsToReport) { + case TEMP_HUMIDITY_ONLY: + snprintf(s, n, "{\"serial\":\"%s\",\"temp\":%s,\"humidity\":%s}", THE_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity); + break; + case TEMP_HUMIDITY_ACCELEROMETER: + snprintf(s, n, "{\"serial\":\"%s\",\"temp\":%s,\"humidity\":%s,\"accelX\":%s,\"accelY\":%s,\"accelZ\":%s}", THE_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX, SENSOR_DATA.AccelY, SENSOR_DATA.AccelZ); + break; + case TEMP_HUMIDITY_ACCELEROMETER_PMODSENSORS: + snprintf(s, n, "{\"serial\":\"%s\",\"temp\":%s,\"humidity\":%s,\"accelX\":%s,\"accelY\":%s,\"accelZ\":%s,\"proximity\":%s,\"light_uv\":%s,\"light_vis\":%s,\"light_ir\":%s}", THE_DEVICE_NAME, SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ, SENSOR_DATA.Proximity, SENSOR_DATA.UVindex, SENSOR_DATA.AmbientLightVis, SENSOR_DATA.AmbientLightIr); + break; + case TEMP_HUMIDITY_ACCELEROMETER_PMODSENSORS_VIRTUALSENSORS: + snprintf(s, n, "{\"serial\":\"%s\",\"temp\":%s,\"humidity\":%s,\"accelX\":%s,\"accelY\":%s,\"accelZ\":%s,\"proximity\":%s,\"light_uv\":%s,\"light_vis\":%s,\"light_ir\":%s,\"virt_sens1\":%s,\"virt_sens2\":%s,\"virt_sens3\":%s,\"virt_sens4\":%s,\"virt_sens5\":%s,\"virt_sens6\":%s,\"virt_sens7\":%s,\"virt_sens8\":%s}", + THE_DEVICE_NAME, + SENSOR_DATA.Temperature, SENSOR_DATA.Humidity, + SENSOR_DATA.AccelX,SENSOR_DATA.AccelY,SENSOR_DATA.AccelZ, + SENSOR_DATA.Proximity, SENSOR_DATA.UVindex, SENSOR_DATA.AmbientLightVis, SENSOR_DATA.AmbientLightIr, + SENSOR_DATA.Virtual_Sensor1, SENSOR_DATA.Virtual_Sensor2, + SENSOR_DATA.Virtual_Sensor3, SENSOR_DATA.Virtual_Sensor4, + SENSOR_DATA.Virtual_Sensor5, SENSOR_DATA.Virtual_Sensor6, + SENSOR_DATA.Virtual_Sensor7, SENSOR_DATA.Virtual_Sensor8); + break; + default: + snprintf(s, n, "\"Invalid sensors selected: %d\"", iSensorsToReport); + break; + } +} + + +static void print_pubnub_result(pubnub_ctx::result r) +{ + switch (r) { + case pubnub_ctx::format_error: + PRINTF(RED "Pubnub response format error" DEF "\r\n"); + break; + case pubnub_ctx::response_too_short: + PRINTF(RED "Pubnub response too short" DEF "\r\n"); + break; + case pubnub_ctx::missing_open_bracket: + PRINTF(RED "Pubnub response missing open bracket `[`" DEF "\r\n"); + break; + case pubnub_ctx::missing_close_bracket: + PRINTF(RED "Pubnub response missing close bracket `]`" DEF "\r\n"); + break; + case pubnub_ctx::missing_time_token: + PRINTF(RED "Pubnub subscribe response missing time token" DEF "\r\n"); + break; + case pubnub_ctx::bad_time_token: + PRINTF(RED "Pubnub subscribe response bad time token" DEF "\r\n"); + break; + case pubnub_ctx::publish_failed: + PRINTF(RED "Pubnub publish failed" DEF "\r\n"); + break; + case pubnub_ctx::ok: + PRINTF(GRN "Pubnub transaction success" DEF "\r\n"); + break; + default: + PRINTF(RED "Unknown Pubnub erorr %d" DEF "\r\n", static_cast<int>(r)); + break; + } +} //Periodic timer @@ -141,9 +170,9 @@ //******************************************************************************************************************************************** //* Process the JSON response. In this example we are only extracting a LED color. //******************************************************************************************************************************************** -bool parse_JSON(char* json_string) +bool parse_JSON(char const* json_string) { - char* beginquote; + char const* beginquote; char token[] = "\"LED\":\""; beginquote = strstr(json_string, token ); if ((beginquote != 0)) @@ -205,7 +234,9 @@ } } //parse_JSON -int main() { + +int main() +{ static unsigned ledOnce = 0; //delay so that the debug terminal can open after power-on reset: wait (5.0); @@ -233,26 +264,38 @@ iTimer1Interval_ms = SENSOR_UPDATE_INTERVAL_MS; OneMsTicker.attach(OneMsFunction, 0.001f) ; + // Create the Pubnub context and message vector + pubnub_ctx pb(PUBNUB_PUBLISH_KEY, PUBNUB_SUBSCRIBE_KEY); + std::vector<std::string> messages; + + // If you wish, you can set the UUID to use - which will make that UUID + // visible for Presence related Pubnub features + // pb.set_uuid(THE_DEVICE_NAME); + // Send and receive data perpetually while(1) { - #ifdef USE_VIRTUAL_SENSORS +#ifdef USE_VIRTUAL_SENSORS ProcessUsbInterface(); - #endif +#endif if (bTimerExpiredFlag) { bTimerExpiredFlag = false; read_sensors(); //read available external sensors from a PMOD and the on-board motion sensor - char modem_string[512]; - GenerateModemString(&modem_string[0]); - char myJsonResponse[512]; - if (cell_modem_Sendreceive(&modem_string[0], &myJsonResponse[0])) - { - if (!ledOnce) - { - ledOnce = 1; - SetLedColor(0x2); //Green - } - parse_JSON(&myJsonResponse[0]); + + char json_string[512]; + GeneratePubnubJSON(json_string, sizeof json_string); + print_pubnub_result(pb.publish(PUBNUB_CHANNEL, json_string)); + + messages.clear(); + print_pubnub_result(pb.subscribe(PUBNUB_CHANNEL, messages)); + if (!ledOnce && !messages.empty()) { + ledOnce = 1; + SetLedColor(0x2); + } + for (std::vector<std::string>::iterator it = messages.begin(); it != messages.end(); ++it) { + char const *s = it->c_str(); + PRINTF(BLU "Pubnub message: %s" DEF "\n", s); + parse_JSON(s); } } //bTimerExpiredFlag } //forever loop
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pubnub.cpp Fri Sep 02 17:44:55 2016 +0000 @@ -0,0 +1,183 @@ +#include "pubnub.h" + +#include "mbed.h" +#include <string> +#include "wnc_control.h" + +#include <stdio.h> + + +pubnub_ctx::pubnub_ctx(char const* pub_key, char const *key_sub) + : d_pub_key(pub_key) + , d_key_sub(key_sub) + , d_token("0") +{ +} + + +pubnub_ctx::~pubnub_ctx() +{ +} + + +static void append_epilogue(std::string &s, std::string const& uuid, std::string const& auth) +{ + s += "?pnsdk=AvnetATTmbed"; + if (!uuid.empty()) { + s += "&uuid="; + s += uuid; + } + if (!auth.empty()) { + s += "&auth="; + s += auth; + } + s += " HTTP/1.1\r\nHost: pubsub.pubnub.com\r\n\r\n"; +} + + +pubnub_ctx::result pubnub_ctx::publish(char const* channel, char const* message) +{ + char const *pmessage = message; + std::string s("GET /publish/"); + s += d_pub_key; s += "/"; + s += d_key_sub; s += "/0/"; + s += channel; s += "/0/"; + + while (pmessage[0]) { + /* RFC 3986 Unreserved characters plus few + * safe reserved ones. */ + size_t okspan = strspn(pmessage, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~" ",=:;@[]"); + if (okspan > 0) { + s.append(pmessage, okspan); + pmessage += okspan; + } + if (pmessage[0]) { + /* %-encode a non-ok character. */ + s.append(1, '%'); + s.append(1, "0123456789ABCDEF"[pmessage[0] / 16]); + s.append(1, "0123456789ABCDEF"[pmessage[0] % 16]); + ++pmessage; + } + } + + append_epilogue(s, d_uuid, d_auth); + + sockwrite_mdm(s.c_str()); + string response; + unsigned read = sockread_mdm(&response, 1024, 50); + + if (read < 5) { + extern Serial pc; + pc.printf("read = %d\r\n", read); + return response_too_short; + } + char const* start = strchr(response.data(), '['); + if (NULL == start) { + return missing_open_bracket; + } + if (NULL == strrchr(response.data() + response.size() - 1, ']')) { + return missing_close_bracket; + } + + if (1 != strtol(start + 1, NULL, 10)) { + return publish_failed; + } + + return ok; +} + +pubnub_ctx::result pubnub_ctx::subscribe(char const* channel, std::vector<std::string>& messages) +{ + std::string s("GET /subscribe/"); + s += d_key_sub; s += "/"; + s += channel; s += "/0/"; + s += d_token;; + append_epilogue(s, d_uuid, d_auth); + + sockwrite_mdm(s.c_str()); + string response; + unsigned read = sockread_mdm(&response, 1024, 50); + + if (read < 10) { + return response_too_short; + } + char const* start = strchr(response.c_str(), '['); + if (NULL == start) { + return missing_open_bracket; + } + if (start[1] != '[') { + return missing_open_bracket; + } + enum { + idle, + in_string, + escape, + done + } state = idle; + start += 2; + char const* end; + int bracket_level = 1; + for (end = start; (*end != '\0') && (state != done); ++end) { + char c = *end; + switch (state) { + case idle: + switch (c) { + case '"': + state = in_string; + break; + case ',': + if (bracket_level == 1) { + messages.push_back(std::string(start, end-start)); + start = end + 1; + } + break; + case '{': + case '[': + ++bracket_level; + break; + case '}': + --bracket_level; + break; + case ']': + if (--bracket_level <= 0) { + if (end-start-1 > 0) { + messages.push_back(std::string(start, end-start)); + } + state = done; + } + break; + } + break; + case in_string: + switch (c) { + case '"': + state = idle; + break; + case '\\': + state = escape; + break; + } + break; + case escape: + state = in_string; + break; + default: + state = done; + break; + } + } + if (state != done) { + return format_error; + } + if ((end[0] != ',') && (end[1] != '"')) { + return missing_time_token; + } + start = end + 2; + end = strchr(start, '"'); + if (NULL == end) { + return bad_time_token; + } + d_token.assign(start, end-start); + + return ok; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pubnub.h Fri Sep 02 17:44:55 2016 +0000 @@ -0,0 +1,73 @@ +#if !defined INC_PUBNUB +#define INC_PUBNUB + +#include <string> +#include <vector> + + +/** A pubnub context. +*/ +class pubnub_ctx { +public: + /** Give publish key as @p pub_key and subscribe key as @p key_sub + to initialize the context. + */ + pubnub_ctx(char const* pub_key, char const* key_sub); + + ~pubnub_ctx(); + + /** Result of a Pubnub transaction */ + enum result { + format_error, + response_too_short, + missing_open_bracket, + missing_close_bracket, + missing_time_token, + bad_time_token, + publish_failed, + ok + }; + + /** Publish a JSON @p message to a pubnub @p channel (or channels, + separated by commas). + This is a synchronous call, so it will take a while to return + (depending on your Internet connection, could be 100s of ms). + */ + result publish(char const* channel, char const* message); + + /** Subscribe on a @p channel (or channels, separated by commas). + Messages received (if any) are pushed back to the @p messages + vector. + + This is a synchronous call, but it will not wait the full Pubnub ~5 min + for a message to appear on the channel. It will be much less, ~1 s. + + @return vector of messages - empty if there are no messages (like on + the very first subscribe) + */ + result subscribe(char const* channel, std::vector<std::string>& messages); + + std::string uuid() const { return d_uuid; } + void set_uuid(char const *s) { d_uuid = s; } + + std::string auth() const { return d_auth; } + void set_auth(char const *s) { d_auth = s; } + +private: + /// The publish key to use + std::string d_pub_key; + + /// The subscribe key to use + std::string d_key_sub; + + /// the last timetoken + std::string d_token; + + /// The UUID to use (empty - do not use) + std::string d_uuid; + + /// The auth key to use (empty - do not use) + std::string d_auth; +}; + +#endif // !defined INC_PUBNUB