forked from adafruit/Adafruit-Tweet-Receipt
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathGutenbird.ino
449 lines (390 loc) · 16.4 KB
/
Gutenbird.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
/*
Gutenbird demo sketch: monitors one or more Twitter accounts
for changes, displaying updates on attached thermal printer.
Written by Adafruit Industries. MIT license.
REQUIRES ARDUINO IDE 1.0 OR LATER -- Back-porting is not likely to
occur, as the code is deeply dependent on the Stream class, etc.
Required hardware includes an Ethernet-connected Arduino board such
as the Arduino Ethernet or other Arduino-compatible board with an
Arduino Ethernet Shield, plus an Adafruit Mini Thermal Receipt
printer and all related power supplies and cabling.
Resources:
http://www.adafruit.com/products/418 Arduino Ethernet
http://www.adafruit.com/products/284 FTDI Friend
http://www.adafruit.com/products/201 Arduino Uno
http://www.adafruit.com/products/201 Ethernet Shield
http://www.adafruit.com/products/597 Mini Thermal Receipt Printer
http://www.adafruit.com/products/600 Printer starter pack
*/
/*Modificado por Nilton Lessa para o projeto do Realejo de Ideias */
#include <SPI.h>
#include <Ethernet.h>
#include <Adafruit_Thermal.h>
#include <SoftwareSerial.h>
#define LED 19
// Global stuff --------------------------------------------------------------
boolean debug = false;
String jsonString = "";
const int
led_pin = 3, // To status LED (hardware PWM pin)
// Pin 4 is skipped -- this is the Card Select line for Arduino Ethernet!
printer_RX_Pin = 5, // Printer connection: green wire
printer_TX_Pin = 6, // Printer connection: yellow wire
printer_Ground = 7, // Printer connection: black wire
maxTweets = 5; // Limit tweets printed; avoid runaway output
const unsigned long // Time limits, expressed in milliseconds:
pollingInterval = 60L * 1000L, // Note: Twitter server will allow 150/hr max
connectTimeout = 15L * 1000L, // Max time to retry server link
responseTimeout = 15L * 1000L; // Max time to wait for data from server
Adafruit_Thermal
printer(printer_RX_Pin, printer_TX_Pin);
byte
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, 0xDA, 0x00, 0xE3, 0x38 };
IPAddress
ip(192,168,1,90); // Fallback address -- code will try DHCP first
EthernetClient
client;
char
*serverName = "search.twitter.com",
// queryString can be any valid Twitter API search string, including
// boolean operators. See https://dev.twitter.com/docs/using-search
// for options and syntax. Funny characters do NOT need to be URL
// encoded here -- the sketch takes care of that.
*queryString = "#realejo OR @realejodeideias OR #curtocafe OR @curtocafe OR #molequedeideias OR @molequedeideias OR @moleque OR #meccarede",
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[11], // Temp space for name:value parsing
value[141], // Temp space for name:value parsing
*mensagemFinal = "Saiba mais sobre o realejo de ideias em http://bit.ly/realejo"; //os espa'os extras no meio da string para quebrar a linha corretamente
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 };
// Function prototypes -------------------------------------------------------
boolean
jsonParse(int, byte),
readString(char *, int);
int
unidecode(byte),
timedRead(void);
// ---------------------------------------------------------------------------
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
Serial.begin(57600);
pinMode(printer_Ground, OUTPUT);
digitalWrite(printer_Ground, LOW); // Just a reference ground, not power
printer.begin();
printer.sleep();
// Initialize Ethernet connection. Request dynamic
// IP address, fall back on fixed IP if that fails:
Serial.print("Inicializando Ethernet...");
if(Ethernet.begin(mac)) {
Serial.println("OK");
} else {
Serial.print("\r\nSem resposta DHCP response, usando IP estatico.");
Ethernet.begin(mac, ip);
}
// Clear all string data
memset(lastId , 0, sizeof(lastId));
memset(timeStamp, 0, sizeof(timeStamp));
memset(fromUser , 0, sizeof(fromUser));
memset(msgText , 0, sizeof(msgText));
memset(name , 0, sizeof(name));
memset(value , 0, sizeof(value));
pinMode(LED, OUTPUT);
}
// ---------------------------------------------------------------------------
void loop() {
unsigned long startTime, t;
int i;
char c;
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);
// Attempt server connection, with timeout...
Serial.print("Conectando ao servidor...");
while((client.connect(serverName, 80) == false) &&
((millis() - startTime) < connectTimeout));
if(client.connected()) { // Success!
Serial.print("OK\r\nFazendo requisicao HTTP...");
// URL-encode queryString to client stream:
client.print("GET /search.json?q=");
for(i=0; c=queryString[i]; i++) {
if(((c >= 'a') && (c <= 'z')) ||
((c >= 'A') && (c <= 'Z')) ||
((c >= '0') && (c <= '9')) ||
(c == '-') || (c == '_') ||
(c == '.') || (c == '~')) {
// Unreserved char: output directly
client.write(c);
} else {
// Reserved/other: percent encode
client.write('%');
client.print(c, HEX);
}
}
client.print("&result_type=recent&include_entities=false&rpp=");
if(lastId[0]) {
client.print(maxTweets); // Limit to avoid runaway printing
client.print("&since_id="); // Display tweets since prior query
client.print(lastId);
} else {
client.print('1'); // First run; show single latest tweet
}
client.print(" HTTP/1.1\r\nHost: ");
client.println(serverName);
client.println("Connection: close\r\n");
Serial.print("OK\r\nAguardando resultados (se houver)...");
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.println("OK\r\nProcessando resultados...");
resultsDepth = 0;
jsonParse(0, 0);
} else Serial.println("response nao reconhecida.");
} else Serial.println("conexao expirada.");
client.stop();
} else { // Couldn't contact server
Serial.println("nao foi possivel contactar server.Falhou");
}
// 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.
t = millis() - startTime;
if(t < 4000L) delay(4000L - t);
// Pause between queries, factoring in time already spent on network
// access, parsing, printing and LED pause above.
t = millis() - startTime;
if(t < pollingInterval) {
Serial.print("Pausando...");
sleepPos = sizeof(sleepTab); // Resume following brightest position
TIMSK1 |= _BV(TOIE1); // Re-enable Timer1 interrupt for sleep throb
delay(pollingInterval - t);
Serial.println("feito");
}
}
// ---------------------------------------------------------------------------
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
// Output to printer
digitalWrite(LED, HIGH);
printer.wake();
printer.inverseOn();
printer.write(' ');
printer.print("Enviado por: @");// sao 14 caracteres
printer.print(fromUser);
for(i=strlen(fromUser)+14; i<31; i++) printer.write(' ');
printer.inverseOff();
printer.underlineOn();
printer.print(timeStamp);
for(i=strlen(timeStamp); i<32; i++) printer.write(' ');
printer.underlineOff();
printer.boldOn();
printer.setSize('M');
printer.println(msgText);
printer.boldOff();
printer.setSize('S');
printer.println(mensagemFinal);
printer.feed(3);
printer.sleep();
delay(60000);
digitalWrite(LED, LOW);
// Dump to serial console as well
if (debug) {
Serial.println("depth: "+depth);
Serial.print("Json: ");
Serial.println(jsonString);
jsonString = "";
}
Serial.print("Usuario: ");
Serial.println(fromUser);
Serial.print("Texto: ");
Serial.println(msgText);
Serial.print("Quando: ");
Serial.println(timeStamp);
Serial.println(mensagemFinal);
// Clear strings for next object
timeStamp[0] = fromUser[0] = msgText[0] = 0;
}
} else if(c == '[') { // Array follows
if((!resultsDepth) && (!strcasecmp(name, "results")))
resultsDepth = depth + 1;
if(!jsonParse(depth + 1,']')) return false;
} else if(c == '"') { // String follows
if(readName) { // Name-reading mode
if(!readString(name, sizeof(name)-1)) return false;
} else { // Value-reading mode
if(!readString(value, sizeof(value)-1)) 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")) {
strncpy(timeStamp, value, sizeof(timeStamp)-1);
} else if(!strcasecmp(name, "from_user")) {
strncpy(fromUser, value, sizeof(fromUser)-1);
} else if(!strcasecmp(name, "text")) {
strncpy(msgText, value, sizeof(msgText)-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) {
int c, len = 0;
while((c = timedRead()) != '\"') { // Read until closing quote
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
if (debug) {
//Serial.print(c + " ");
jsonString+= (unidecode(c) + " ");
}
if(c < 0) return false; // Timeout
// 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;
}
dest[len] = 0;
return true; // Success (even if empty string)
}
//----------------------------------------------------------------------------
// Dado um timestamp em ingles, converte Dia e Mes para portugues
//funcao nao funcionou!! ainda tem que resolver....
String timeStampToPt_Br(char* timeOriginal) {
String timeModificado = String(timeOriginal);
if (timeModificado.indexOf("Mon") >= 0)
timeModificado.replace("Mon", "Seg");
else if (timeModificado.indexOf("Tue") >= 0)
timeModificado.replace("Tue", "Ter");
else if (-1 >= 0)
//else if (timeModificado.indexOf("Wed") >= 0)
timeModificado.replace("Wed", "Qua");
else if (timeModificado.indexOf("Thu") >= 0)
timeModificado.replace("Thu", "Qui");
else if (-1 >= 0)
//else if (timeModificado.indexOf("Fri") >= 0)
timeModificado.replace("Fri", "Sex");
else if (-1 >= 0)
//else if (timeModificado.indexOf("Sat") >= 0)
timeModificado.replace("Sat", "Sab");
else if (-1 >= 0)
timeModificado.replace("Sun", "Dom");
if (-1 >= 0)
timeModificado.replace("Feb", "Fev");
else if (-1 >= 0)
timeModificado.replace("Apr", "Abr");
else if (-1 >= 0)
timeModificado.replace("May", "Mai");
else if (-1 >= 0)
timeModificado.replace("Aug", "Ago");
else if (-1 >= 0)
timeModificado.replace("Sep", "Set");
else if (-1 >= 0)
timeModificado.replace("Oct", "Out");
else if (-1 >= 0)
timeModificado.replace("Dec", "Dez");
else if (0 >= 0)
timeModificado.replace("Jul", "Dez");
return timeModificado;
}
// ---------------------------------------------------------------------------
// 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 {
Serial.print("Caracter nao mapeado = " + c);
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) {
int c;
unsigned long start = millis();
while((!client.available()) && ((millis() - start) < 5000L));
return client.read(); // -1 on timeout
}
// ---------------------------------------------------------------------------
// 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) - 1) * 2 - sleepPos) : sleepPos]));
if(++sleepPos >= ((sizeof(sleepTab) - 1) * 2)) sleepPos = 0; // Roll over
TIFR1 |= TOV1; // Clear Timer1 interrupt flag
}