Shop OBEX P1 Docs P2 Docs Learn Events
Emic2 reading tweets — Parallax Forums

Emic2 reading tweets

SakuraTotSakuraTot Posts: 4
edited 2014-01-06 14:17 in Robotics
Hello,

I have a problem with a project I am working on with
emic2 and Arduino Uno+Ethernet Shield. I want to make emic reading tweets. This project is similar with Social chatter (http://www.adafruit.com/blog/2012/08/16 ... ad-tweets/). The problem is that the twitter API has changed so now I have to combine the code form socialChatter and thermalPrinter. I have done this very carefully but emic2 doesn't read the first tweet that is appeared on serial monitor and everything stops there. And if I place emic2TtsModule.say(msgText); before (for example) Serial.print(F("OK\r\nAwaiting results (if any)...")); the whole process stops there...Any idea why emic2 stops the whole operation? It would be very helpful!!!
Thanks!! icon_biggrin.gif

ps. Here is the code I have done!

[/FONT][/COLOR][COLOR=#2E8B57][FONT=Monaco]#include <SPI.h>[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]#include <Ethernet.h>[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]#include <EthernetUdp.h>[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]#include <Dns.h>[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]#include <sha1.h>[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]#include <SoftwareSerial.h>[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]#include <Emic2TtsModule.h>[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Stream parse states[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]enum ParseState {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_NORMAL,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_H,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HT,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTT,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTTP,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTTPS,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTTP_COLON,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTTP_COLON_SLASH,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_FOUND,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_FALSE_POSITIVE,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_DONE[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]};[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Similar to F(), but for PROGMEM string pointers rather than literals[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]#define F2(progmem_ptr) (const __FlashStringHelper *)progmem_ptr[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Configurable globals.  Edit to your needs. -------------------------------[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]const char PROGMEM[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Twitter application credentials -- see notes above -- DO NOT SHARE.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco] consumer_key[]  = "PUT_YOUR_CONSUMER_KEY_HERE",//PUT_YOUR_CONSUMER_KEY_HERE[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  access_token[]  = "PUT_YOUR_ACCESS_TOKEN_HERE",//PUT_YOUR_ACCESS_TOKEN_HERE[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  signingKey[]    = "Consumer secret" // Consumer secret[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    "&"             "Access token secret", // Access token secret[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // The ampersand is intentional -- do not delete![/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // queryString can be any valid Twitter API search string, including[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // boolean operators.  See http://dev.twitter.com/docs/using-search[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // for options and syntax.  Funny characters do NOT need to be URL[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // encoded here -- the code takes care of that.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  queryString[]   = "Christmas",[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Other globals.  You probably won't need to change these. -----------------[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  endpoint[]      = "/1.1/search/tweets.json",[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  agent[]         = "Gutenbird v1.0";[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]const char[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  host[]          = "api.twitter.com";[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]const int led_pin         = 1;           // To status LED (hardware PWM pin)[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// Emic 2 Globals[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]const int emic2RxPin = 11;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]const int emic2TxPin = 12;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]SoftwareSerial emic2Serial = SoftwareSerial(emic2RxPin, emic2TxPin);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]Emic2TtsModule emic2TtsModule = Emic2TtsModule(&emic2Serial);[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]const unsigned long[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  pollingInterval = 60L * 1000L, // Note: Twitter server will allow 150/hr max[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  searchesPerDay  = 86400000L / pollingInterval,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  connectTimeout  = 15L * 1000L, // Max time to wait for server connection[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  responseTimeout = 15L * 1000L; // Max time to wait for data from server[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//Adafruit_Thermal[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//  printer(printer_RX_Pin, printer_TX_Pin);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]byte[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  maxTweets = 3, // One tweet on first run; avoid runaway output[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  sleepPos  = 0, // Current "sleep throb" table position[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  resultsDepth,  // Used in JSON parsing[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Ethernet MAC address is found on sticker on Ethernet shield or board:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  mac[] = { 0x90, 0xA2, 0xWA, 0x0E, 0xEB, 0x43 };[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]IPAddress[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  ip(192,168,0,118); // Fallback address -- code will try DHCP first[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]char[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  lastId[21],    // 18446744073709551615\0 (64-bit maxint as string)[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  timeStamp[32], // WWW, DD MMM YYYY HH:MM:SS +XXXX\0[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  fromUser[16],  // Max username length (15) + \0[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  msgText[141],  // Max tweet length (140) + \0[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  name[12],      // Temp space for name:value parsing[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  value[141];    // Temp space for name:value parsing[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]int[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  searchCount = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]unsigned long[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  currentTime = 0L;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]EthernetClient[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  client;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Function prototypes -------------------------------------------------------[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]boolean jsonParse(int, byte);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]boolean readString(char *, int, char);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]int writeStringIfPossible(int, int, char *, char *);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]int unidecode(byte);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]int timedRead(void);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]PROGMEM byte[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  sleepTab[] = { // "Sleep throb" brightness table (reverse for second half)[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      0,   0,   0,   0,   0,   0,   0,   0,   0,   1,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      1,   1,   2,   3,   4,   5,   6,   8,  10,  13,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]     15,  19,  22,  26,  31,  36,  41,  47,  54,  61,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]     68,  76,  84,  92, 101, 110, 120, 129, 139, 148,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    158, 167, 177, 186, 194, 203, 211, 218, 225, 232,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    237, 242, 246, 250, 252, 254, 255 };[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// --------------------------------------------------------------------------[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]void setup() {[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Set up LED "sleep throb" ASAP, using Timer1 interrupt:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  TCCR1A  = _BV(WGM11); // Mode 14 (fast PWM), 64:1 prescale, OC1A off[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  TCCR1B  = _BV(WGM13) | _BV(WGM12) | _BV(CS11) | _BV(CS10);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  ICR1    = 8333;       // ~30 Hz between sleep throb updates[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  TIMSK1 |= _BV(TOIE1); // Enable Timer1 interrupt[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  sei();                // Enable global interrupts[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  randomSeed(analogRead(0));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Serial.begin(9600);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//  pinMode(printer_Ground, OUTPUT);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//  digitalWrite(printer_Ground, LOW); // Just a reference ground, not power[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//  printer.begin();[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//  printer.sleep();[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco] [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]Serial.print(F("Initializing Emic..."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  pinMode(emic2RxPin, INPUT);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  pinMode(emic2TxPin, OUTPUT);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  emic2Serial.begin(9600);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.init();[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.setVolume(8);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.setWordsPerMinute(120);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.setVoice(PerfectPaul);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("OK"));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Initialize Ethernet connection.  Request dynamic[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // IP address, fall back on fixed IP if that fails:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("Initializing Ethernet..."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.say(F("Hello, my name is Tweet o' Phone."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  if(Ethernet.begin(mac)) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("OK\r\n"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//    emic2TtsModule.say(F("OK."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("\r\nno DHCP response, using static IP address."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//    emic2TtsModule.say(F("No DHCP response, using static IP address."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    Ethernet.begin(mac, ip);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Get initial time from time server (make a few tries if needed)[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  for(uint8_t i=0; (i<5) && !(currentTime = getTime()); delay(15000L), i++);[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Clear all string data[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  strcpy_P(lastId, PSTR("1"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  timeStamp[0] = fromUser[0] = msgText[0] = name[0] = value[0] = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Search occurs in loop. ---------------------------------------------------[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]void loop() {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  uint8_t                  *in, out, i;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  char                      nonce[9],       // 8 random digits + NUL[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]                            searchTime[11], // 32-bit int + NUL[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]                            b64[29];[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  unsigned long             startTime, t;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  static const char PROGMEM b64chars[] =[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  startTime = millis();[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Disable Timer1 interrupt during network access, else there's trouble.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Just show LED at steady 100% while working.  :T[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  TIMSK1 &= ~_BV(TOIE1);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  analogWrite(led_pin, 255);[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Initialize unique values for query[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  sprintf(nonce, "%04x%04x", random() ^ currentTime, startTime ^ currentTime);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  sprintf(searchTime, "%ld", currentTime);[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Some debugging/testing/status stuff[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("  Current time: "));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Serial.println(currentTime);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("  Last ID: "));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Serial.println(lastId);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Serial.println(msgText[141]);[/FONT][/COLOR]


[COLOR=#2E8B57][FONT=Monaco]  Sha1.initHmac_P((uint8_t *)signingKey, sizeof(signingKey) - 1);[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // A dirty hack makes the Oauth song and dance MUCH simpler within the[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Arduino's limited RAM and CPU.  A proper general-purpose implementation[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // would be expected to URL-encode keys and values (from the complete list[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // of GET parameters and authentication values), sort by encoded key,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // concatenate and URL-encode the combined result.  Sorting is avoided[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // because every query this program performs has exactly the same set of[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // parameters, so we've pre-sorted the list as it appears here.  Keys[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // (and many values) are pre-encoded because they never change; many are[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // passed through verbatim because the format is known to not require[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // encoding.  Most reside in PROGMEM, not RAM.  This is bending a LOT of[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // rules of Good and Proper Authentication and would land you an 'F' in[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Comp Sci class, but it handles the required task and is VERY compact.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("GET&http%3A%2F%2F"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(host);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  urlEncode(Sha1, endpoint, true, false);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("&count%3D"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(maxTweets);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26include_entities%3D0%26oauth_consumer_key%3D"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F2(consumer_key));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26oauth_nonce%3D"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(nonce);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(searchTime);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26oauth_token%3D"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F2(access_token));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26oauth_version%3D1.0%26q%3D"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  urlEncode(Sha1, queryString, true, true);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26since_id%3D"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Sha1.print(lastId);[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // base64-encode SHA-1 hash output.  This is NOT a general-purpose base64[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // encoder!  It's stripped down for the fixed-length hash -- always 20[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // bytes input, always 27 chars output + '='.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  for(in = Sha1.resultHmac(), out=0; ; in += 3) { // octets to sextets[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    b64[out++] =   in[0] >> 2;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    b64[out++] = ((in[0] & 0x03) << 4) | (in[1] >> 4);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if(out >= 26) break;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    b64[out++] = ((in[1] & 0x0f) << 2) | (in[2] >> 6);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    b64[out++] =   in[2] & 0x3f;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  b64[out] = (in[1] & 0x0f) << 2;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Remap sextets to base64 ASCII chars[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  for(i=0; i<=out; i++) b64[i] = pgm_read_byte(&b64chars[b64[i]]);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  b64[i++] = '=';[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  b64[i++] = 0;[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("Connecting to server..."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  t = millis();[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  while((client.connect(host, 80) == false) &&[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    ((millis() - t) < connectTimeout));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  if(client.connected()) { // Success![/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("OK\r\nIssuing HTTP request..."));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    // Unlike the hash prep, parameters in the HTTP request don't require[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    // sorting, but are still somewhat ordered by function: GET parameters[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    // (search values), HTTP headers and Oauth credentials.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("GET "));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F2(endpoint));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("?count="));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(maxTweets);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("&since_id="));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(lastId);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("&include_entities=0&q="));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    urlEncode(client, queryString, true, false);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F(" HTTP/1.1\r\nHost: "));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(host);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("\r\nUser-Agent: "));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F2(agent));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("\r\nConnection: close\r\n"[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]                       "Content-Type: application/x-www-form-urlencoded;charset=UTF-8\r\n"[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]                       "Authorization: Oauth oauth_consumer_key=\""));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F2(consumer_key));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_nonce=\""));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(nonce);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_signature=\""));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    urlEncode(client, b64, false, false);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\""));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(searchTime);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_token=\""));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F2(access_token));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_version=\"1.0\"\r\n\r\n"));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("OK\r\nAwaiting results (if any)..."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    t = millis();[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    while((!client.available()) && ((millis() - t) < responseTimeout));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if(client.available()) { // Response received?[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      // Could add HTTP response header parsing here (400, etc.)[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if(client.find("\r\n\r\n")) { // Skip HTTP response header[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        Serial.print(F("OK\r\nProcessing results...\r\n"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        resultsDepth = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        jsonParse(0, 0);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else Serial.print(F("response not recognized.\r\n"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else   Serial.print(F("connection timed out.\r\n"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("Done.\r\n"));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    client.stop();[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  } else { // Couldn't contact server[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("failed\r\n"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Update time in seconds.  Once per day, re-sync with time server[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  currentTime += pollingInterval / 1000L;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  if((++searchCount >= searchesPerDay) && (t = getTime())) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    currentTime = t;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    searchCount = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Sometimes network access & printing occurrs so quickly, the steady-on[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // LED wouldn't even be apparent, instead resembling a discontinuity in[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // the otherwise smooth sleep throb.  Keep it on at least 4 seconds.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  while((millis() - startTime) < 4000UL);[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Pause between queries, factoring in time already spent on network[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // access, parsing, printing and LED pause above.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  if((millis() - startTime) < pollingInterval) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("Pausing..."));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    sleepPos = sizeof(sleepTab); // Resume following brightest position[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    TIMSK1 |= _BV(TOIE1); // Re-enable Timer1 interrupt for sleep throb[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    while((millis() - startTime) < pollingInterval);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("done\r\n"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Helper functions. --------------------------------------------------------[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]boolean jsonParse(int depth, byte endChar) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  int     c, i;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  boolean readName = true;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  for(;;) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    while(isspace(c = timedRead())); // Scan past whitespace[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if(c < 0)        return false;   // Timeout[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if(c == endChar) return true;    // EOD[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    if(c == '{') { // Object follows[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if(!jsonParse(depth + 1, '}')) return false;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if(!depth)                     return true; // End of file[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if(depth == resultsDepth) { // End of object in results list[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        // Dump to serial console as well[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//        Serial.print(F("  User: "));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//        Serial.println(fromUser);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//        Serial.print(F(" "));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        Serial.println(msgText);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//        Serial.print(F("  Time: "));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//        Serial.println(timeStamp);[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Output to Emic 2[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        emic2TtsModule.say(msgText);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]//          [/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]        // Clear strings for next object[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        timeStamp[0] = fromUser[0] = msgText[0] = 0;[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]        maxTweets = 1; // After first, subsequent queries allow more tweets[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if(c == '[') { // Array follows[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if((!resultsDepth) && (!strcasecmp(name, "statuses")))[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        resultsDepth = depth + 1;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if(!jsonParse(depth + 1,']')) return false;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if((c == '"') || (c == '\'')) { // String follows[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if(readName) { // Name-reading mode[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        if(!readString(name, sizeof(name)-1, c)) return false;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else { // Value-reading mode[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        if(!readString(value, sizeof(value)-1, c)) return false;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        // Process name and value strings:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        if       (!strcasecmp(name, "max_id_str")) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          strncpy(lastId, value, sizeof(lastId)-1);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else if(!strcasecmp(name, "created_at")) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          // Use created_at value for tweet, not user[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          if(depth == (resultsDepth + 1)) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]            strncpy(timeStamp, value, sizeof(timeStamp)-1);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else if(!strcasecmp(name, "screen_name")) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          strncpy(fromUser, value, sizeof(fromUser)-1);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else if(!strcasecmp(name, "text")) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          strncpy(msgText, value, sizeof(msgText)-1);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else if((!strcasecmp(name, "id_str")) &&[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]                  (strcasecmp(value, lastId) > 0) &&[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]                  (depth == (resultsDepth + 1))) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          strncpy(lastId, value, sizeof(lastId)-1);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if(c == ':') { // Separator between name:value[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      readName = false; // Now in value-reading mode[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      value[0] = 0;     // Clear existing value data[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if(c == ',') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      // Separator between name:value pairs.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      readName = true; // Now in name-reading mode[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      name[0]  = 0;    // Clear existing name data[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } // Else true/false/null or a number follows.  These values aren't[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      // used or expected by this program, so just ignore...either a comma[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      // or endChar will come along eventually, these are handled above.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Read string from client stream into destination buffer, up to a maximum[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// requested length.  Buffer should be at least 1 byte larger than this to[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// accommodate NUL terminator.  Opening quote is assumed already read,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// closing quote will be discarded, and stream will be positioned[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// immediately following the closing quote (regardless whether max length[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// is reached -- excess chars are discarded).  Returns true on success[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// (including zero-length string), false on timeout/read error.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]boolean readString(char *dest, int maxLen, char quote) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  int c, len = 0, link_buffer_idx;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  boolean done = false, read_next_char = true;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  char link_buffer[10];[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  ParseState state = STATE_NORMAL;[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  while(!done) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    // Handle cases where a character needs to be parsed twice.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if (read_next_char) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      c = timedRead();[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      read_next_char = true;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    // Exit if current character is the closing quote.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if (c == '"') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      done = true;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      break;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if(c == '\\') {    // Escaped char follows[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      c = timedRead(); // Read it[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      // Certain escaped values are for cursor control --[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      // there might be more suitable printer codes for each.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if     (c == 'b') c = '\b'; // Backspace[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      else if(c == 'f') c = '\f'; // Form feed[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      else if(c == 'n') c = '\n'; // Newline[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      else if(c == 'r') c = '\r'; // Carriage return[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      else if(c == 't') c = '\t'; // Tab[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      else if(c == 'u') c = unidecode(4);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      else if(c == 'U') c = unidecode(8);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      // else c is unaltered -- an escaped char such as \ or "[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } // else c is a normal unescaped char[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    // Special handling for http links & special characters #, :, /, (, ), *[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if (state == STATE_NORMAL) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c == 'h') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_H;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        memset(link_buffer, 0, sizeof(link_buffer));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        link_buffer_idx = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        // Special handling for certain characters[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        if (c == '#') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " hash \0");[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else if (c == ':') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " colon \0");[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else if (c == '/') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " slash \0");[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else if (c == '(' || c == ')') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " parenthesis \0");[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else if (c == '*') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " star \0");[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          // Handle timeout[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          if (c < 0) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]            dest[len] = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]            return false;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]          }[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    // In order to properly position the client stream at the end of[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    // the string, characters are read to the end quote, even if the max[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    // string length is reached...the extra chars are simply discarded.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if(len < maxLen) dest[len++] = c;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_H) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c == 't') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HT;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HT) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c == 't') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTT;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTT) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c == 'p') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTP;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTTP) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c == ':') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTP_COLON;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else if (c == 's') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTPS;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTTPS) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c == ':') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTP_COLON;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTTP_COLON) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c == '/') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTP_COLON_SLASH;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTTP_COLON_SLASH) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c == '/') {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FOUND;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_FOUND) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if (c != '.' && c != '/' && !isalnum(c)) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_DONE;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    switch (state) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      case STATE_NORMAL:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      case STATE_LINK_FOUND:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        // Do nothing.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        break;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      case STATE_LINK_FALSE_POSITIVE:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        len = writeStringIfPossible(len, maxLen, dest, link_buffer);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        read_next_char = false;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_NORMAL;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        break;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      case STATE_LINK_DONE:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        len = writeStringIfPossible(len, maxLen, dest, " link \0");[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        read_next_char = false;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        state = STATE_NORMAL;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        break;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      default:[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        // Save current character in case of false positive.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        link_buffer[link_buffer_idx++] = c;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]        break;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  dest[len] = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  return true; // Success (even if empty string)[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Write a passed in string out to the destination buffer if possible, advancing the[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// length as needed and returning it to the caller.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]int writeStringIfPossible(int len, int maxLen, char *dest, char *str) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  int str_idx = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  [/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  while (str[str_idx] != '\0' && len < maxLen) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    dest[len++] = str[str_idx++];[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  return len;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// Read a given number of hexadecimal characters from client stream,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// representing a Unicode symbol.  Return -1 on error, else return nearest[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// equivalent glyph in printer's charset.  (See notes below -- for now,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// always returns '-' or -1.)[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]int unidecode(byte len) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  int c, v, result = 0;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  while(len--) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if((c = timedRead()) < 0) return -1; // Stream timeout[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if     ((c >= '0') && (c <= '9')) v =      c - '0';[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    else if((c >= 'A') && (c <= 'F')) v = 10 + c - 'A';[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    else if((c >= 'a') && (c <= 'f')) v = 10 + c - 'a';[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    else return '-'; // garbage[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    result = (result << 4) | v;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // To do: some Unicode symbols may have equivalents in the printer's[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // native character set.  Remap any such result values to corresponding[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // printer codes.  Until then, all Unicode symbols are returned as '-'.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // (This function still serves an interim purpose in skipping a given[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // number of hex chars while watching for timeouts or malformed input.)[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  return '-';[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Read from client stream with a 5 second timeout.  Although an[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// essentially identical method already exists in the Stream() class,[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// it's declared private there...so this is a local copy.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]int timedRead(void) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  unsigned long start = millis();[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  while((!client.available()) && ((millis() - start) < 5000L));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  return client.read();  // -1 on timeout[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// URL-encoding output function for Print class.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// Input from RAM or PROGMEM (flash).  Double-encoding is a weird special[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]// case for Oauth (encoded strings get encoded a second time).[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]void urlEncode([/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  Print      &p,       // EthernetClient, Sha1, etc.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  const char *src,     // String to be encoded[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  boolean     progmem, // If true, string is in PROGMEM (else RAM)[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  boolean     x2)      // If true, "double encode" parenthesis[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]{[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  static const char PROGMEM hexChar[] = "0123456789ABCDEF";[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  uint8_t c;[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  while((c = (progmem ? pgm_read_byte(src) : *src))) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if(((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]       ((c >= '0') && (c <= '9')) || strchr_P(PSTR("-_.~"), c)) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      p.write(c);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    } else {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      if(x2) p.print("%25");[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      else   p.write('%');[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      p.write(pgm_read_byte(&hexChar[c >> 4]));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      p.write(pgm_read_byte(&hexChar[c & 15]));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    src++;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Minimalist time server query; adapted from Arduino UdpNTPClient tutorial.[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]unsigned long getTime(void) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  EthernetUDP   udp;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  DNSClient     dns;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  IPAddress     addr;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  byte          buf[48];[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  unsigned long t = 0L;[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("Polling time server..."));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  udp.begin(8888);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  dns.begin(Ethernet.dnsServerIP());[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  // Get a time server address from NTP pool[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  if(dns.getHostByName("pool.ntp.org", addr)) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    static const char PROGMEM[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      timeReqA[] = { 227,  0,  6, 236 },[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      timeReqB[] = {  49, 78, 49,  52 };[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    // Assemble and issue request packet[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    memset(buf, 0, sizeof(buf));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    memcpy_P( buf    , timeReqA, sizeof(timeReqA));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    memcpy_P(&buf[12], timeReqB, sizeof(timeReqB));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    udp.beginPacket(addr, 123);[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    udp.write(buf, sizeof(buf));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    udp.endPacket();[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]    delay(1000); // Allow time for response[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    if(udp.parsePacket()) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      // Read result, convert to UNIX time format[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      udp.read(buf, sizeof(buf));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      t = (((unsigned long)buf[40] << 24) |[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]           ((unsigned long)buf[41] << 16) |[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]           ((unsigned long)buf[42] <<  8) |[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]            (unsigned long)buf[43]) - 2208988800UL;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]      Serial.print(F("OK\r\n"));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  udp.stop();[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  if(!t) Serial.print(F("error\r\n"));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  return t;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Timer1 interrupt handler for sleep throb[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]ISR(TIMER1_OVF_vect, ISR_NOBLOCK) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Sine table contains only first half...reflect for second half...[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  analogWrite(led_pin, pgm_read_byte(&sleepTab[[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    (sleepPos >= sizeof(sleepTab)) ?[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    (sizeof(sleepTab) * 2 - 1 - sleepPos) : sleepPos]));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  if(++sleepPos >= (sizeof(sleepTab) * 2)) sleepPos = 0; // Roll over[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  TIFR1 |= TOV1; // Clear Timer1 interrupt flag[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  if(!t) Serial.print(F("error\r\n"));[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]  return t;[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]

[COLOR=#2E8B57][FONT=Monaco]// Timer1 interrupt handler for sleep throb[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]ISR(TIMER1_OVF_vect, ISR_NOBLOCK) {[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  // Sine table contains only first half...reflect for second half...[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  analogWrite(led_pin, pgm_read_byte(&sleepTab[[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    (sleepPos >= sizeof(sleepTab)) ?[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]    (sizeof(sleepTab) * 2 - 1 - sleepPos) : sleepPos]));[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  if(++sleepPos >= (sizeof(sleepTab) * 2)) sleepPos = 0; // Roll over[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]  TIFR1 |= TOV1; // Clear Timer1 interrupt flag[/FONT][/COLOR]
[COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR][COLOR=#333333][FONT=verdana]

Comments

  • SakuraTotSakuraTot Posts: 4
    edited 2014-01-06 08:20
    Hello,

    I am new in Arduino family and I have a problem with a project I am working on with emic2 and an Arduino Uno+Ethernet Shield. I want to make emic reading tweets. This project is similar with Social chatter (http://www.adafruit.com/blog/2012/08/16 ... ad-tweets/). The problem is that twitter API has changed, so now I have to combine the code form socialChatter and thermalPrinter. I have done this very carefully but emic2[FONT=verdana, arial, helvetica, sans-serif] doesn't read the first tweet that is appeared on serial monitor and everything stops there, also if I place [/FONT] emic2TtsModule.say(msgText); for example, before Serial.print(F("OK\r\nProcessing results...\r\n"));[FONT=verdana, arial, helvetica, sans-serif] the operation stops there… Any idea why[/FONT]emic2 stops the whole operation? It would be very helpful!!!

    Thanks!! icon_biggrin.gif

    ps. Here is the code I have done!

    [/FONT][/COLOR][COLOR=#2E8B57][FONT=Monaco]#include <SPI.h>[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]#include <Ethernet.h>[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]#include <EthernetUdp.h>[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]#include <Dns.h>[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]#include <sha1.h>[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]#include <SoftwareSerial.h>[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]#include <Emic2TtsModule.h>[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Stream parse states[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]enum ParseState {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_NORMAL,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_H,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HT,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTT,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTTP,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTTPS,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTTP_COLON,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_HTTP_COLON_SLASH,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_FOUND,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_FALSE_POSITIVE,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  STATE_LINK_DONE[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]};[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Similar to F(), but for PROGMEM string pointers rather than literals[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]#define F2(progmem_ptr) (const __FlashStringHelper *)progmem_ptr[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Configurable globals.  Edit to your needs. -------------------------------[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]const char PROGMEM[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Twitter application credentials -- see notes above -- DO NOT SHARE.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco] consumer_key[]  = "PUT_YOUR_CONSUMER_KEY_HERE",//PUT_YOUR_CONSUMER_KEY_HERE[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  access_token[]  = "PUT_YOUR_ACCESS_TOKEN_HERE",//PUT_YOUR_ACCESS_TOKEN_HERE[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  signingKey[]    = "Consumer secret" // Consumer secret[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    "&"             "Access token secret", // Access token secret[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // The ampersand is intentional -- do not delete![/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // queryString can be any valid Twitter API search string, including[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // boolean operators.  See http://dev.twitter.com/docs/using-search[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // for options and syntax.  Funny characters do NOT need to be URL[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // encoded here -- the code takes care of that.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  queryString[]   = "Christmas",[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Other globals.  You probably won't need to change these. -----------------[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  endpoint[]      = "/1.1/search/tweets.json",[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  agent[]         = "Gutenbird v1.0";[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]const char[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  host[]          = "api.twitter.com";[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]const int led_pin         = 1;           // To status LED (hardware PWM pin)[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// Emic 2 Globals[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]const int emic2RxPin = 11;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]const int emic2TxPin = 12;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]SoftwareSerial emic2Serial = SoftwareSerial(emic2RxPin, emic2TxPin);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]Emic2TtsModule emic2TtsModule = Emic2TtsModule(&emic2Serial);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]const unsigned long[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  pollingInterval = 60L * 1000L, // Note: Twitter server will allow 150/hr max[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  searchesPerDay  = 86400000L / pollingInterval,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  connectTimeout  = 15L * 1000L, // Max time to wait for server connection[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  responseTimeout = 15L * 1000L; // Max time to wait for data from server[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//Adafruit_Thermal[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//  printer(printer_RX_Pin, printer_TX_Pin);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]byte[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  maxTweets = 3, // One tweet on first run; avoid runaway output[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  sleepPos  = 0, // Current "sleep throb" table position[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  resultsDepth,  // Used in JSON parsing[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Ethernet MAC address is found on sticker on Ethernet shield or board:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  mac[] = { 0x90, 0xA2, 0xWA, 0x0E, 0xEB, 0x43 };[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]IPAddress[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  ip(192,168,0,118); // Fallback address -- code will try DHCP first[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]char[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  lastId[21],    // 18446744073709551615\0 (64-bit maxint as string)[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  timeStamp[32], // WWW, DD MMM YYYY HH:MM:SS +XXXX\0[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  fromUser[16],  // Max username length (15) + \0[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  msgText[141],  // Max tweet length (140) + \0[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  name[12],      // Temp space for name:value parsing[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  value[141];    // Temp space for name:value parsing[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]int[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  searchCount = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]unsigned long[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  currentTime = 0L;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]EthernetClient[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  client;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Function prototypes -------------------------------------------------------[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]boolean jsonParse(int, byte);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]boolean readString(char *, int, char);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]int writeStringIfPossible(int, int, char *, char *);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]int unidecode(byte);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]int timedRead(void);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]PROGMEM byte[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  sleepTab[] = { // "Sleep throb" brightness table (reverse for second half)[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      0,   0,   0,   0,   0,   0,   0,   0,   0,   1,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      1,   1,   2,   3,   4,   5,   6,   8,  10,  13,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]     15,  19,  22,  26,  31,  36,  41,  47,  54,  61,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]     68,  76,  84,  92, 101, 110, 120, 129, 139, 148,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    158, 167, 177, 186, 194, 203, 211, 218, 225, 232,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    237, 242, 246, 250, 252, 254, 255 };[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// --------------------------------------------------------------------------[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]void setup() {[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Set up LED "sleep throb" ASAP, using Timer1 interrupt:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  TCCR1A  = _BV(WGM11); // Mode 14 (fast PWM), 64:1 prescale, OC1A off[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  TCCR1B  = _BV(WGM13) | _BV(WGM12) | _BV(CS11) | _BV(CS10);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  ICR1    = 8333;       // ~30 Hz between sleep throb updates[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  TIMSK1 |= _BV(TOIE1); // Enable Timer1 interrupt[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  sei();                // Enable global interrupts[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  randomSeed(analogRead(0));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Serial.begin(9600);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//  pinMode(printer_Ground, OUTPUT);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//  digitalWrite(printer_Ground, LOW); // Just a reference ground, not power[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//  printer.begin();[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//  printer.sleep();[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco] [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]Serial.print(F("Initializing Emic..."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  pinMode(emic2RxPin, INPUT);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  pinMode(emic2TxPin, OUTPUT);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  emic2Serial.begin(9600);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.init();[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.setVolume(8);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.setWordsPerMinute(120);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.setVoice(PerfectPaul);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("OK"));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Initialize Ethernet connection.  Request dynamic[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // IP address, fall back on fixed IP if that fails:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("Initializing Ethernet..."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  emic2TtsModule.say(F("Hello, my name is Tweet o' Phone."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  if(Ethernet.begin(mac)) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("OK\r\n"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//    emic2TtsModule.say(F("OK."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("\r\nno DHCP response, using static IP address."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//    emic2TtsModule.say(F("No DHCP response, using static IP address."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    Ethernet.begin(mac, ip);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Get initial time from time server (make a few tries if needed)[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  for(uint8_t i=0; (i<5) && !(currentTime = getTime()); delay(15000L), i++);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Clear all string data[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  strcpy_P(lastId, PSTR("1"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  timeStamp[0] = fromUser[0] = msgText[0] = name[0] = value[0] = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Search occurs in loop. ---------------------------------------------------[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]void loop() {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  uint8_t                  *in, out, i;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  char                      nonce[9],       // 8 random digits + NUL[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]                            searchTime[11], // 32-bit int + NUL[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]                            b64[29];[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  unsigned long             startTime, t;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  static const char PROGMEM b64chars[] =[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  startTime = millis();[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Disable Timer1 interrupt during network access, else there's trouble.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Just show LED at steady 100% while working.  :T[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  TIMSK1 &= ~_BV(TOIE1);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  analogWrite(led_pin, 255);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Initialize unique values for query[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  sprintf(nonce, "%04x%04x", random() ^ currentTime, startTime ^ currentTime);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  sprintf(searchTime, "%ld", currentTime);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Some debugging/testing/status stuff[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("  Current time: "));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Serial.println(currentTime);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("  Last ID: "));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Serial.println(lastId);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Serial.println(msgText[141]);[/FONT][/COLOR]
    
    
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.initHmac_P((uint8_t *)signingKey, sizeof(signingKey) - 1);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // A dirty hack makes the Oauth song and dance MUCH simpler within the[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Arduino's limited RAM and CPU.  A proper general-purpose implementation[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // would be expected to URL-encode keys and values (from the complete list[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // of GET parameters and authentication values), sort by encoded key,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // concatenate and URL-encode the combined result.  Sorting is avoided[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // because every query this program performs has exactly the same set of[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // parameters, so we've pre-sorted the list as it appears here.  Keys[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // (and many values) are pre-encoded because they never change; many are[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // passed through verbatim because the format is known to not require[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // encoding.  Most reside in PROGMEM, not RAM.  This is bending a LOT of[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // rules of Good and Proper Authentication and would land you an 'F' in[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Comp Sci class, but it handles the required task and is VERY compact.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("GET&http%3A%2F%2F"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(host);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  urlEncode(Sha1, endpoint, true, false);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("&count%3D"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(maxTweets);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26include_entities%3D0%26oauth_consumer_key%3D"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F2(consumer_key));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26oauth_nonce%3D"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(nonce);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(searchTime);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26oauth_token%3D"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F2(access_token));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26oauth_version%3D1.0%26q%3D"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  urlEncode(Sha1, queryString, true, true);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(F("%26since_id%3D"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Sha1.print(lastId);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // base64-encode SHA-1 hash output.  This is NOT a general-purpose base64[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // encoder!  It's stripped down for the fixed-length hash -- always 20[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // bytes input, always 27 chars output + '='.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  for(in = Sha1.resultHmac(), out=0; ; in += 3) { // octets to sextets[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    b64[out++] =   in[0] >> 2;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    b64[out++] = ((in[0] & 0x03) << 4) | (in[1] >> 4);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if(out >= 26) break;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    b64[out++] = ((in[1] & 0x0f) << 2) | (in[2] >> 6);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    b64[out++] =   in[2] & 0x3f;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  b64[out] = (in[1] & 0x0f) << 2;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Remap sextets to base64 ASCII chars[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  for(i=0; i<=out; i++) b64[i] = pgm_read_byte(&b64chars[b64[i]]);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  b64[i++] = '=';[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  b64[i++] = 0;[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("Connecting to server..."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  t = millis();[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  while((client.connect(host, 80) == false) &&[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    ((millis() - t) < connectTimeout));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  if(client.connected()) { // Success![/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("OK\r\nIssuing HTTP request..."));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    // Unlike the hash prep, parameters in the HTTP request don't require[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    // sorting, but are still somewhat ordered by function: GET parameters[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    // (search values), HTTP headers and Oauth credentials.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("GET "));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F2(endpoint));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("?count="));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(maxTweets);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("&since_id="));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(lastId);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("&include_entities=0&q="));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    urlEncode(client, queryString, true, false);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F(" HTTP/1.1\r\nHost: "));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(host);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("\r\nUser-Agent: "));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F2(agent));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("\r\nConnection: close\r\n"[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]                       "Content-Type: application/x-www-form-urlencoded;charset=UTF-8\r\n"[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]                       "Authorization: Oauth oauth_consumer_key=\""));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F2(consumer_key));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_nonce=\""));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(nonce);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_signature=\""));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    urlEncode(client, b64, false, false);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\""));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(searchTime);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_token=\""));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F2(access_token));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    client.print(F("\", oauth_version=\"1.0\"\r\n\r\n"));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("OK\r\nAwaiting results (if any)..."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    t = millis();[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    while((!client.available()) && ((millis() - t) < responseTimeout));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if(client.available()) { // Response received?[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      // Could add HTTP response header parsing here (400, etc.)[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if(client.find("\r\n\r\n")) { // Skip HTTP response header[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        Serial.print(F("OK\r\nProcessing results...\r\n"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        resultsDepth = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        jsonParse(0, 0);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else Serial.print(F("response not recognized.\r\n"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else   Serial.print(F("connection timed out.\r\n"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("Done.\r\n"));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    client.stop();[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  } else { // Couldn't contact server[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("failed\r\n"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Update time in seconds.  Once per day, re-sync with time server[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  currentTime += pollingInterval / 1000L;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  if((++searchCount >= searchesPerDay) && (t = getTime())) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    currentTime = t;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    searchCount = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Sometimes network access & printing occurrs so quickly, the steady-on[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // LED wouldn't even be apparent, instead resembling a discontinuity in[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // the otherwise smooth sleep throb.  Keep it on at least 4 seconds.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  while((millis() - startTime) < 4000UL);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Pause between queries, factoring in time already spent on network[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // access, parsing, printing and LED pause above.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  if((millis() - startTime) < pollingInterval) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("Pausing..."));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    sleepPos = sizeof(sleepTab); // Resume following brightest position[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    TIMSK1 |= _BV(TOIE1); // Re-enable Timer1 interrupt for sleep throb[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    while((millis() - startTime) < pollingInterval);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    Serial.print(F("done\r\n"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Helper functions. --------------------------------------------------------[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]boolean jsonParse(int depth, byte endChar) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  int     c, i;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  boolean readName = true;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  for(;;) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    while(isspace(c = timedRead())); // Scan past whitespace[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if(c < 0)        return false;   // Timeout[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if(c == endChar) return true;    // EOD[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    if(c == '{') { // Object follows[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if(!jsonParse(depth + 1, '}')) return false;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if(!depth)                     return true; // End of file[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if(depth == resultsDepth) { // End of object in results list[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        // Dump to serial console as well[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//        Serial.print(F("  User: "));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//        Serial.println(fromUser);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//        Serial.print(F(" "));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        Serial.println(msgText);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//        Serial.print(F("  Time: "));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]//        Serial.println(timeStamp);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Output to Emic 2[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        emic2TtsModule.say(msgText);[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]        // Clear strings for next object[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        timeStamp[0] = fromUser[0] = msgText[0] = 0;[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]        maxTweets = 1; // After first, subsequent queries allow more tweets[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if(c == '[') { // Array follows[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if((!resultsDepth) && (!strcasecmp(name, "statuses")))[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        resultsDepth = depth + 1;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if(!jsonParse(depth + 1,']')) return false;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if((c == '"') || (c == '\'')) { // String follows[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if(readName) { // Name-reading mode[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        if(!readString(name, sizeof(name)-1, c)) return false;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else { // Value-reading mode[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        if(!readString(value, sizeof(value)-1, c)) return false;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        // Process name and value strings:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        if       (!strcasecmp(name, "max_id_str")) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          strncpy(lastId, value, sizeof(lastId)-1);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else if(!strcasecmp(name, "created_at")) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          // Use created_at value for tweet, not user[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          if(depth == (resultsDepth + 1)) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]            strncpy(timeStamp, value, sizeof(timeStamp)-1);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else if(!strcasecmp(name, "screen_name")) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          strncpy(fromUser, value, sizeof(fromUser)-1);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else if(!strcasecmp(name, "text")) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          strncpy(msgText, value, sizeof(msgText)-1);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else if((!strcasecmp(name, "id_str")) &&[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]                  (strcasecmp(value, lastId) > 0) &&[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]                  (depth == (resultsDepth + 1))) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          strncpy(lastId, value, sizeof(lastId)-1);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if(c == ':') { // Separator between name:value[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      readName = false; // Now in value-reading mode[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      value[0] = 0;     // Clear existing value data[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if(c == ',') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      // Separator between name:value pairs.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      readName = true; // Now in name-reading mode[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      name[0]  = 0;    // Clear existing name data[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } // Else true/false/null or a number follows.  These values aren't[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      // used or expected by this program, so just ignore...either a comma[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      // or endChar will come along eventually, these are handled above.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Read string from client stream into destination buffer, up to a maximum[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// requested length.  Buffer should be at least 1 byte larger than this to[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// accommodate NUL terminator.  Opening quote is assumed already read,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// closing quote will be discarded, and stream will be positioned[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// immediately following the closing quote (regardless whether max length[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// is reached -- excess chars are discarded).  Returns true on success[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// (including zero-length string), false on timeout/read error.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]boolean readString(char *dest, int maxLen, char quote) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  int c, len = 0, link_buffer_idx;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  boolean done = false, read_next_char = true;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  char link_buffer[10];[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  ParseState state = STATE_NORMAL;[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  while(!done) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    // Handle cases where a character needs to be parsed twice.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if (read_next_char) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      c = timedRead();[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      read_next_char = true;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    // Exit if current character is the closing quote.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if (c == '"') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      done = true;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      break;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if(c == '\\') {    // Escaped char follows[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      c = timedRead(); // Read it[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      // Certain escaped values are for cursor control --[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      // there might be more suitable printer codes for each.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if     (c == 'b') c = '\b'; // Backspace[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      else if(c == 'f') c = '\f'; // Form feed[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      else if(c == 'n') c = '\n'; // Newline[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      else if(c == 'r') c = '\r'; // Carriage return[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      else if(c == 't') c = '\t'; // Tab[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      else if(c == 'u') c = unidecode(4);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      else if(c == 'U') c = unidecode(8);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      // else c is unaltered -- an escaped char such as \ or "[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } // else c is a normal unescaped char[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    // Special handling for http links & special characters #, :, /, (, ), *[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if (state == STATE_NORMAL) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c == 'h') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_H;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        memset(link_buffer, 0, sizeof(link_buffer));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        link_buffer_idx = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        // Special handling for certain characters[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        if (c == '#') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " hash \0");[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else if (c == ':') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " colon \0");[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else if (c == '/') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " slash \0");[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else if (c == '(' || c == ')') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " parenthesis \0");[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else if (c == '*') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          len = writeStringIfPossible(len, maxLen, dest, " star \0");[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          // Handle timeout[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          if (c < 0) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]            dest[len] = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]            return false;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]          }[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    // In order to properly position the client stream at the end of[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    // the string, characters are read to the end quote, even if the max[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    // string length is reached...the extra chars are simply discarded.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if(len < maxLen) dest[len++] = c;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_H) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c == 't') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HT;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HT) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c == 't') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTT;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTT) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c == 'p') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTP;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTTP) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c == ':') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTP_COLON;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else if (c == 's') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTPS;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTTPS) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c == ':') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTP_COLON;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTTP_COLON) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c == '/') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_HTTP_COLON_SLASH;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_HTTP_COLON_SLASH) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c == '/') {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FOUND;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_FALSE_POSITIVE;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else if (state == STATE_LINK_FOUND) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if (c != '.' && c != '/' && !isalnum(c)) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_LINK_DONE;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    switch (state) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      case STATE_NORMAL:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      case STATE_LINK_FOUND:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        // Do nothing.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        break;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      case STATE_LINK_FALSE_POSITIVE:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        len = writeStringIfPossible(len, maxLen, dest, link_buffer);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        read_next_char = false;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_NORMAL;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        break;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      case STATE_LINK_DONE:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        len = writeStringIfPossible(len, maxLen, dest, " link \0");[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        read_next_char = false;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        state = STATE_NORMAL;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        break;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      default:[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        // Save current character in case of false positive.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        link_buffer[link_buffer_idx++] = c;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]        break;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  dest[len] = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  return true; // Success (even if empty string)[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Write a passed in string out to the destination buffer if possible, advancing the[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// length as needed and returning it to the caller.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]int writeStringIfPossible(int len, int maxLen, char *dest, char *str) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  int str_idx = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  [/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  while (str[str_idx] != '\0' && len < maxLen) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    dest[len++] = str[str_idx++];[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  return len;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// Read a given number of hexadecimal characters from client stream,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// representing a Unicode symbol.  Return -1 on error, else return nearest[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// equivalent glyph in printer's charset.  (See notes below -- for now,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// always returns '-' or -1.)[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]int unidecode(byte len) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  int c, v, result = 0;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  while(len--) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if((c = timedRead()) < 0) return -1; // Stream timeout[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if     ((c >= '0') && (c <= '9')) v =      c - '0';[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    else if((c >= 'A') && (c <= 'F')) v = 10 + c - 'A';[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    else if((c >= 'a') && (c <= 'f')) v = 10 + c - 'a';[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    else return '-'; // garbage[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    result = (result << 4) | v;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // To do: some Unicode symbols may have equivalents in the printer's[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // native character set.  Remap any such result values to corresponding[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // printer codes.  Until then, all Unicode symbols are returned as '-'.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // (This function still serves an interim purpose in skipping a given[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // number of hex chars while watching for timeouts or malformed input.)[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  return '-';[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Read from client stream with a 5 second timeout.  Although an[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// essentially identical method already exists in the Stream() class,[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// it's declared private there...so this is a local copy.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]int timedRead(void) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  unsigned long start = millis();[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  while((!client.available()) && ((millis() - start) < 5000L));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  return client.read();  // -1 on timeout[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// URL-encoding output function for Print class.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// Input from RAM or PROGMEM (flash).  Double-encoding is a weird special[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]// case for Oauth (encoded strings get encoded a second time).[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]void urlEncode([/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  Print      &p,       // EthernetClient, Sha1, etc.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  const char *src,     // String to be encoded[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  boolean     progmem, // If true, string is in PROGMEM (else RAM)[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  boolean     x2)      // If true, "double encode" parenthesis[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]{[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  static const char PROGMEM hexChar[] = "0123456789ABCDEF";[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  uint8_t c;[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  while((c = (progmem ? pgm_read_byte(src) : *src))) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if(((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]       ((c >= '0') && (c <= '9')) || strchr_P(PSTR("-_.~"), c)) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      p.write(c);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    } else {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      if(x2) p.print("%25");[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      else   p.write('%');[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      p.write(pgm_read_byte(&hexChar[c >> 4]));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      p.write(pgm_read_byte(&hexChar[c & 15]));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    src++;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Minimalist time server query; adapted from Arduino UdpNTPClient tutorial.[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]unsigned long getTime(void) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  EthernetUDP   udp;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  DNSClient     dns;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  IPAddress     addr;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  byte          buf[48];[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  unsigned long t = 0L;[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  Serial.print(F("Polling time server..."));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  udp.begin(8888);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  dns.begin(Ethernet.dnsServerIP());[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  // Get a time server address from NTP pool[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  if(dns.getHostByName("pool.ntp.org", addr)) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    static const char PROGMEM[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      timeReqA[] = { 227,  0,  6, 236 },[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      timeReqB[] = {  49, 78, 49,  52 };[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    // Assemble and issue request packet[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    memset(buf, 0, sizeof(buf));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    memcpy_P( buf    , timeReqA, sizeof(timeReqA));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    memcpy_P(&buf[12], timeReqB, sizeof(timeReqB));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    udp.beginPacket(addr, 123);[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    udp.write(buf, sizeof(buf));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    udp.endPacket();[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]    delay(1000); // Allow time for response[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    if(udp.parsePacket()) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      // Read result, convert to UNIX time format[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      udp.read(buf, sizeof(buf));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      t = (((unsigned long)buf[40] << 24) |[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]           ((unsigned long)buf[41] << 16) |[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]           ((unsigned long)buf[42] <<  8) |[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]            (unsigned long)buf[43]) - 2208988800UL;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]      Serial.print(F("OK\r\n"));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  }[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  udp.stop();[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  if(!t) Serial.print(F("error\r\n"));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  return t;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Timer1 interrupt handler for sleep throb[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]ISR(TIMER1_OVF_vect, ISR_NOBLOCK) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Sine table contains only first half...reflect for second half...[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  analogWrite(led_pin, pgm_read_byte(&sleepTab[[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    (sleepPos >= sizeof(sleepTab)) ?[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    (sizeof(sleepTab) * 2 - 1 - sleepPos) : sleepPos]));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  if(++sleepPos >= (sizeof(sleepTab) * 2)) sleepPos = 0; // Roll over[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  TIFR1 |= TOV1; // Clear Timer1 interrupt flag[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  if(!t) Serial.print(F("error\r\n"));[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]  return t;[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR]
    
    [COLOR=#2E8B57][FONT=Monaco]// Timer1 interrupt handler for sleep throb[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]ISR(TIMER1_OVF_vect, ISR_NOBLOCK) {[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  // Sine table contains only first half...reflect for second half...[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  analogWrite(led_pin, pgm_read_byte(&sleepTab[[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    (sleepPos >= sizeof(sleepTab)) ?[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]    (sizeof(sleepTab) * 2 - 1 - sleepPos) : sleepPos]));[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  if(++sleepPos >= (sizeof(sleepTab) * 2)) sleepPos = 0; // Roll over[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]  TIFR1 |= TOV1; // Clear Timer1 interrupt flag[/FONT][/COLOR]
    [COLOR=#2E8B57][FONT=Monaco]}[/FONT][/COLOR][COLOR=#000000][FONT=verdana]
    
  • SakuraTotSakuraTot Posts: 4
    edited 2014-01-06 08:55
    Hello everybody,

    I am new in Arduino family but also in this forum!! I have a problem with a project I am working on with emic 2 and an ethernet Shield. I want to make emic reading tweets. This project is similar with Social chatter (http://www.adafruit.com/blog/2012/08/16 ... ad-tweets/). The problem is that the twitter API has changed so now I have to combine the code form socialChatter and thermalPrinter. I have done this very carefully but emic 2 doesn't read the first tweet that is appeared on serial monitor and everything stops there. Also if I place emic2TtsModule.say(msgText); for example before Serial.print(F("OK\r\nIssuing HTTP request...")); twitter's operation stops again there (before Issuing HTTP request...)… Any idea why emic 2 stops the whole operation? It would be very helpful!!!

    Thanks!!

    ps. Here is the code I have done!
    #include <SPI.h>
    #include <Ethernet.h>
    #include <EthernetUdp.h>
    #include <Dns.h>
    #include <sha1.h>
    #include <SoftwareSerial.h>
    #include <Emic2TtsModule.h>
    
    // Stream parse states
    enum ParseState {
     STATE_NORMAL,
     STATE_LINK_H,
     STATE_LINK_HT,
     STATE_LINK_HTT,
     STATE_LINK_HTTP,
     STATE_LINK_HTTPS,
     STATE_LINK_HTTP_COLON,
     STATE_LINK_HTTP_COLON_SLASH,
     STATE_LINK_FOUND,
     STATE_LINK_FALSE_POSITIVE,
     STATE_LINK_DONE
    };
    
    // Similar to F(), but for PROGMEM string pointers rather than literals
    #define F2(progmem_ptr) (const __FlashStringHelper *)progmem_ptr
    
    // Configurable globals.  Edit to your needs. -------------------------------
    
    const char PROGMEM
     // Twitter application credentials -- see notes above -- DO NOT SHARE.
    consumer_key[]  = "PUT_YOUR_CONSUMER_KEY_HERE",//PUT_YOUR_CONSUMER_KEY_HERE
     access_token[]  = "PUT_YOUR_ACCESS_TOKEN_HERE",//PUT_YOUR_ACCESS_TOKEN_HERE
     signingKey[]    = "Consumer secret" // Consumer secret
       "&"             "Access token secret", // Access token secret
     // The ampersand is intentional -- do not delete!
    
     // queryString can be any valid Twitter API search string, including
     // boolean operators.  See http://dev.twitter.com/docs/using-search
     // for options and syntax.  Funny characters do NOT need to be URL
     // encoded here -- the code takes care of that.
     queryString[]   = "Christmas",
    
    // Other globals.  You probably won't need to change these. -----------------
    
     endpoint[]      = "/1.1/search/tweets.json",
     agent[]         = "Gutenbird v1.0";
    const char
     host[]          = "api.twitter.com";
    const int led_pin         = 1;           // To status LED (hardware PWM pin)
    // Emic 2 Globals
    const int emic2RxPin = 11;
    const int emic2TxPin = 12;
    SoftwareSerial emic2Serial = SoftwareSerial(emic2RxPin, emic2TxPin);
    Emic2TtsModule emic2TtsModule = Emic2TtsModule(&emic2Serial);
    
    const unsigned long
     pollingInterval = 60L * 1000L, // Note: Twitter server will allow 150/hr max
     searchesPerDay  = 86400000L / pollingInterval,
     connectTimeout  = 15L * 1000L, // Max time to wait for server connection
     responseTimeout = 15L * 1000L; // Max time to wait for data from server
    //Adafruit_Thermal
    //  printer(printer_RX_Pin, printer_TX_Pin);
    byte
     maxTweets = 3, // One tweet on first run; avoid runaway output
     sleepPos  = 0, // Current "sleep throb" table position
     resultsDepth,  // Used in JSON parsing
     // Ethernet MAC address is found on sticker on Ethernet shield or board:
     mac[] = { 0x90, 0xA2, 0xWA, 0x0E, 0xEB, 0x43 };
    IPAddress
     ip(192,168,0,118); // Fallback address -- code will try DHCP first
    char
     lastId[21],    // 18446744073709551615\0 (64-bit maxint as string)
     timeStamp[32], // WWW, DD MMM YYYY HH:MM:SS +XXXX\0
     fromUser[16],  // Max username length (15) + \0
     msgText[141],  // Max tweet length (140) + \0
     name[12],      // Temp space for name:value parsing
     value[141];    // Temp space for name:value parsing
    int
     searchCount = 0;
    unsigned long
     currentTime = 0L;
    EthernetClient
     client;
    
     // Function prototypes -------------------------------------------------------
    boolean jsonParse(int, byte);
    boolean readString(char *, int, char);
    int writeStringIfPossible(int, int, char *, char *);
    int unidecode(byte);
    int timedRead(void);
    
    PROGMEM byte
     sleepTab[] = { // "Sleep throb" brightness table (reverse for second half)
         0,   0,   0,   0,   0,   0,   0,   0,   0,   1,
         1,   1,   2,   3,   4,   5,   6,   8,  10,  13,
        15,  19,  22,  26,  31,  36,  41,  47,  54,  61,
        68,  76,  84,  92, 101, 110, 120, 129, 139, 148,
       158, 167, 177, 186, 194, 203, 211, 218, 225, 232,
       237, 242, 246, 250, 252, 254, 255 };
    
    // --------------------------------------------------------------------------
    
    void setup() {
    
     // Set up LED "sleep throb" ASAP, using Timer1 interrupt:
     TCCR1A  = _BV(WGM11); // Mode 14 (fast PWM), 64:1 prescale, OC1A off
     TCCR1B  = _BV(WGM13) | _BV(WGM12) | _BV(CS11) | _BV(CS10);
     ICR1    = 8333;       // ~30 Hz between sleep throb updates
     TIMSK1 |= _BV(TOIE1); // Enable Timer1 interrupt
     sei();                // Enable global interrupts
    
     randomSeed(analogRead(0));
     Serial.begin(9600);
    //  pinMode(printer_Ground, OUTPUT);
    //  digitalWrite(printer_Ground, LOW); // Just a reference ground, not power
    //  printer.begin();
    //  printer.sleep();
    
    
    Serial.print(F("Initializing Emic..."));
     pinMode(emic2RxPin, INPUT);
     pinMode(emic2TxPin, OUTPUT);
     emic2Serial.begin(9600);
     emic2TtsModule.init();
     emic2TtsModule.setVolume(8);
     emic2TtsModule.setWordsPerMinute(120);
     emic2TtsModule.setVoice(PerfectPaul);
     Serial.print(F("OK"));
    
     // Initialize Ethernet connection.  Request dynamic
     // IP address, fall back on fixed IP if that fails:
     Serial.print(F("Initializing Ethernet..."));
     emic2TtsModule.say(F("Hello, my name is Tweet o' Phone."));
     if(Ethernet.begin(mac)) {
       Serial.print(F("OK\r\n"));
    //    emic2TtsModule.say(F("OK."));
     } else {
       Serial.print(F("\r\nno DHCP response, using static IP address."));
    //    emic2TtsModule.say(F("No DHCP response, using static IP address."));
       Ethernet.begin(mac, ip);
     }
    
     // Get initial time from time server (make a few tries if needed)
     for(uint8_t i=0; (i<5) && !(currentTime = getTime()); delay(15000L), i++);
    
     // Clear all string data
     strcpy_P(lastId, PSTR("1"));
     timeStamp[0] = fromUser[0] = msgText[0] = name[0] = value[0] = 0;
    }
    
    // Search occurs in loop. ---------------------------------------------------
    
    void loop() {
     uint8_t                  *in, out, i;
     char                      nonce[9],       // 8 random digits + NUL
                               searchTime[11], // 32-bit int + NUL
                               b64[29];
     unsigned long             startTime, t;
     static const char PROGMEM b64chars[] =
       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
     startTime = millis();
    
     // Disable Timer1 interrupt during network access, else there's trouble.
     // Just show LED at steady 100% while working.  :T
     TIMSK1 &= ~_BV(TOIE1);
     analogWrite(led_pin, 255);
    
     // Initialize unique values for query
     sprintf(nonce, "%04x%04x", random() ^ currentTime, startTime ^ currentTime);
     sprintf(searchTime, "%ld", currentTime);
    
     // Some debugging/testing/status stuff
     Serial.print(F("  Current time: "));
     Serial.println(currentTime);
     Serial.print(F("  Last ID: "));
     Serial.println(lastId);
     Serial.println(msgText[141]);
    
    
     Sha1.initHmac_P((uint8_t *)signingKey, sizeof(signingKey) - 1);
    
     // A dirty hack makes the Oauth song and dance MUCH simpler within the
     // Arduino's limited RAM and CPU.  A proper general-purpose implementation
     // would be expected to URL-encode keys and values (from the complete list
     // of GET parameters and authentication values), sort by encoded key,
     // concatenate and URL-encode the combined result.  Sorting is avoided
     // because every query this program performs has exactly the same set of
     // parameters, so we've pre-sorted the list as it appears here.  Keys
     // (and many values) are pre-encoded because they never change; many are
     // passed through verbatim because the format is known to not require
     // encoding.  Most reside in PROGMEM, not RAM.  This is bending a LOT of
     // rules of Good and Proper Authentication and would land you an 'F' in
     // Comp Sci class, but it handles the required task and is VERY compact.
     Sha1.print(F("GET&http%3A%2F%2F"));
     Sha1.print(host);
     urlEncode(Sha1, endpoint, true, false);
     Sha1.print(F("&count%3D"));
     Sha1.print(maxTweets);
     Sha1.print(F("%26include_entities%3D0%26oauth_consumer_key%3D"));
     Sha1.print(F2(consumer_key));
     Sha1.print(F("%26oauth_nonce%3D"));
     Sha1.print(nonce);
     Sha1.print(F("%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D"));
     Sha1.print(searchTime);
     Sha1.print(F("%26oauth_token%3D"));
     Sha1.print(F2(access_token));
     Sha1.print(F("%26oauth_version%3D1.0%26q%3D"));
     urlEncode(Sha1, queryString, true, true);
     Sha1.print(F("%26since_id%3D"));
     Sha1.print(lastId);
    
     // base64-encode SHA-1 hash output.  This is NOT a general-purpose base64
     // encoder!  It's stripped down for the fixed-length hash -- always 20
     // bytes input, always 27 chars output + '='.
     for(in = Sha1.resultHmac(), out=0; ; in += 3) { // octets to sextets
       b64[out++] =   in[0] >> 2;
       b64[out++] = ((in[0] & 0x03) << 4) | (in[1] >> 4);
       if(out >= 26) break;
       b64[out++] = ((in[1] & 0x0f) << 2) | (in[2] >> 6);
       b64[out++] =   in[2] & 0x3f;
     }
     b64[out] = (in[1] & 0x0f) << 2;
     // Remap sextets to base64 ASCII chars
     for(i=0; i<=out; i++) b64[i] = pgm_read_byte(&b64chars[b64[i]]);
     b64[i++] = '=';
     b64[i++] = 0;
    
     Serial.print(F("Connecting to server..."));
     t = millis();
     while((client.connect(host, 80) == false) &&
       ((millis() - t) < connectTimeout));
    
     if(client.connected()) { // Success!
       Serial.print(F("OK\r\nIssuing HTTP request..."));
    
       // Unlike the hash prep, parameters in the HTTP request don't require
       // sorting, but are still somewhat ordered by function: GET parameters
       // (search values), HTTP headers and Oauth credentials.
       client.print(F("GET "));
       client.print(F2(endpoint));
       client.print(F("?count="));
       client.print(maxTweets);
       client.print(F("&since_id="));
       client.print(lastId);
       client.print(F("&include_entities=0&q="));
       urlEncode(client, queryString, true, false);
       client.print(F(" HTTP/1.1\r\nHost: "));
       client.print(host);
       client.print(F("\r\nUser-Agent: "));
       client.print(F2(agent));
       client.print(F("\r\nConnection: close\r\n"
                          "Content-Type: application/x-www-form-urlencoded;charset=UTF-8\r\n"
                          "Authorization: Oauth oauth_consumer_key=\""));
       client.print(F2(consumer_key));
       client.print(F("\", oauth_nonce=\""));
       client.print(nonce);
       client.print(F("\", oauth_signature=\""));
       urlEncode(client, b64, false, false);
       client.print(F("\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\""));
       client.print(searchTime);
       client.print(F("\", oauth_token=\""));
       client.print(F2(access_token));
       client.print(F("\", oauth_version=\"1.0\"\r\n\r\n"));
    
       Serial.print(F("OK\r\nAwaiting results (if any)..."));
       t = millis();
       while((!client.available()) && ((millis() - t) < responseTimeout));
       if(client.available()) { // Response received?
         // Could add HTTP response header parsing here (400, etc.)
         if(client.find("\r\n\r\n")) { // Skip HTTP response header
           Serial.print(F("OK\r\nProcessing results...\r\n"));
           resultsDepth = 0;
           jsonParse(0, 0);
         } else Serial.print(F("response not recognized.\r\n"));
       } else   Serial.print(F("connection timed out.\r\n"));
       Serial.print(F("Done.\r\n"));
    
       client.stop();
     } else { // Couldn't contact server
       Serial.print(F("failed\r\n"));
     }
    
     // Update time in seconds.  Once per day, re-sync with time server
     currentTime += pollingInterval / 1000L;
     if((++searchCount >= searchesPerDay) && (t = getTime())) {
       currentTime = t;
       searchCount = 0;
     }
    
     // Sometimes network access & printing occurrs so quickly, the steady-on
     // LED wouldn't even be apparent, instead resembling a discontinuity in
     // the otherwise smooth sleep throb.  Keep it on at least 4 seconds.
     while((millis() - startTime) < 4000UL);
    
     // Pause between queries, factoring in time already spent on network
     // access, parsing, printing and LED pause above.
     if((millis() - startTime) < pollingInterval) {
       Serial.print(F("Pausing..."));
       sleepPos = sizeof(sleepTab); // Resume following brightest position
       TIMSK1 |= _BV(TOIE1); // Re-enable Timer1 interrupt for sleep throb
       while((millis() - startTime) < pollingInterval);
       Serial.print(F("done\r\n"));
     }
    }
    
    // Helper functions. --------------------------------------------------------
    
    boolean jsonParse(int depth, byte endChar) {
     int     c, i;
     boolean readName = true;
     for(;;) {
       while(isspace(c = timedRead())); // Scan past whitespace
       if(c < 0)        return false;   // Timeout
       if(c == endChar) return true;    // EOD
    
       if(c == '{') { // Object follows
         if(!jsonParse(depth + 1, '}')) return false;
         if(!depth)                     return true; // End of file
         if(depth == resultsDepth) { // End of object in results list
    
           // Dump to serial console as well
    //        Serial.print(F("  User: "));
    //        Serial.println(fromUser);
    //        Serial.print(F(" "));
           Serial.println(msgText);
    
    //        Serial.print(F("  Time: "));
    //        Serial.println(timeStamp);
    
    // Output to Emic 2
           emic2TtsModule.say(msgText);
    
           // Clear strings for next object
           timeStamp[0] = fromUser[0] = msgText[0] = 0;
    
           maxTweets = 1; // After first, subsequent queries allow more tweets
         }
       } else if(c == '[') { // Array follows
         if((!resultsDepth) && (!strcasecmp(name, "statuses")))
           resultsDepth = depth + 1;
         if(!jsonParse(depth + 1,']')) return false;
       } else if((c == '"') || (c == '\'')) { // String follows
         if(readName) { // Name-reading mode
           if(!readString(name, sizeof(name)-1, c)) return false;
         } else { // Value-reading mode
           if(!readString(value, sizeof(value)-1, c)) return false;
           // Process name and value strings:
           if       (!strcasecmp(name, "max_id_str")) {
             strncpy(lastId, value, sizeof(lastId)-1);
           } else if(!strcasecmp(name, "created_at")) {
             // Use created_at value for tweet, not user
             if(depth == (resultsDepth + 1)) {
               strncpy(timeStamp, value, sizeof(timeStamp)-1);
             }
           } else if(!strcasecmp(name, "screen_name")) {
             strncpy(fromUser, value, sizeof(fromUser)-1);
           } else if(!strcasecmp(name, "text")) {
             strncpy(msgText, value, sizeof(msgText)-1);
           } else if((!strcasecmp(name, "id_str")) &&
                     (strcasecmp(value, lastId) > 0) &&
                     (depth == (resultsDepth + 1))) {
             strncpy(lastId, value, sizeof(lastId)-1);
           }
         }
       } else if(c == ':') { // Separator between name:value
         readName = false; // Now in value-reading mode
         value[0] = 0;     // Clear existing value data
       } else if(c == ',') {
         // Separator between name:value pairs.
         readName = true; // Now in name-reading mode
         name[0]  = 0;    // Clear existing name data
       } // Else true/false/null or a number follows.  These values aren't
         // used or expected by this program, so just ignore...either a comma
         // or endChar will come along eventually, these are handled above.
     }
    }
    
    // Read string from client stream into destination buffer, up to a maximum
    // requested length.  Buffer should be at least 1 byte larger than this to
    // accommodate NUL terminator.  Opening quote is assumed already read,
    // closing quote will be discarded, and stream will be positioned
    // immediately following the closing quote (regardless whether max length
    // is reached -- excess chars are discarded).  Returns true on success
    // (including zero-length string), false on timeout/read error.
    boolean readString(char *dest, int maxLen, char quote) {
     int c, len = 0, link_buffer_idx;
     boolean done = false, read_next_char = true;
     char link_buffer[10];
     ParseState state = STATE_NORMAL;
    
     while(!done) {
       // Handle cases where a character needs to be parsed twice.
       if (read_next_char) {
         c = timedRead();
       } else {
         read_next_char = true;
       }
    
       // Exit if current character is the closing quote.
       if (c == '"') {
         done = true;
         break;
       }
       if(c == '\\') {    // Escaped char follows
         c = timedRead(); // Read it
         // Certain escaped values are for cursor control --
         // there might be more suitable printer codes for each.
         if     (c == 'b') c = '\b'; // Backspace
         else if(c == 'f') c = '\f'; // Form feed
         else if(c == 'n') c = '\n'; // Newline
         else if(c == 'r') c = '\r'; // Carriage return
         else if(c == 't') c = '\t'; // Tab
         else if(c == 'u') c = unidecode(4);
         else if(c == 'U') c = unidecode(8);
         // else c is unaltered -- an escaped char such as \ or "
       } // else c is a normal unescaped char
    
       // Special handling for http links & special characters #, :, /, (, ), *
       if (state == STATE_NORMAL) {
         if (c == 'h') {
           state = STATE_LINK_H;
           memset(link_buffer, 0, sizeof(link_buffer));
           link_buffer_idx = 0;
         } else {
           // Special handling for certain characters
           if (c == '#') {
             len = writeStringIfPossible(len, maxLen, dest, " hash \0");
           } else if (c == ':') {
             len = writeStringIfPossible(len, maxLen, dest, " colon \0");
           } else if (c == '/') {
             len = writeStringIfPossible(len, maxLen, dest, " slash \0");
           } else if (c == '(' || c == ')') {
             len = writeStringIfPossible(len, maxLen, dest, " parenthesis \0");
           } else if (c == '*') {
             len = writeStringIfPossible(len, maxLen, dest, " star \0");
           } else {
             // Handle timeout
             if (c < 0) {
               dest[len] = 0;
               return false;
             }
    
       // In order to properly position the client stream at the end of
       // the string, characters are read to the end quote, even if the max
       // string length is reached...the extra chars are simply discarded.
       if(len < maxLen) dest[len++] = c;
     }
    }
       } else if (state == STATE_LINK_H) {
         if (c == 't') {
           state = STATE_LINK_HT;
         } else {
           state = STATE_LINK_FALSE_POSITIVE;
         }
       } else if (state == STATE_LINK_HT) {
         if (c == 't') {
           state = STATE_LINK_HTT;
         } else {
           state = STATE_LINK_FALSE_POSITIVE;
         }
       } else if (state == STATE_LINK_HTT) {
         if (c == 'p') {
           state = STATE_LINK_HTTP;
         } else {
           state = STATE_LINK_FALSE_POSITIVE;
         }
       } else if (state == STATE_LINK_HTTP) {
         if (c == ':') {
           state = STATE_LINK_HTTP_COLON;
         } else if (c == 's') {
           state = STATE_LINK_HTTPS;
         } else {
           state = STATE_LINK_FALSE_POSITIVE;
         }
       } else if (state == STATE_LINK_HTTPS) {
         if (c == ':') {
           state = STATE_LINK_HTTP_COLON;
         } else {
           state = STATE_LINK_FALSE_POSITIVE;
         }
       } else if (state == STATE_LINK_HTTP_COLON) {
         if (c == '/') {
           state = STATE_LINK_HTTP_COLON_SLASH;
         } else {
           state = STATE_LINK_FALSE_POSITIVE;
         }
       } else if (state == STATE_LINK_HTTP_COLON_SLASH) {
         if (c == '/') {
           state = STATE_LINK_FOUND;
         } else {
           state = STATE_LINK_FALSE_POSITIVE;
         }
       } else if (state == STATE_LINK_FOUND) {
         if (c != '.' && c != '/' && !isalnum(c)) {
           state = STATE_LINK_DONE;
         }
       }
    
       switch (state) {
         case STATE_NORMAL:
         case STATE_LINK_FOUND:
           // Do nothing.
           break;
    
         case STATE_LINK_FALSE_POSITIVE:
           len = writeStringIfPossible(len, maxLen, dest, link_buffer);
           read_next_char = false;
           state = STATE_NORMAL;
           break;
    
         case STATE_LINK_DONE:
           len = writeStringIfPossible(len, maxLen, dest, " link \0");
           read_next_char = false;
           state = STATE_NORMAL;
           break;
    
         default:
           // Save current character in case of false positive.
           link_buffer[link_buffer_idx++] = c;
           break;
       }
     }
    
     dest[len] = 0;
     return true; // Success (even if empty string)
    }
    
    // Write a passed in string out to the destination buffer if possible, advancing the
    // length as needed and returning it to the caller.
    int writeStringIfPossible(int len, int maxLen, char *dest, char *str) {
     int str_idx = 0;
    
     while (str[str_idx] != '\0' && len < maxLen) {
       dest[len++] = str[str_idx++];
     }
    
     return len;
    }
    // Read a given number of hexadecimal characters from client stream,
    // representing a Unicode symbol.  Return -1 on error, else return nearest
    // equivalent glyph in printer's charset.  (See notes below -- for now,
    // always returns '-' or -1.)
    int unidecode(byte len) {
     int c, v, result = 0;
     while(len--) {
       if((c = timedRead()) < 0) return -1; // Stream timeout
       if     ((c >= '0') && (c <= '9')) v =      c - '0';
       else if((c >= 'A') && (c <= 'F')) v = 10 + c - 'A';
       else if((c >= 'a') && (c <= 'f')) v = 10 + c - 'a';
       else return '-'; // garbage
       result = (result << 4) | v;
     }
    
     // To do: some Unicode symbols may have equivalents in the printer's
     // native character set.  Remap any such result values to corresponding
     // printer codes.  Until then, all Unicode symbols are returned as '-'.
     // (This function still serves an interim purpose in skipping a given
     // number of hex chars while watching for timeouts or malformed input.)
    
     return '-';
    }
    
    // Read from client stream with a 5 second timeout.  Although an
    // essentially identical method already exists in the Stream() class,
    // it's declared private there...so this is a local copy.
    int timedRead(void) {
     unsigned long start = millis();
     while((!client.available()) && ((millis() - start) < 5000L));
     return client.read();  // -1 on timeout
    }
    
    // URL-encoding output function for Print class.
    // Input from RAM or PROGMEM (flash).  Double-encoding is a weird special
    // case for Oauth (encoded strings get encoded a second time).
    void urlEncode(
     Print      &p,       // EthernetClient, Sha1, etc.
     const char *src,     // String to be encoded
     boolean     progmem, // If true, string is in PROGMEM (else RAM)
     boolean     x2)      // If true, "double encode" parenthesis
    {
     static const char PROGMEM hexChar[] = "0123456789ABCDEF";
     uint8_t c;
    
     while((c = (progmem ? pgm_read_byte(src) : *src))) {
       if(((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
          ((c >= '0') && (c <= '9')) || strchr_P(PSTR("-_.~"), c)) {
         p.write(c);
       } else {
         if(x2) p.print("%25");
         else   p.write('%');
         p.write(pgm_read_byte(&hexChar[c >> 4]));
         p.write(pgm_read_byte(&hexChar[c & 15]));
       }
       src++;
     }
    }
    
    // Minimalist time server query; adapted from Arduino UdpNTPClient tutorial.
    unsigned long getTime(void) {
     EthernetUDP   udp;
     DNSClient     dns;
     IPAddress     addr;
     byte          buf[48];
     unsigned long t = 0L;
    
     Serial.print(F("Polling time server..."));
    
     udp.begin(8888);
     dns.begin(Ethernet.dnsServerIP());
    
     // Get a time server address from NTP pool
     if(dns.getHostByName("pool.ntp.org", addr)) {
       static const char PROGMEM
         timeReqA[] = { 227,  0,  6, 236 },
         timeReqB[] = {  49, 78, 49,  52 };
    
       // Assemble and issue request packet
       memset(buf, 0, sizeof(buf));
       memcpy_P( buf    , timeReqA, sizeof(timeReqA));
       memcpy_P(&buf[12], timeReqB, sizeof(timeReqB));
    
       udp.beginPacket(addr, 123);
       udp.write(buf, sizeof(buf));
       udp.endPacket();
    
       delay(1000); // Allow time for response
       if(udp.parsePacket()) {
         // Read result, convert to UNIX time format
         udp.read(buf, sizeof(buf));
         t = (((unsigned long)buf[40] << 24) |
              ((unsigned long)buf[41] << 16) |
              ((unsigned long)buf[42] <<  8) |
               (unsigned long)buf[43]) - 2208988800UL;
         Serial.print(F("OK\r\n"));
       }
     }
     udp.stop();
     if(!t) Serial.print(F("error\r\n"));
    
     return t;
    }
    
    // Timer1 interrupt handler for sleep throb
    ISR(TIMER1_OVF_vect, ISR_NOBLOCK) {
     // Sine table contains only first half...reflect for second half...
     analogWrite(led_pin, pgm_read_byte(&sleepTab[
       (sleepPos >= sizeof(sleepTab)) ?
       (sizeof(sleepTab) * 2 - 1 - sleepPos) : sleepPos]));
     if(++sleepPos >= (sizeof(sleepTab) * 2)) sleepPos = 0; // Roll over
     TIFR1 |= TOV1; // Clear Timer1 interrupt flag
    }
    
     if(!t) Serial.print(F("error\r\n"));
    
     return t;
    }
    
    // Timer1 interrupt handler for sleep throb
    ISR(TIMER1_OVF_vect, ISR_NOBLOCK) {
     // Sine table contains only first half...reflect for second half...
     analogWrite(led_pin, pgm_read_byte(&sleepTab[
       (sleepPos >= sizeof(sleepTab)) ?
       (sizeof(sleepTab) * 2 - 1 - sleepPos) : sleepPos]));
     if(++sleepPos >= (sizeof(sleepTab) * 2)) sleepPos = 0; // Roll over
     TIFR1 |= TOV1; // Clear Timer1 interrupt flag
    }
    
  • WBA ConsultingWBA Consulting Posts: 2,934
    edited 2014-01-06 13:34
    SakuraTot, Welcome to the forums! A quick note, I merged your three posts into one since they are basically duplicates. Hopefully someone with EMIC2 or C experience can jump in on this thread soon to help out. Good luck!


    One comment:
    Try breaking your code down into sections to verify the individual segments are functioning properly. I have to do this frequently. In other wordds, have you verified your setup to the EMIC2 as functional using simple code to just make it speak? Once that is good, check your code between the display and EMIC2. Then go after the tweet reading code. Just a thought.
  • SakuraTotSakuraTot Posts: 4
    edited 2014-01-06 14:17
    Thanks a lot for your advise! I already have done this and also when I run this code Emic2 speaks just once. After the ethernet initialising, emic2 presents the project! But after the first tweet the code stops working.
Sign In or Register to comment.