plotly interface based on ardunio sample code

Dependents:   Plotly_HelloWorld

Library for plotting a simple x/y scatter chart on the plot.ly website.

See plotly_HelloWorld for sample usage.

Files at this revision

API Documentation at this revision

Comitter:
AndyA
Date:
Fri Jul 11 10:10:16 2014 +0000
Parent:
3:967be3d46701
Child:
5:fc8eefeb301b
Commit message:
Tidy up and add documentation

Changed in this revision

plotly.cpp Show annotated file Show diff for this revision Revisions of this file
plotly.h Show annotated file Show diff for this revision Revisions of this file
--- a/plotly.cpp	Fri Jul 11 08:08:06 2014 +0000
+++ b/plotly.cpp	Fri Jul 11 10:10:16 2014 +0000
@@ -4,23 +4,22 @@
 #define plotlyURL "plot.ly"
 #define dataURL "arduino.plot.ly"
 
-plotly::plotly(char *username, char *api_key, char* stream_tokens[], char *filename, int nTraces)
+plotly::plotly(char *username, char *api_key, char* stream_token, char *filename)
 {
-    log_level = 0;  // 0 = Debugging, 1 = Informational, 2 = Status, 3 = Errors, 4 = Quiet (// Serial Off)
+    log_level = 3;  // 0 = Debugging, 1 = Informational, 2 = Status, 3 = Errors, 4 = Quiet (// Serial Off)
     dry_run = false;
     username_ = username;
     api_key_ = api_key;
-    stream_tokens_ = stream_tokens;
+    stream_token_ = stream_token;
     filename_ = filename;
-    nTraces_ = nTraces;
     maxpoints = 30;
-    fibonacci_ = 1;
     world_readable = true;
-    convertTimestamp = true;
+    convertTimestamp = false;
     timezone = "America/Montreal";
     fileopt = "overwrite";
 
     socket = NULL;
+
     initalised = false;
 }
 
@@ -28,13 +27,16 @@
 plotly::~plotly()
 {
     closeStream();
+
 }
 
