/*
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
*/
#include <SPI.h>
#include <Ethernet.h>
#include <Thermal.h>
#include <SoftwareSerial.h>
// Global stuff --------------------------------------------------------------
const int
led_pin = 13, // To status LED (hardware PWM pin)
// Pin 4 is skipped -- this is the Card Select line for Arduino Ethernet!
printer_RX_Pin = 6, // Printer connection: green wire
printer_TX_Pin = 5, // Printer connection: yellow wire
printer_Ground = 4, // Printer connection: black wire
maxTweets = 10; // Limit tweets printed; avoid runaway output
const unsigned long // Time limits, expressed in milliseconds:
pollingInterval = 25L * 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
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, 0x76, 0x09 };
IPAddress
ip(192,168,0,118); // 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 = "majsterkowo",
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
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("Initializing Ethernet...");
if(Ethernet.begin(mac)) {
Serial.println("OK");
} else {
Serial.print("\r\nno DHCP response, using static IP address.");
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));
}
// ---------------------------------------------------------------------------
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("Connecting to server...");
while((client.connect(serverName, 80) == false) &&
((millis() - startTime) < connectTimeout));
if(client.connected()) { // Success!
Serial.print("OK\r\nIssuing HTTP request...");
// 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\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.println("OK\r\nProcessing results...");
resultsDepth = 0;
jsonParse(0, 0);
} else Serial.println("response not recognized.");
} else Serial.println("connection timed out.");
client.stop();
} else { // Couldn't contact server
Serial.println("failed");
}
// 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("Pausing...");
sleepPos = sizeof(sleepTab); // Resume following brightest position
TIMSK1 |= _BV(TOIE1); // Re-enable Timer1 interrupt for sleep throb
delay(pollingInterval - t);
Serial.println("done");
}
}
// ---------------------------------------------------------------------------
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
printer.wake();
printer.inverseOn();
printer.println(" ");
printer.print(fromUser);
printer.println(" ");
printer.inverseOff();
printer.println(msgText);
printer.feed(3);
printer.sleep();
// Dump to serial console as well
Serial.print("User: ");
Serial.println(fromUser);
Serial.print("Text: ");
Serial.println(msgText);
Serial.print("Time: ");
Serial.println(timeStamp);
// 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(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)
}
// ---------------------------------------------------------------------------
// 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) {
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
}
Projekt zacny – już gratulowałem realizacji, ale umieść może stream gdzieś gdzie nie będzie wymagane posiadanie i zakładanie konta aby zerknąć na stream na żywo z drukarki.
Próbowałem robić stream w różnych serwisach i tutaj udało się uzyskać najlepszą jakość i najmniejsze opóźnienia :)
Oglądnąłbym ale nie chce mi się zakładać konta. Wierzę na słowo, że działa xD
Łukaszu, a próbowałeś może justin.tv? Nigdy nie korzystałem i nie używałem, ale wiem że ludzie nawet TV streamują tam i strumienie z gameplayami więc jakość powinna być ok, a nie ma przymusu rejestracji aby oglądać – o ile wiem. Sprawdź również może twitch.tv – co prawda to serwis do streamowania gier, ale może się nie obrażą gdy dorzucisz swoją drukarkę – w końcu można powiedzieć że
twój projekt to rodzaj interaktywnej gry z czytelnikami :)
Popróbuję jeszcze w nocy :)
Próbowałeś streamowania bezpośrednio z VLC? Potrzebne by było jedynie przekierowanie portów.
Próbowałem jakiś czas temu przy okazji innego projektu. Niestety przy moim łączu okazało się to bardzo kiepskim pomysłem ;)
Szkoda. Czekam na email żeby się zarejestrować i mam nadzieję, że przyjdzie.
Ze 2 miesiące temu próbowałem zarejestrować się na pewnej stronie wymiany książek (bez nazwisk :D ) na 2 różne adresy email i do tej pory nie otrzymałem linka aktywacyjnego.
Jak masz w przeglądarce dodatek Stylish, to wystarczy dodać dla tamtej strony:
#fancybox-overlay, #fancybox-content {display: none !important;}
Lub doraźnie można też ręcznie usunąć te dwa divy klikając prawym na stronie i wybierając “Zbadaj element” (tak jest w Chromie – w innych przeglądarkach podobnie;)
Jeśli masz podgląd na wydrukowane tweety to.. już sobie poradziłem AdBlockiem :D
Musiało CI się bardzo nudzić, że na coś takiego wpadłeś. Jednak gratuluję pomysłu ;)
Przecież znalazł ten pomysł w sieci, czytaj ze zrozumieniem.
Genialne w swojej prostocie ;P Ale co Ty chcesz z tą drukarką jeszcze zrobić to nie mam pojęcia ;P Ściągi by fajne wychodziły ;P wydruki na płytki drukowane ;P Jak ethernet to może nie tylko Tweet’y, a jakieś kanały RSS ;P
A tak na marginesie to pamiętaj że paragony fiskalne trzeba przez 5 lat trzymać ;p ;p ;p
Fajna ta drukareczka :) Tylko troszkę droga. Wchodzą do tego takie same rolki jak do kas fiskalnych? Co do pomysłu wykorzystania to myślę że doceni ten projekt jakaś restauracja odbierająca zamówienia przez internet :D
Można by też drukować sobie jakieś statystyki temperatury z termometru :)
Kurcze spodobała mi się ta drukarka :) Jakby kosztowała 100-120zł. to bym kupił ale za 199zł. to trochę za drogo.
Nic nie stoi na przeszkodzie, żebyś ją sobie kupił za 100zł. Widziałeś: https://majsterkowo.pl/dla-majsterkowiczow-zakupy-w-nettigo-za-pol-ceny/ ? ;)
Czyli jak dopiszę do mojego artykułu VU Metra ten moduł mikrofonu z nettigo to będę się kwalifikował do zniżki? :D
Liczyć się będą raczej tylko nowe posty :)
Wygląda mi to na reklamę nettigo… widać idea bloga zmierza ku pieniądzowi…
Jak mi powiesz za co mam sobie kupić pieczywo na śniadanie, zapłacić rachunki (w tym te za hosting i domenę), a także kupować materiały do kolejnych projektów, to z chęcią reklamy wyrzucę :)
A jeżeli chodzi o samo Nettigo, to w chwili obecnej jest w pewnym sensie partnerem Majsterkowa. Chociaż ja kompletnie nic z tego nie mam, bo cała nasza współpraca polega obecnie na tym, że to WY możecie robić tam zakupy za połowę ceny: https://majsterkowo.pl/dla-majsterkowiczow-zakupy-w-nettigo-za-pol-ceny/
Ale pomarudzić trzeba, no nie? ;)
No niestety… Łukaszu – jak śmiesz zarabiać na tym co robisz – oddaj moje pieniędze!!!
Przecież powinieneś miłością do majsterkowania płacić za prąd, pasją za internet i domenę, a chęcią blogowania za mieszkanie, chleb i domenę.
Zacny punkt widzenia… oj jaki ten zespół mały i z kiepskimi instrumentami i mało znany… mija pół roku … ale sprzedajne psy – puszczają ich w radio, sprzedali się, komercha.
Widać z blogerami jest tak samo. Bloger ma przymierać głodem i odrzucać wszelkie sytuacje które mogą doprowadzić do zbrukania się jakimkolwiek dochodem.
Uniosłem się … przepraszam za off-top.
Przypomniał mi się kawał, ze przyszła do lekarza kobieta z czarnym brzuchem i jak lekarz zapytał się czemu tak, odpowiedziała że płaciła za węgiel ;-).
Nie jęcz. Nie zazdrość. Bierz się do roboty. Stwórz lepszy serwis, konkurencję dla majsterkowa. Pełną interesującego materiału. Poświęć swój kawałek życia, wiedzy, środków na stworzenie czegoś lepszego, lepszego dla hobbystów, z tekstami wysokiej jakości, z konkursami (z własnej kieszeni! nie próbuj kombinowania ze sponsorami). I niech cię ręka boska broni przed niecnymi próbami uzyskania z tegoż serwisu “rzek” pieniędzy z niego płynących :> Chętnie popatrzę na taką inicjatywę.
@Tajniak: o zenboxie który zasponsorował kamerkę też nie zapomnij. Nie mniej jednak za pracę kasa się należy.
Fajnie by było to podłączyć pod terminal w linuxie ;]
To by było akurat jeszcze prostsze do zrobienia. :D
Pomysł wbrew pozorom nie jest taki bezużyteczny, być może w przypadku podpięcia drukarki do Twittera wydaje się bezcelowy, ale taka drukarka miała by dobre zastosowanie, np gdyby została podpięta pod stronę internetowa baru czy restauracji, następnie klient dokonywałby zamówienia potraw i podawał adres, a drukarka wydrukowałaby zamówienie, w podobne terminale wyposaża swoich współpracowników znany portal zamówień jedzenia na wynos, warto skomercjalizować projekt
Zastanawiam się czy można w taki sam sposób podłączyć drukarkę hp deskjet f2280. Ona jest na USB więc też wykorzystuje RX i TX.