diff --git a/README.md b/README.md index 03e449c..30bb010 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,11 @@ This project is born from the idea of "bringing receipts" for the life you lead/ Affiliate links to grab the components (if you want to use them): -| Component | Amazon US | Amazon UK | AliExpress | -| --------------------------------- | ----------------------- | ----------------------- | ----------------------------------------- | -| Microcontroller (USB-C D1 Mini) | https://amzn.to/4h2zQYO | https://amzn.to/4gRFgFe | - | -| Thermal Printer (CSN-A4L) | https://amzn.to/4kr5ksq | - | https://s.click.aliexpress.com/e/_opjoNrw | -| Paper Rolls, BPA-free (57.5x30mm) | https://amzn.to/4kpOREP | https://amzn.to/44nqGCg | - | +| Component | Amazon US | Amazon UK | AliExpress | Europe | +| --------------------------------- | ----------------------- | ----------------------- | ----------------------------------------- | -------- | +| Microcontroller (USB-C D1 Mini) | https://amzn.to/4h2zQYO | https://amzn.to/4gRFgFe | - | https://amzn.eu/d/c7Gh3AX | +| Thermal Printer (CSN-A4L) | https://amzn.to/4kr5ksq | - | https://s.click.aliexpress.com/e/_opjoNrw | https://de.aliexpress.com/item/1005004083860562.html (TAKE TTL NOT RS232) | +| Paper Rolls, BPA-free (57.5x30mm) | https://amzn.to/4kpOREP | https://amzn.to/44nqGCg | - | hard to find | **Important Note (Thanks to the community for pointed this out!):** Do your own due diligence regarding thermal paper types - the thermal paper we handle everyday (e.g. through receipts from the grocery store, restaurants, takeaway, taxis, etc.) will contain BPA. When choosing your rolls for this, you should definitely go for BPA-free paper just to be on the safer side - the links provided are for BPA-free paper. If you can, go a step further and look for “phenol-free” paper. Three types that do not contain BPA or BPS and are competitively priced contain either ascorbic acid (vitamin C), urea-based Pergafast 201, or a technology without developers, Blue4est. diff --git a/firmware v1/firmware v1.ino b/firmware v1/firmware v1.ino index 87cd973..0d13c48 100644 --- a/firmware v1/firmware v1.ino +++ b/firmware v1/firmware v1.ino @@ -19,6 +19,8 @@ void setInverse(bool enable); void printLine(String line); void advancePaper(int lines); void printWrappedUpsideDown(String text); +String replaceUmlauts(String text); // umlaut replacement +void ensureUpsideDown(); // NEW: force upside-down mode each job // === WiFi Configuration === const char* ssid = "Your WIFI name"; @@ -142,7 +144,7 @@ void handleRoot() { -
+

Life Receipt:

-
200 characters left
+
250 characters left
@@ -340,16 +342,30 @@ void initializePrinter() { // Enable 180° rotation (which also reverses the line order) printer.write(0x1B); printer.write('{'); printer.write(0x01); // ESC { 1 + + // Prime a newline so the first real command/char isn't dropped + printer.write('\n'); + delay(20); Serial.println("Printer initialized"); } +// Force upside-down mode; resend cmd to survive dropped first byte +void ensureUpsideDown() { + printer.write(0x1B); printer.write('{'); printer.write(0x01); // ESC { 1 + delay(10); +} + void printReceipt() { + ensureUpsideDown(); // ensure orientation before each job Serial.println("Printing receipt..."); // Print wrapped message first (appears at bottom after rotation) printWrappedUpsideDown(currentReceipt.message); + // Insert a blank line before the date for an easier tear line + printLine(""); // blank line + // Print header last (appears at top after rotation) setInverse(true); printLine(currentReceipt.timestamp); @@ -362,6 +378,7 @@ void printReceipt() { } void printServerInfo() { + ensureUpsideDown(); // ensure orientation before this job Serial.println("=== Server Info ==="); Serial.print("Local IP: "); Serial.println(WiFi.localIP()); @@ -389,6 +406,8 @@ void setInverse(bool enable) { } void printLine(String line) { + // Replace umlauts before printing (German to ASCII workaround) + line = replaceUmlauts(line); printer.println(line); } @@ -398,26 +417,67 @@ void advancePaper(int lines) { } } +// Updated text wrapping with explicit newline handling and CR/LF normalization void printWrappedUpsideDown(String text) { + // Normalize line endings to '\n' to handle Windows/Mac inputs + text.replace("\r\n", "\n"); + text.replace("\r", "\n"); + + // Apply umlaut workaround + text = replaceUmlauts(text); + String lines[100]; int lineCount = 0; - + + // First split the incoming text by explicit newline characters + int nlIndex = text.indexOf('\n'); + while (nlIndex != -1) { + String oneLine = text.substring(0, nlIndex); + text = text.substring(nlIndex + 1); + + // Wrap this single logical line + while (oneLine.length() > 0) { + if (oneLine.length() <= maxCharsPerLine) { + lines[lineCount++] = oneLine; + break; + } + int lastSpace = oneLine.lastIndexOf(' ', maxCharsPerLine); + if (lastSpace == -1) lastSpace = maxCharsPerLine; + lines[lineCount++] = oneLine.substring(0, lastSpace); + oneLine = oneLine.substring(lastSpace); + oneLine.trim(); + } + + nlIndex = text.indexOf('\n'); + } + + // Wrap the remaining text after the last newline while (text.length() > 0) { - int breakIndex = maxCharsPerLine; if (text.length() <= maxCharsPerLine) { lines[lineCount++] = text; break; } - int lastSpace = text.lastIndexOf(' ', maxCharsPerLine); if (lastSpace == -1) lastSpace = maxCharsPerLine; - lines[lineCount++] = text.substring(0, lastSpace); text = text.substring(lastSpace); text.trim(); } - + + // Print in reverse order to achieve upside-down final layout for (int i = lineCount - 1; i >= 0; i--) { printLine(lines[i]); } } + +// === umlaut replacement === +String replaceUmlauts(String text) { + text.replace("ä", "ae"); + text.replace("ö", "oe"); + text.replace("ü", "ue"); + text.replace("Ä", "Ae"); + text.replace("Ö", "Oe"); + text.replace("Ü", "Ue"); + text.replace("ß", "ss"); + return text; +}