+
 bool plotly::init()
 {
 
     //
-    //  Validate a stream with a REST post to plotly
+    //  Create plot with a REST post to plotly
+    //  See the clientresp section of https://plot.ly/rest/ for details
     //
     if(dry_run && log_level < 3) {
         fprintf(stderr,"... This is a dry run, we are not connecting to plotly's servers...\n");
@@ -42,13 +44,18 @@
         fprintf(stderr,"... Attempting to connect to plotly's REST servers\n");
     }
 
-//    socket.set_blocking(false);
-
     if (!dry_run) {
+        int pause = 1;
         socket = new TCPSocketConnection();
         while (socket->connect(plotlyURL, 80) < 0) {
+            if (pause > 30) {
+                fprintf(stderr,"Failed to connect. :-(\n");
+                delete socket;
+                return false;
+            }
             fprintf(stderr,"... Couldn\'t connect to plotly's REST servers... trying again!\n");
-            wait(1);
+            wait(pause);
+            pause *= 2;
         }
     }
 
@@ -60,93 +67,24 @@
     print_("Host: 107.21.214.199\r\n");
     print_("User-Agent: Arduino/0.5.1\r\n");
     print_("plotly-streamtoken: ");
-    print_(stream_tokens_[0]);
-    print_("\r\n");
+    print_(stream_token_);
+    printNetTerminator_();
 
     print_("Content-Length: ");
-    int contentLength = 126 + len_(username_) + len_(fileopt) + nTraces_*(87+len_(maxpoints)) + (nTraces_-1)*2 + len_(filename_);
-    if(world_readable) {
-        contentLength += 4;
-    } else {
-        contentLength += 5;
-    }
-    // contentLength =
-    //   44  // first part of querystring below
-    // + len_(username)  // upper bound on username length
-    // + 5   // &key=
-    // + 10  // api_key length
-    // + 7  // &args=[...
-    // + nTraces*(87+len(maxpoints)) // len({\"y\": [], \"x\": [], \"type\": \"scatter\", \"stream\": {\"token\": \") + 10 + len(\", "maxpoints": )+len(maxpoints)+len(}})
-    // + (nTraces-1)*2 // ", " in between trace objects
-    // + 22  // ]&kwargs={\"fileopt\": \"
-    // + len_(fileopt)
-    // + 16  // \", \"filename\": \"
-    // + len_(filename)
-    // + 21 // ", "world_readable":
-    // + 4 if world_readable, 5 otherwise
-    // + 1   // closing }
-    //------
-    // 126 + len_(username) + len_(fileopt) + nTraces*(86+len(maxpoints)) + (nTraces-1)*2 + len_(filename)
-    //
-    // Terminate headers with new lines
 
-
-// big buffer method to generate the string so that length can be measured directly.
-
-
-//    fprintf(stderr,"AutoVersion:\n");
+    // calculate the length of the packet payload and store it in the big buffer.
     int lineLen = snprintf(buffer,k_bufferSize,"version=2.3&origin=plot&platform=arduino&un=%s&key=%s&args=[",username_,api_key_);
-    for(int i=0; i<nTraces_; i++) {
-        lineLen += snprintf((buffer+lineLen),k_bufferSize-lineLen,"{\"y\": [], \"x\": [], \"type\": \"scatter\", \"stream\": {\"token\": \"%s\", \"maxpoints\": %d}}%s",stream_tokens_[i],maxpoints,((nTraces_ > 1) && (i != nTraces_-1))?", ":"");
-    }
+    lineLen += snprintf((buffer+lineLen),k_bufferSize-lineLen,"{\"y\": [], \"x\": [], \"type\": \"scatter\", \"stream\": {\"token\": \"%s\", \"maxpoints\": %d}}",stream_token_,maxpoints);
     lineLen += snprintf((buffer+lineLen),k_bufferSize-lineLen,"]&kwargs={\"fileopt\": \"%s\", \"filename\": \"%s\", \"world_readable\": %s}",fileopt,filename_,world_readable?"true":"false");
 
-//    fprintf(stderr,buffer);
-//    fprintf(stderr,"\nLen = %d",lineLen);
+    print_(lineLen);
 
-    print_(lineLen);
-    print_("\r\n\r\n");
-    
-    lineLen = snprintf(buffer,k_bufferSize,"version=2.3&origin=plot&platform=arduino&un=%s&key=%s&args=[",username_,api_key_);
-    for(int i=0; i<nTraces_; i++) {
-        lineLen += snprintf((buffer+lineLen),k_bufferSize-lineLen,"{\"y\": [], \"x\": [], \"type\": \"scatter\", \"stream\": {\"token\": \"%s\", \"maxpoints\": %d}}%s",stream_tokens_[i],maxpoints,((nTraces_ > 1) && (i != nTraces_-1))?", ":"");
-    }
-    lineLen += snprintf((buffer+lineLen),k_bufferSize-lineLen,"]&kwargs={\"fileopt\": \"%s\", \"filename\": \"%s\", \"world_readable\": %s}",fileopt,filename_,world_readable?"true":"false");
+    printNetTerminator_();
+    printNetTerminator_();
+
     sendFormatedText(buffer,lineLen);
-    
-    print_("\r\n");
-/*            
-    // Start printing querystring body
-    print_("version=2.2&origin=plot&platform=arduino&un=");
-    print_(username_);
-    print_("&key=");
-    print_(api_key_);
-    print_("&args=[");
-    // print a trace for each token supplied
-    for(int i=0; i<nTraces_; i++) {
-        print_("{\"y\": [], \"x\": [], \"type\": \"scatter\", \"stream\": {\"token\": \"");
-        print_(stream_tokens_[i]);
-        print_("\", \"maxpoints\": ");
-        print_(maxpoints);
-        print_("}}");
-        if(nTraces_ > 1 && i != nTraces_-1) {
-            print_(", ");
-        }
-    }
-    print_("]&kwargs={\"fileopt\": \"");
-    print_(fileopt);
-    print_("\", \"filename\": \"");
-    print_(filename_);
-    print_("\", \"world_readable\": ");
-    if(world_readable) {
-        print_("true");
-    } else {
-        print_("false");
-    }
-    print_("}");
-    // final newline to terminate the POST
-    print_("\r\n");
-*/
+
+    printNetTerminator_();
 
     //
     // Wait for a response
@@ -188,7 +126,7 @@
                     // by comparing characters as they roll in
                     //
 
-                    if(asgCnt == len_(allStreamsGo) && !proceed) {
+                    if(asgCnt == strlen(allStreamsGo) && !proceed) {
                         proceed = true;
                     } else if(allStreamsGo[asgCnt]==c) {
                         asgCnt += 1;
@@ -205,15 +143,15 @@
                     //
 
                     if(log_level < 3) {
-                        if(url[urlCnt]==c && urlCnt < len_(url)) {
+                        if(url[urlCnt]==c && urlCnt < strlen(url)) {
                             urlCnt += 1;
-                        } else if(urlCnt > 0 && urlCnt < len_(url)) {
+                        } else if(urlCnt > 0 && urlCnt < strlen(url)) {
                             // Reset counter
                             urlCnt = 0;
                         }
-                        if(urlCnt == len_(url) && fidCnt < 4 && !fidMatched) {
+                        if(urlCnt == strlen(url) && fidCnt < 4 && !fidMatched) {
                             // We've counted through the url, start counting through the username
-                            if(usernameCnt < len_(username_)+2) {
+                            if(usernameCnt < strlen(username_)+2) {
                                 usernameCnt += 1;
                             } else {
                                 // the url ends with "
@@ -244,27 +182,28 @@
             fprintf(stderr,username_);
             fprintf(stderr,"/");
             for(int i=0; i<fidCnt; i++) {
-                fprintf(stderr,"%d ",fid[i]);
+                fprintf(stderr,"%c",fid[i]);
             }
             fprintf(stderr,"\n");
         }
     }
 
     if (proceed || dry_run) {
-      initalised = true;
+        initalised = true;
     }
     if (socket) {
-      delete socket;
-      socket=NULL;
-      }
+        delete socket;
+        socket=NULL;
+    }
     return initalised;
 }
 
-void plotly::openStream()
+bool plotly::openStream()
 {
 
     if (!initalised)
-        return;
+        return false;
+
     //
     // Start request to stream servers
     //
@@ -272,20 +211,28 @@
     if (socket) {
         delete socket;
         socket = NULL;
-        }
-        
+    }
+
 
     if(log_level < 3) fprintf(stderr,"... Connecting to plotly's streaming servers...\n");
 
 
-    if (!dry_run && !socket) {
+    if (!dry_run) {
+        int pause = 1;
         socket = new TCPSocketConnection();
         while (socket->connect(dataURL, 80) < 0) {
-            fprintf(stderr,"... Couldn\'t connect to servers... trying again!\n");
-            wait(10);
+            if (pause > 30) {
+                fprintf(stderr,"Failed to connect. :-(\n");
+                delete socket;
+                return false;
+            }
+            fprintf(stderr,"... Couldn\'t connect to plotly's REST servers... trying again!\n");
+            wait(pause);
+            pause *= 2;
         }
     }
 
+
     if(log_level < 3) fprintf(stderr,"... Connected to plotly's streaming servers\n... Initializing stream\n");
 
     print_("POST / HTTP/1.1\r\n");
@@ -294,28 +241,29 @@
     print_("Transfer-Encoding: chunked\r\n");
     print_("Connection: close\r\n");
     print_("plotly-streamtoken: ");
-    print_(stream_tokens_[0]);
-    print_("\r\n");
-//    if(convertTimestamp) {
-//        print_("plotly-convertTimestamp: \"");
-//        print_(timezone);
-//        print_("\"");
-//        print_("\r\n");
-//    }
-    print_("\r\n");
+    print_(stream_token_);
+    printNetTerminator_();
+    if(convertTimestamp) {
+        print_("plotly-convertTimestamp: \"");
+        print_(timezone);
+        print_("\"");
+        printNetTerminator_();
+    }
+    printNetTerminator_();
 
     if(log_level < 3) fprintf(stderr,"... Done initializing, ready to stream!\n");
+    return true;
 }
 
 void plotly::closeStream()
 {
     if (socket) {
         if (socket->is_connected()) {
-          print_("0\r\n\r\n");
-          socket->close();
+            print_("0\r\n\r\n");
+            socket->close();
         }
         delete socket;
-    socket=NULL;
+        socket=NULL;
     }
 }
 
@@ -328,116 +276,65 @@
     }
 }
 
-void plotly::jsonStart(int i)
+void plotly::plot(unsigned long x, int y)
 {
-    // Print the length of the message in hex:
-    // 15 char for the json that wraps the data: {"x": , "y": }\n
-    // + 23 char for the token: , "token": "abcdefghij"
-    // = 38
-    printHex_(i+38);
-    print_("\r\n{\"x\": ");
-}
-void plotly::jsonMiddle()
-{
-    print_(", \"y\": ");
-}
-void plotly::jsonEnd(char *token)
-{
-    print_(", \"streamtoken\": \"");
-    print_(token);
-    print_("\"");
-    print_("}\n\r\n");
+    if (!initalised)
+        return;
+
+    reconnectStream();
+
+    // need to prefix with the length so print it once to get the content lenght and then a second time with the prefix.
+    int len = snprintf(buffer,k_bufferSize,"{\"x\": %lu, \"y\": %d}\n", x,y);
+    len = snprintf(buffer,k_bufferSize,"%x\r\n{\"x\": %lu, \"y\": %d}\n\r\n",len, x,y);
+    sendFormatedText(buffer,len);
 }
 
-int plotly::len_(int i)
-{
-    // int range: -32,768 to 32,767
-    if(i > 9999) return 5;
-    else if(i > 999) return 4;
-    else if(i > 99) return 3;
-    else if(i > 9) return 2;
-    else if(i > -1) return 1;
-    else if(i > -10) return 2;
-    else if(i > -100) return 3;
-    else if(i > -1000) return 4;
-    else if(i > -10000) return 5;
-    else return 6;
-}
-int plotly::len_(unsigned long i)
-{
-    // max length of unsigned long: 4294967295
-    if(i > 999999999) return 10;
-    else if(i > 99999999) return 9;
-    else if(i > 9999999) return 8;
-    else if(i > 999999) return 7;
-    else if(i > 99999) return 6;
-    else if(i > 9999) return 5;
-    else if(i > 999) return 4;
-    else if(i > 99) return 3;
-    else if(i > 9) return 2;
-    else return 1;
-}
-int plotly::len_(char *i)
-{
-    return strlen(i);
-}
-void plotly::plot(unsigned long x, int y, char *token)
+void plotly::plot(unsigned long x, float y)
 {
     if (!initalised)
         return;
 
     reconnectStream();
 
-//    int len = snprintf(buffer,k_bufferSize,"{\"x\": %lu, \"y\": %d, \"streamtoken\": \"%s\"}\n", x,y,token);
-//    len = snprintf(buffer,k_bufferSize,"%X\r\n{\"x\": %lu, \"y\": %d, \"streamtoken\": \"%s\"}\n\r\n",len, x,y,token);
-    int len = snprintf(buffer,k_bufferSize,"{\"x\": %lu, \"y\": %d}\n", x,y);
-    len = snprintf(buffer,k_bufferSize,"%x\r\n{\"x\": %lu, \"y\": %d}\n\r\n",len, x,y);
-    sendFormatedText(buffer,len);
-}
-
-void plotly::plot(unsigned long x, float y, char *token)
-{
-    if (!initalised)
-        return;
-
-    reconnectStream();
-
+    // need to prefix with the length so print it once to get the content lenght and then a second time with the prefix.
     int len = snprintf(buffer,k_bufferSize,"{\"x\": %lu, \"y\": %.3f}\n", x,y);
     len = snprintf(buffer,k_bufferSize,"%x\r\n{\"x\": %lu, \"y\": %.3f}\n\r\n",len, x,y);
-//    int len = snprintf(buffer,k_bufferSize,"{\"x\": %lu, \"y\": %.3f, \"streamtoken\": \"%s\"}\n", x,y,token);
-//    len = snprintf(buffer,k_bufferSize,"%X\r\n{\"x\": %lu, \"y\": %.3f, \"streamtoken\": \"%s\"}\n\r\n",len, x,y,token);
     sendFormatedText(buffer,len);
 }
 
 bool plotly::print_(int d)
 {
-    int32_t len = snprintf(buffer,k_bufferSize,"%d",d);
-    return sendFormatedText(buffer,len);
+    char smallBuffer[10];
+    int32_t len = snprintf(smallBuffer,10,"%d",d);
+    return sendFormatedText(smallBuffer,len);
 }
 
 bool plotly::print_(unsigned long d)
 {
-    int32_t len = snprintf(buffer,k_bufferSize,"%lu",d);
-    return sendFormatedText(buffer,len);
+    char smallBuffer[12];
+    int32_t len = snprintf(smallBuffer,12,"%lu",d);
+    return sendFormatedText(smallBuffer,len);
 }
 
 bool plotly::print_(float d)
 {
-    int32_t len = snprintf(buffer,k_bufferSize,"%f",d);
-    return sendFormatedText(buffer,len);
+    char smallBuffer[12];
+    int32_t len = snprintf(smallBuffer,12,"%f",d);
+    return sendFormatedText(smallBuffer,len);
 }
 
-bool plotly::print_(char *d)
+bool plotly::printNetTerminator_()
+{
+    return sendFormatedText("\r\n",2);
+}
+
+
+bool plotly::print_(char *d) // strings could be long, use the big buffer
 {
     int32_t len = snprintf(buffer,k_bufferSize,"%s",d);
     return sendFormatedText(buffer,len);
 }
 
-bool plotly::printHex_(uint16_t d)
-{
-    int32_t len = snprintf(buffer,k_bufferSize,"%X",d);
-    return sendFormatedText(buffer,len);
-}
 
 bool plotly::sendFormatedText(char* data, int size)
 {
@@ -448,46 +345,20 @@
         if (!socket) {
             fprintf(stderr,"\nTX failed, No network socket exists\n");
             return false;
-            }
+        }
         if (!(socket->is_connected())) {
             fprintf(stderr,"\nTX failed, Network socket not connected\n");
-            return false;           
-            }
-        
+            return false;
+        }
+
         int32_t sent = socket->send_all(data,size);
         if (sent == size)
             return true;
         else {
             fprintf(stderr,"\nTX failed to send _%s_ Sent %d of %d bytes\n",data,sent,size);
-            echoRxData();
             return false;
         }
     } else
         return true;
 }
 
