plotly interface based on ardunio sample code
Library for plotting a simple x/y scatter chart on the plot.ly website.
See plotly_HelloWorld for sample usage.
Diff: plotly.cpp
- Revision:
- 0:96532c59670f
- Child:
- 1:d532e96fca12
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plotly.cpp Wed Jul 02 08:47:09 2014 +0000 @@ -0,0 +1,372 @@ +#include "plotly.h" +#include "mbed.h" + +#define plotlyURL "plot.ly" + +#define dataURL "arduino.plot.ly" + + +//#define plotlyURL "192.168.1.87" +//#define dataURL "192.168.1.87" + +plotly::plotly(char *username, char *api_key, char* stream_tokens[], char *filename, int nTraces) +{ + log_level = 0; // 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; + filename_ = filename; + nTraces_ = nTraces; + maxpoints = 30; + fibonacci_ = 1; + world_readable = true; + convertTimestamp = true; + timezone = "America/Montreal"; + fileopt = "overwrite"; +} + + +plotly::~plotly() { +closeStream(); + } + +bool plotly::init() +{ + + // + // Validate a stream with a REST post to plotly + // + if(dry_run && log_level < 3) { + fprintf(stderr,"... This is a dry run, we are not connecting to plotly's servers...\n"); + } else if(log_level < 3) { + fprintf(stderr,"... Attempting to connect to plotly's REST servers\n"); + } + + while ( !socket.connect(plotlyURL, 80) ) { + if(log_level < 4) { + fprintf(stderr,"... Couldn\'t connect to plotly's REST servers... trying again!\n"); + } + fibonacci_ += fibonacci_; + wait(10); + } + fibonacci_ = 1; + if(log_level < 3) {} fprintf(stderr,"... Connected to plotly's REST servers\n"); + if(log_level < 3) {} fprintf(stderr,"... Sending HTTP Post to plotly\n"); + print_("POST /clientresp HTTP/1.1\r\n"); + print_("Host: 107.21.214.199\r\n"); + print_("User-Agent: Arduino/0.5.1\r\n"); + + 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; + } + print_(contentLength); + // 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 + print_("\r\n\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"); + + // + // Wait for a response + // Parse the response for the "All Streams Go!" and proceed to streaming + // if we find it + // + char allStreamsGo[] = "All Streams Go!"; + char error[] = "\"error\": \""; + int asgCnt = 0; // asg stands for All Streams Go + char url[] = "\"url\": \"http://107.21.214.199/~"; + char fid[4]; + int fidCnt = 0; + int urlCnt = 0; + int usernameCnt = 0; + int urlLower = 0; + int urlUpper = 0; + bool proceed = false; + bool fidMatched = false; + char c; + + if(log_level < 2) { + fprintf(stderr,"... Sent message, waiting for plotly's response...\n"); + } + + if(!dry_run) { + while(!proceed) { + uint32_t dataIn = socket.receive_all(rxBuffer,127); + if(dataIn > 0) { + rxBuffer[dataIn]=0; + + if(log_level < 2) fprintf(stderr,rxBuffer); + + for (int i = 0; i<dataIn; i++) { + c = rxBuffer[i]; + // + // Attempt to read the "All streams go" msg if it exists + // by comparing characters as they roll in + // + + if(asgCnt == len_(allStreamsGo) && !proceed) { + proceed = true; + } else if(allStreamsGo[asgCnt]==c) { + asgCnt += 1; + } else if(asgCnt > 0) { + // reset counter + asgCnt = 0; + } + + // + // Extract the last bit of the URL from the response + // The url is in the form http://107.21.214.199/~USERNAME/FID + // We'll character-count up through char url[] and through username_, then start + // filling in characters into fid + // + + if(log_level < 3) { + if(url[urlCnt]==c && urlCnt < len_(url)) { + urlCnt += 1; + } else if(urlCnt > 0 && urlCnt < len_(url)) { + // Reset counter + urlCnt = 0; + } + if(urlCnt == len_(url) && fidCnt < 4 && !fidMatched) { + // We've counted through the url, start counting through the username + if(usernameCnt < len_(username_)+2) { + usernameCnt += 1; + } else { + // the url ends with " + if(c != '"') { + fid[fidCnt] = c; + fidCnt += 1; + } else if(fidCnt>0) { + fidMatched = true; + } + + } + } + } + } + } + wait(0.1); + } + } + + if(!dry_run && !proceed && log_level < 4) { + fprintf(stderr,"... Error initializing stream, aborting. Try again or get in touch with Chris at chris@plot.ly\n"); + } + + if(!dry_run && proceed && log_level < 3) { + fprintf(stderr,"... A-ok from plotly, All Streams Go!\n"); + if(fidMatched) { + fprintf(stderr,"... View your streaming plot here: https://plot.ly/~"); + fprintf(stderr,username_); + fprintf(stderr,"/"); + for(int i=0; i<fidCnt; i++) { + fprintf(stderr,"%d ",fid[i]); + } + fprintf(stderr,"\n"); + } + } + return proceed; +} + +void plotly::openStream() +{ + // + // Start request to stream servers + // + if(log_level < 3) {} fprintf(stderr,"... Connecting to plotly's streaming servers...\n"); + char server[] = dataURL; + int port = 80; + while ( !socket.connect(server, port) ) { + if(log_level < 4) fprintf(stderr,"... Couldn\'t connect to servers... trying again!\n"); + fibonacci_ += fibonacci_; + wait(10); + } + fibonacci_ = 1; + if(log_level < 3) {} fprintf(stderr,"... Connected to plotly's streaming servers\n... Initializing stream\n"); + + print_("POST / HTTP/1.1\r\n"); + print_("Host: 127.0.0.1\r\n"); + print_("User-Agent: Python\r\n"); + print_("Transfer-Encoding: chunked\r\n"); + print_("Connection: close\r\n"); + if(convertTimestamp) { + print_("plotly-convertTimestamp: \""); + print_(timezone); + print_("\""); + } + print_("\r\n\r\n"); + + if(log_level < 3) {} fprintf(stderr,"... Done initializing, ready to stream!\n"); +} + +void plotly::closeStream() +{ + print_("0\r\n\r\n"); + socket.close(); +} +void plotly::reconnectStream() +{ + while(!socket.is_connected()) { + if(log_level<4) fprintf(stderr,"... Disconnected from streaming servers\n"); + closeStream(); + openStream(); + } +} +void plotly::jsonStart(int i) +{ + // 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+44); + print_("\r\n{\"x\": "); +} +void plotly::jsonMiddle() +{ + print_(", \"y\": "); +} +void plotly::jsonEnd(char *token) +{ + print_(", \"streamtoken\": \""); + print_(token); + print_("\"}\n\r\n"); +} + +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) +{ + reconnectStream(); + jsonStart(len_(x)+len_(y)); + print_(x); + jsonMiddle(); + print_(y); + jsonEnd(token); +} + +void plotly::plot(unsigned long x, float y, char *token) +{ + reconnectStream(); + + char s_[15]; + snprintf(s_,15,"%2.3lf",y); + + jsonStart(len_(x)+len_(s_)-1); + print_(x); + jsonMiddle(); + print_(y); + jsonEnd(token); +} + +void plotly::print_(int d) +{ + uint32_t len = snprintf(txBuffer,128,"%d",d); + if(log_level < 2) fprintf(stderr,"%s",txBuffer); + if(!dry_run) socket.send_all(txBuffer,len); // skip the trailing 0 +} +void plotly::print_(unsigned long d) +{ + uint32_t len = snprintf(txBuffer,128,"%lu",d); + if(log_level < 2) fprintf(stderr,"%s",txBuffer); + if(!dry_run) socket.send_all(txBuffer,len); // skip the trailing 0 +} +void plotly::print_(float d) +{ + uint32_t len = snprintf(txBuffer,128,"%f",d); + if(log_level < 2) fprintf(stderr,"%s",txBuffer); + if(!dry_run) socket.send_all(txBuffer,len); // skip the trailing 0 +} +void plotly::print_(char *d) +{ + uint32_t len = snprintf(txBuffer,128,"%s",d); + if(log_level < 2) fprintf(stderr,"%s",txBuffer); + if(!dry_run) socket.send_all(txBuffer,len); // skip the trailing 0 +} + +void plotly::printHex_(uint16_t d) +{ + uint32_t len = snprintf(txBuffer,128,"%X",d); + if(log_level < 2) fprintf(stderr,"%s",txBuffer); + if(!dry_run) socket.send_all(txBuffer,len); // skip the trailing 0 +}