#define USE_SPARK_RGB // Use the onbaord RGB LED, comment this out to use extenal LEDs
#define SPARK_MODE MANUAL // Use MANUAL mode to increase data download speed
#define DEBUG // When debug is enabled, log a bunch of stuff to the hardware Serial, if disabled some events will use Spark.publish
#define DEBUG_BAUD 9600 // Serial baud rate for debugging logs
#define DEBUG_DELAY 5 // Delay system by x seconds or wait for serial connection, useful to allow time to attach to serial port for debugging
#define PRINTER_CONNECT_VIA_IP // Connect via IP instead of DNS Lookup of host, host must still be set however
#define PRINTER_SERVER_IP 178,79,132,137 // Print server IP (only required if PRINTER_CONNECT_VIA_IP defined)
#define PRINTER_SERVER_PORT 80 // Printer Server Port (eg. 80)
#define PRINTER_POLL_DELAY 30 // Time in x seconds between polling server for next print job
#define PRINTER_REQUEST_TIMEOUT 300 // Time in x seconds before request for print job is aborted
#define PRINTER_BAUD 19200 // Printer baud rate
#define PRINTER_KEEP_PRINTS // Uncomment to archive all print jobs -- EXPERIMENTAL
#define PRINTER_CACHE_NAME_FORMAT "%03d.job" // Adjust to set the name of the cache files
//#define PRINTER_MAX_SIZE 20000 // If defined, sets max content size to prevent timeouts
//#define USE_ADAFRUIT_THERMAL // Uncomment to use the Adafruit libary instead -- NOT RECOMMENDED / UNTESTED
/********************************
Do NOT modify anything below, for normal operations simply adjust the above defines as required
*********************************/
#ifdef SPARK_MODE
SYSTEM_MODE(SPARK_MODE);
#endif
#include "sd-card-library.h"
#ifdef USE_ADAFRUIT_THERMAL
#define PRINTER_TYPE "A2-bitmap"
#include "Adafruit_Thermal.h"
Adafruit_Thermal printer;
//#define PRINTER_WRITE(b) printer.writeBytes(b);
#define PRINTER_INIT() Serial1.begin(printer_baud); printer.begin(&Serial1);
#else
#define PRINTER_TYPE "A2-raw"
#define PRINTER_WRITE(b) Serial1.write(b)
#define PRINTER_INIT() Serial1.begin(printer_baud);
#endif
const char printerType[] = PRINTER_TYPE;
const char host[] = PRINTER_SERVER_HOST; // the host of the backend server
#ifdef PRINTER_CONNECT_VIA_IP
IPAddress host_ip(PRINTER_SERVER_IP);
#define PRINTER_SERVER host_ip
#else
#define PRINTER_SERVER host
#endif
const unsigned int port = PRINTER_SERVER_PORT;
const unsigned long pollingDelay = PRINTER_POLL_DELAY * 1000; // delay between polling requests (milliseconds)
const unsigned long serialDelay = DEBUG_DELAY * 1000;
const unsigned long requestTimeout = PRINTER_REQUEST_TIMEOUT * 1000;
const int pingTries = 20;
const int serialBaud = DEBUG_BAUD;
// Printer Connections
//const int printer_TX_Pin = RX; // this is the yellow wire
//const int printer_RX_Pin = TX; // this is the green wire
const int printer_baud = PRINTER_BAUD;
// Buttons -- Not in use but for reference of the Choosatron
const uint8_t button1 = D1;
const uint8_t button2 = D2;
const uint8_t button3 = D3;
const uint8_t button4 = D4;
// LED Pins
#ifdef USE_SPARK_RGB
#define LED_ON true
#define LED_OFF false
#else
const uint8_t errorLED = D5; // the red LED
const uint8_t downloadLED = D6; // the amber LED
const uint8_t readyLED = D7; // the green LED
#define LED_ON HIGH
#define LED_OFF LOW
#endif
#define LED_ERROR 2
#define LED_DOWNLOAD 0
#define LED_READY 1
// SD Card
const uint8_t chipSelect = A2;
const uint8_t mosiPin = A5;
const uint8_t misoPin = A4;
const uint8_t clockPin = A3;
// SD Card debugging
#ifdef DEBUG
Sd2Card card;
SdVolume volume;
SdFile root;
#endif
boolean downloadWaiting = false;
unsigned long content_length = 0;
boolean statusOk = false;
char cacheFilename[10];
// -- Everything below here can be left alone
const char sketchVersion[] = "1.0.7";
// -- Debugging
#ifdef DEBUG
void debugTimeAndSeparator() {
Serial.print(millis()); Serial.print(": ");
}
void debug(const char *a) {
debugTimeAndSeparator(); Serial.println(a);
}
#define debug2(a, b) debugTimeAndSeparator(); Serial.print(a); Serial.println(b);
#else
#define debug(a) Spark.publish(a);
#define debug2(a, b) Spark.publish(a, b);
#endif
// -- Initialize the printer ID
const byte idAddress = 0;
char printerId[17]; // the unique ID for this printer.
//char printerId[] = "4r2f5i4u1l3s3s8d\0";
inline void initPrinterID() {
#ifdef DEBUG
debug("Initializing Printer ID");
#endif
if ((EEPROM.read(idAddress) == 255) || (EEPROM.read(idAddress+1) == 255)) {
debug("Generating new ID");
randomSeed(analogRead(0) * analogRead(5));
for(int i = 0; i < 16; i += 2) {
printerId[i] = random(48, 57); // 0-9
printerId[i+1] = random(97, 122); // a-z
EEPROM.write(idAddress + i, printerId[i]);
EEPROM.write(idAddress + i+1, printerId[i+1]);
}
} else {
for(int i = 0; i < 16; i++) {
printerId[i] = (char)EEPROM.read(idAddress + i);
}
}
printerId[16] = '\0';
debug2("Printer ID: ", printerId);
}
// -- Initialize the LEDs
void initDiagnosticLEDs() {
#ifdef DEBUG
debug("Initializing LEDs");
#endif
#ifndef USE_SPARK_RGB
pinMode(errorLED, OUTPUT);
pinMode(downloadLED, OUTPUT);
pinMode(readyLED, OUTPUT);
#endif
statusLED(LED_ERROR, LED_ON);
delay(500);
statusLED(LED_ERROR, LED_OFF);
statusLED(LED_DOWNLOAD, LED_ON);
delay(500);
statusLED(LED_DOWNLOAD, LED_OFF);
statusLED(LED_READY, LED_ON);
delay(500);
statusLED(LED_READY, LED_OFF);
}
// -- Initialize the printer connection
void initPrinter() {
#ifdef DEBUG
debug("Initializing printer");
#endif
PRINTER_INIT();
}
// -- Initialize the SD card
inline void initSD() {
#ifdef DEBUG
debug("Initializing SD card");
// we'll use the initialization code from the utility libraries
// since we're just testing if the card is working!
if (!card.init(SPI_HALF_SPEED, chipSelect)) {
debug("initialization failed. Things to check:");
debug("* is a card is inserted?");
debug("* Is your wiring correct?");
debug("* did you change the chipSelect pin to match your shield or module?");
return;
} else {
debug("Wiring is correct and a card is present.");
}
// print the type of card
Serial.print("\nCard type: ");
switch(card.type()) {
case SD_CARD_TYPE_SD1:
Serial.println("SD1");
break;
case SD_CARD_TYPE_SD2:
Serial.println("SD2");
break;
case SD_CARD_TYPE_SDHC:
Serial.println("SDHC");
break;
default:
Serial.println("Unknown");
}
// Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
if (!volume.init(card)) {
debug("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
return;
}
// print the type and size of the first FAT-type volume
uint32_t volumesize;
Serial.print("\nVolume type is FAT");
Serial.println(volume.fatType(), DEC);
volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
volumesize *= volume.clusterCount(); // we'll have a lot of clusters
volumesize *= 512; // SD card blocks are always 512 bytes
volumesize /= 1024;
volumesize /= 1024;
debug2("Volume size (Mbytes): ", volumesize);
#endif
// Initialize HARDWARE SPI with user defined chipSelect
if (!SD.begin(chipSelect)) {
// SD Card failure.
terminalError(2);
#ifdef DEBUG
debug("Initialization failed!");
#endif
return;
}
}
// -- Initialize the Ethernet connection & DHCP
TCPClient client;
inline void initNetwork() {
#ifdef DEBUG
debug("Initialization network...");
#endif
#ifdef SPARK_MODE
WiFi.connect();
delay(5000);
#endif
#ifdef DEBUG
debug2("IP: ", WiFi.localIP());
debug2("NM: ", WiFi.subnetMask());
debug2("GW: ", WiFi.gatewayIP());
#endif
}
// // -- Setup; runs once on boot.
void setup(){
#ifdef DEBUG
Serial.begin(DEBUG_BAUD);
while (!Serial.available() || millis() < serialDelay) {Spark.process();}
#endif
initDiagnosticLEDs();
initPrinterID();
initSD();
initPrinter();
initNetwork();
}
// Set status LEDs
void statusLED(uint8_t status, bool state) {
#ifdef USE_SPARK_RGB
if (state == LED_OFF) {
RGB.control(false);
} else {
RGB.control(true);
RGB.brightness(255);
switch(status) {
case LED_ERROR:
RGB.color(255, 0, 0); //RED
break;
case LED_DOWNLOAD:
RGB.color(0, 255, 0); //YELLOW
break;
case LED_READY:
RGB.color(0, 0, 255); //BLUE
break;
}
}
#else
switch(status) {
case LED_ERROR:
digitalWrite(errorLED, state);
break;
case LED_DOWNLOAD:
digitalWrite(downloadLED, state);
break;
case LED_READY:
digitalWrite(readyLED, state);
break;
}
#endif
}
void generateFilename() {
int n = 0;
File temp;
int size = 0;
//char filename[10];
snprintf(cacheFilename, sizeof(cacheFilename), PRINTER_CACHE_NAME_FORMAT, n); // includes a three-digit sequence number in the file name
#ifdef PRINTER_KEEP_PRINTS
while(SD.exists(cacheFilename)) {
temp = SD.open(cacheFilename, FILE_READ);
size = temp.size();
temp.close();
if (size == 0) {
SD.remove(cacheFilename);
} else {
n++;
snprintf(cacheFilename, sizeof(cacheFilename), PRINTER_CACHE_NAME_FORMAT, n);
}
}
#else
if (SD.exists(cacheFilename)) {
if (!SD.remove(cacheFilename)) {
// Failed to clear cache.
statusLED(LED_ERROR, LED_ON);
terminalError(4);
}
}
#endif
}
// -- Check for new data and download if found
void checkForDownload() {
unsigned long length = 0;
content_length = 0;
statusOk = false;
#ifdef DEBUG
unsigned long start = millis();
#endif
//cacheFilename =
generateFilename();
File cache = SD.open(cacheFilename, FILE_WRITE);
debug2("Attempting to connect to ", PRINTER_SERVER);
if (client.connect(PRINTER_SERVER, port)) {
debug2("Connected to ", PRINTER_SERVER);
statusLED(LED_READY, LED_ON);
client.print("GET "); client.print("/printer/"); client.print(printerId); client.println(" HTTP/1.0");
client.print("Host: "); client.print(host); client.print(":"); client.println(port);
client.flush();
client.print("Accept: application/vnd.exciting.printer."); client.println(printerType);
client.print("X-Printer-Version: "); client.println(sketchVersion);
client.println();
boolean parsingHeader = true;
float downloadPerc;
unsigned long connectTime = millis() + requestTimeout;
while(client.connected()) {
while(client.available()) {
if (parsingHeader) {
client.find((char*)"HTTP/1.1 ");
char statusCode[] = "xxx";
client.readBytes(statusCode, 3);
statusOk = (strcmp(statusCode, "200") == 0);
client.find((char*)"Content-Length: ");
char c;
while (isdigit(c = client.read())) {
content_length = content_length*10 + (c - '0');
}
if (content_length == 0) {
debug("Disconnecting due to nothing to download");
client.stop();
cache.close();
SD.remove(cacheFilename);
return;
}
#ifdef PRINTER_MAX_SIZE
else if (content_length > PRINTER_MAX_SIZE) {
debug("Disconnecting due to content exceeding limit");
client.stop();
cache.close();
SD.remove(cacheFilename);
return;
}
#endif
debug2("Content length: ", content_length);
client.find((char*)"\n\r\n"); // the first \r may already have been read above
parsingHeader = false;
} else {
cache.write(client.read());
length++;
if (length == content_length) {
debug("Downloaded all content...");
//client.stop();
}
//char c = client.read();
//Serial.print(c);
}
}
if (connectTime < millis()) {
debug("Disconnecting due to timeout");
client.stop();
cache.close();
SD.remove(cacheFilename);
return;
}
}
debug("Server disconnected");
statusLED(LED_DOWNLOAD, LED_OFF);
// Close the connection, and flush any unwritten bytes to the cache.
client.stop();
cache.seek(0);
if (statusOk) {
if ((content_length == length) && (content_length == cache.size())) {
if (content_length > 0) {
debug2("Successfully downloaded print job ", cacheFilename)
downloadWaiting = true;
statusLED(LED_READY, LED_ON);
}
} else {
debug2("Failure, content length: ", content_length);
if (content_length != length) debug2("length: ", length);
if (content_length != cache.size()) debug2("cache: ", cache.size());
statusLED(LED_ERROR, LED_ON);
}
} else {
debug("Response code != 200");
recoverableError();
}
} else {
debug("Couldn't connect");
recoverableError();
}
cache.close();
#ifdef DEBUG
unsigned long duration = millis() - start;
debug2("Bytes: ", length);
debug2("Duration: ", duration);
#endif
}
void flashErrorLEDs(unsigned int times, unsigned int pause) {
while (times--) {
statusLED(LED_ERROR, LED_ON);
//delay(pause);
statusLED(LED_ERROR, LED_OFF);
//delay(pause);
}
}
inline void recoverableError() {
flashErrorLEDs(5, 100);
}
inline void terminalError(unsigned int times) {
flashErrorLEDs(times, 500);
statusLED(LED_ERROR, LED_ON);
debug("Terminal Error - SYSTEM HALT");
while(true) {SPARK_WLAN_Loop();} // no point in carrying on, so do nothing forevermore:
}
// // -- Print send any data from the cache to the printer
#ifdef USE_ADAFRUIT_THERMAL
void printFromDownload() {
debug2("Printing job : ", cacheFilename);
onpaper(String(cacheFilename));
debug2("Finished printing job : ", cacheFilename);
downloadWaiting = false;
statusLED(LED_READY, LED_OFF);
}
void onpaper(String chemin) {
char chemin2[50];
chemin.toCharArray(chemin2, sizeof(chemin2));
File dataFile = SD.open(chemin2, FILE_READ);
if (dataFile) {
printer.printBitmap(dynamic_cast<Stream*>(&dataFile));
}
else {
printer.println("no dataFile");
}
printer.sleep();
printer.wake();
dataFile.close();
}
#else
void printFromDownload() {
File cache = SD.open(cacheFilename);
byte b;
debug2("Printing job : ", cacheFilename);
int file_size = cache.size();
while (file_size--) {
b = (byte)cache.read();
PRINTER_WRITE(b);
}
debug2("Finished printing job : ", cacheFilename);
cache.close();
downloadWaiting = false;
statusLED(LED_READY, LED_OFF);
}
#endif
// // -- Check for new data, if any new data then print
unsigned long nextDownloadCheck = 0;
void loop() {
serialEvent();
if (nextDownloadCheck < millis()) {
checkForDownload();
if (downloadWaiting) {
printFromDownload();
}
nextDownloadCheck = millis() + pollingDelay;
debug2("Next Download: ", nextDownloadCheck);
}
//unsigned long pollingDelaySeconds = pollingDelay / 1000;
//debug2("Next Poll: ", pollingDelaySeconds);
//delay(pollingDelay);
}
String inputString = "";
void serialEvent() {
while (Serial.available()) {
// get the new byte:
char inChar = (char)Serial.read();
//Serial.print(inChar);
// if the incoming character is a newline, set a flag
// so the main loop can do something about it:
if (inChar == '\n' || inChar == '\r') {
//inputString.toUpperCase();
Serial.println(inputString);
if (inputString == "PRINT CACHE") {
Serial.println("Printing cache...");
int n = 0;
File temp;
int size = 0;
char filename[10];
snprintf(filename, sizeof(filename), PRINTER_CACHE_NAME_FORMAT, n); // includes a three-digit sequence number in the file name
while(SD.exists(filename)) {
printFromDownload();
n++;
snprintf(filename, sizeof(filename), PRINTER_CACHE_NAME_FORMAT, n);
}
} else if (inputString == "CLEAR CACHE") {
Serial.println("Clearing cache...");
int n = 0;
File temp;
int size = 0;
char filename[10];
snprintf(filename, sizeof(filename), PRINTER_CACHE_NAME_FORMAT, n); // includes a three-digit sequence number in the file name
while(SD.exists(filename)) {
SD.remove(filename);
Serial.print("Deleting cached job : ");
Serial.println(filename);
n++;
snprintf(filename, sizeof(filename), PRINTER_CACHE_NAME_FORMAT, n);
}
} else if (inputString == "CHECK") {
Serial.println("Checking for downloads...");
checkForDownload();
if (downloadWaiting) {
printFromDownload();
}
nextDownloadCheck = millis() + pollingDelay;
debug2("Next Download: ", nextDownloadCheck);
} else if (inputString == "SPARK") {
Spark.process();
} else if (inputString == "DFU") {
debug("Entering DFU Mode");
System.bootloader();
} else {
Serial.println("Unknown Command!");
}
// clear the string:
inputString = "";
} else {
// add it to the inputString:
inputString += inChar;
}
}
}