-void plotly::echoRxData()
-{
-
-    int32_t dataIn = socket->receive(buffer,k_bufferSize -1);
-    if (dataIn < 0) {
-        if (socket->is_connected()) {
-            fprintf(stderr,"error reading network socket. Closing it\n");
-            socket->close();
-            delete socket;
-            socket = NULL;
-            }
-        else {
-            fprintf(stderr,"error reading network socket, socket isn't connected\n");
-            delete socket;
-            socket = NULL;
-            }
-    }
-    if(dataIn > 0) {
-        buffer[dataIn]=0;
-        fprintf(stderr,"Rx Data __");
-        fprintf(stderr,buffer);
-        fprintf(stderr,"__\n");
-    }
-    
-}
\ No newline at end of file
--- a/plotly.h	Fri Jul 11 08:08:06 2014 +0000
+++ b/plotly.h	Fri Jul 11 10:10:16 2014 +0000
@@ -4,89 +4,138 @@
 #include <EthernetInterface.h>
 #include <TCPSocketConnection.h>
 
-
-/*********************************
-in main.cpp
-EthernetInterface eth;
-#define num_traces 1
-// Sign up to plotly here: https://plot.ly
-// View your API key and stream tokens in your settings: https://plot.ly/settings
-char *streaming_tokens[num_traces] = {"leofdu7cun"};
-plotly graph = plotly("AndyA", "acchq52p7j", streaming_tokens, "test1", num_traces);
-
-void plotSetup() {
-
-//    eth.init();
-//    eth.connect();
-
-  // Initialize a streaming graph in your plotly account
-    pc.printf("plot init..\n");
-  graph.init();
-    pc.printf("Open stream..\n");
-  // Initialize plotly's streaming service
-  graph.openStream(); 
-}
-
-void plotGenerateData() {
-  static int counter = 0;
-  graph.plot(counter, counter*counter, streaming_tokens[0]);
-  counter++;
-}
+// size of the large buffer used for constructing messages.
+#define k_bufferSize 400
 
 
-********************************************/
-
-#define k_bufferSize 512
+/** Create a plot on plot.ly
+*
+* Based on the Ardunio code supplied by plot.ly
+*
+* Creates a streaming X/Y scatter plot with line on the plot.ly site.
+* Update periods can be between 50ms and 60s due to limitations imposed by plot.ly.
+* 
+* Requires an mbed with network support.
+* 
+* Provided as is, it works for me but your mileage may vary. Sorry, I don't have time to offer much support on this.
+* 
+* You will need to create a plot.ly account and then go to https://plot.ly/settings to get your API key and a streaming token.
+* 
+* See Plotly_HelloWorld for a sample implimentation.
+* 
+*/
 
 class plotly
 {
     public:
-        plotly(char *username, char *api_key, char* stream_tokens[], char *filename, int nTraces);
+    /**
+    @param username Your plot.ly username
+    @param api_key Your plot.ly API key
+    @param stream_token A plot.ly streaming token for your plot.ly account
+    @param filename The name of the file to save the chart as
+    */
+        plotly(char *username, char *api_key, char* stream_token, char *filename);
+        /**
+        */
         ~plotly();
+        
+        /** Initalises the chart
+        
+        This fucntion creates a blank chart on the plot.ly system and configures it to recieve streamed data using the specified token
+        
+        Time taken for this function can vary depending on network delays.
+        
+        If you wish to change any of the options line max points or world readability then make sure you change them BEFORE calling this function.       
+        */
         bool init();
-        void openStream();
-        void closeStream();
-        void reconnectStream();
-        void jsonStart(int i);
-        void jsonMiddle();
-        void jsonEnd(char *token);
+
+        /**
+        Adds a point to the chart. The chart MUST be initalised before calling this.
+        Note, if the streaming network port is closed then this will attempt to open the port and re-establish the stream connection, this could block for a while.
+        
+        @param x The X value.
+        @param y The y value.
+        */
+        void plot(unsigned long x, int y);
+        /**
+        Adds a point to the chart. The chart MUST be initalised before calling this.
+        Note, if the streaming network port is closed then this will attempt to open the port and re-establish the stream connection, this could block for a while.
+        
+        @param x The X value.
+        @param y The y value.
+        */
+        void plot(unsigned long x, float y);
 
-        void plot(unsigned long x, int y, char *token);
-        void plot(unsigned long x, float y, char *token);
+        /**
+        Opens the streaming connection.
+        
+        Normally you'd do this after a sucessful call to init() and before starting plotting in order to avoid a connection delays when you first call plot()
+        */
+        bool openStream();
+        /**
+        closes the streaming connection
+        */
+        void closeStream();
 
+        /**
+         output message level
+         Messages are sent to stderr (which defaults to the mBed USB programing/debug port).
+          0 = Debugging, 1 = Informational, 2 = Status, 3 = Errors (default), 4 = Quiet
+          */
         int log_level;
+
+        /**
+        set true to not actually connect to the network..
+        */
         bool dry_run;
+
+        /**
+        Maximum points to display on the streaming chart, once you go over this old points will no longer be displayed.
+        Defaults to 30
+        */
         int maxpoints;
+        
+        /**
+        Sets whether the chart will be public or not.
+        Defaults to true
+        */
         bool world_readable;
+        
+        /**
+        Converts timestamps to the local time zone
+        */
         bool convertTimestamp;
+        /**
+        Timezone string to use
+        */
         char *timezone;
+        /**
+        Sets what to do if the file already exists, valid options are:
+        "new","overwrite" (default),"append","extend"
+        */
         char *fileopt;
 
     private:
+
+        void reconnectStream();
+
         bool print_(int d);
         bool print_(unsigned long d);
         bool print_(float d);
         bool print_(char *d);
-        bool printHex_(uint16_t d);
+        bool printNetTerminator_();
+ 
         
         bool sendFormatedText(char* data, int size);
-        void echoRxData();
-
-
-        int len_(int i);
-        int len_(unsigned long i);
-        int len_(char *i);
 
         char buffer[512];
 //        char rxBuffer[128];
         TCPSocketConnection *socket;
         
-        unsigned long fibonacci_;
         char *username_;
         char *api_key_;
-        char** stream_tokens_;
+        char *stream_token_;
         char *filename_;
-        int nTraces_;
         
         bool initalised;