553 lines
22 KiB
Python
Executable File
553 lines
22 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Morning Edition - Daily receipt printer for Bridgeland, Cypress TX
|
|
Prints to Epson TM-T88V via CUPS printer 'receipt'
|
|
Designed for 80mm paper (42 chars wide)
|
|
|
|
Usage:
|
|
morningedition.py -- print to receipt printer
|
|
morningedition.py --preview -- dump to stdout for testing
|
|
"""
|
|
|
|
import subprocess
|
|
import datetime
|
|
import json
|
|
import urllib.request
|
|
import urllib.error
|
|
import xml.etree.ElementTree as ET
|
|
import sys
|
|
import textwrap
|
|
|
|
# ── Config ─────────────────────────────────────────────────────────────────
|
|
PRINTER = "receipt"
|
|
WIDTH = 30 # printer NV is locked to 58mm mode (30 chars); change to 42 after fixing printer to 80mm
|
|
ZIPCODE = "77433"
|
|
CITY_LINE = "Bridgeland, Cypress TX 77433"
|
|
NAME = "Rich"
|
|
|
|
# ── ESC/POS commands ────────────────────────────────────────────────────────
|
|
ESC = b'\x1b'
|
|
GS = b'\x1d'
|
|
|
|
INIT = ESC + b'\x40' # Initialize printer
|
|
FONT_A = ESC + b'\x4d\x00' # Font A 12x24 — 42 chars/line at 80mm
|
|
BOLD_ON = ESC + b'\x45\x01'
|
|
BOLD_OFF = ESC + b'\x45\x00'
|
|
ALIGN_L = ESC + b'\x61\x00'
|
|
ALIGN_C = ESC + b'\x61\x01'
|
|
CUT = GS + b'\x56\x00' # Full cut
|
|
|
|
# ── Weather ─────────────────────────────────────────────────────────────────
|
|
|
|
def get_weather():
|
|
try:
|
|
url = f"https://wttr.in/{ZIPCODE}?format=j1"
|
|
req = urllib.request.Request(url, headers={"User-Agent": "morningedition/1.0"})
|
|
resp = urllib.request.urlopen(req, timeout=15)
|
|
data = json.loads(resp.read().decode())
|
|
|
|
cc = data["current_condition"][0]
|
|
day = data["weather"][0]
|
|
ast = day.get("astronomy", [{}])[0]
|
|
|
|
return {
|
|
"temp_f" : cc["temp_F"],
|
|
"feels_f" : cc["FeelsLikeF"],
|
|
"desc" : cc["weatherDesc"][0]["value"],
|
|
"humidity" : cc["humidity"],
|
|
"wind_mph" : cc["windspeedMiles"],
|
|
"wind_dir" : cc["winddir16Point"],
|
|
"visibility" : cc["visibility"],
|
|
"uv" : cc["uvIndex"],
|
|
"hi_f" : day["maxtempF"],
|
|
"lo_f" : day["mintempF"],
|
|
"sunrise" : ast.get("sunrise", "?"),
|
|
"sunset" : ast.get("sunset", "?"),
|
|
}
|
|
except Exception as e:
|
|
print(f"[weather error] {e}", file=sys.stderr)
|
|
return None
|
|
|
|
|
|
def weather_art(desc):
|
|
d = desc.lower()
|
|
if any(w in d for w in ["sunny", "clear"]):
|
|
return [
|
|
r" \ | / ",
|
|
r" .---. ",
|
|
r" --- ( * * * ) --- ",
|
|
r" `---' ",
|
|
r" / | \ ",
|
|
]
|
|
elif "partly" in d:
|
|
return [
|
|
r" \ .---. ",
|
|
r" --- ( sun ) ",
|
|
r" `---' )-. ",
|
|
r" .-~~~{ cloudy } ",
|
|
r" `-.__________.-' ",
|
|
]
|
|
elif any(w in d for w in ["overcast", "cloudy"]):
|
|
return [
|
|
r" .-~~~~-. ",
|
|
r" .-{ }-. ",
|
|
r"{ overcast } ",
|
|
r" `-.________.-' ",
|
|
r" ",
|
|
]
|
|
elif any(w in d for w in ["thunder", "storm"]):
|
|
return [
|
|
r" .-~~~~-. ",
|
|
r" .-{ STORM }-. ",
|
|
r" `-.________.-' ",
|
|
r" /\ ",
|
|
r" / \ ",
|
|
]
|
|
elif any(w in d for w in ["rain", "shower", "drizzle"]):
|
|
return [
|
|
r" .-~~~~-. ",
|
|
r" .-{ rain }-. ",
|
|
r" `-.________.-' ",
|
|
r" ' ' ' ' ' ' ' ",
|
|
r" ' ' ' ' ' ' ' ",
|
|
]
|
|
elif any(w in d for w in ["fog", "mist", "haze"]):
|
|
return [
|
|
r" ~ ~ ~ ~ ~ ~ ~ ~ ",
|
|
r" ~ foggy / hazy ~ ",
|
|
r" ~ ~ ~ ~ ~ ~ ~ ~ ",
|
|
r" ~ ~ ~ ~ ~ ~ ~ ~ ",
|
|
r" ~ ~ ~ ~ ~ ~ ~ ~ ",
|
|
]
|
|
else:
|
|
return [
|
|
r" \ | / ",
|
|
r" .---. ",
|
|
r" --- ( ) --- ",
|
|
r" `---' ",
|
|
r" / | \ ",
|
|
]
|
|
|
|
|
|
def uv_label(uv):
|
|
uv = int(uv)
|
|
if uv <= 2: return f"{uv} - Low"
|
|
if uv <= 5: return f"{uv} - Moderate"
|
|
if uv <= 7: return f"{uv} - High"
|
|
if uv <= 10: return f"{uv} - Very High"
|
|
return f"{uv} - Extreme!"
|
|
|
|
|
|
def get_weather_tip(weather):
|
|
if not weather:
|
|
return "Have a safe and productive day!"
|
|
desc = weather["desc"].lower()
|
|
temp = int(weather["temp_f"])
|
|
if any(w in desc for w in ["thunder", "storm"]):
|
|
return "Stay safe! Monitor weather alerts today."
|
|
if any(w in desc for w in ["rain", "shower", "drizzle"]):
|
|
return "Grab an umbrella -- Houston rain waits for no one!"
|
|
if any(w in desc for w in ["fog", "mist"]):
|
|
return "Allow extra drive time -- visibility may be low."
|
|
if temp >= 95:
|
|
return "Stay hydrated! Carry water everywhere today."
|
|
if temp <= 45:
|
|
return "Layer up -- don't let the cold catch you off guard."
|
|
if int(weather.get("uv", 0)) >= 7:
|
|
return "High UV today -- sunscreen before you head out!"
|
|
if int(weather.get("humidity", 0)) >= 85:
|
|
return "Steamy one today -- moisture is high out there."
|
|
return "Make it a great day -- you've got this!"
|
|
|
|
|
|
# ── News ─────────────────────────────────────────────────────────────────────
|
|
|
|
def fetch_rss_headlines(url, count=5):
|
|
try:
|
|
req = urllib.request.Request(url, headers={"User-Agent": "morningedition/1.0"})
|
|
resp = urllib.request.urlopen(req, timeout=15)
|
|
tree = ET.fromstring(resp.read())
|
|
items = tree.findall(".//item")[:count]
|
|
headlines = []
|
|
for item in items:
|
|
t = item.find("title")
|
|
if t is not None and t.text:
|
|
# Strip CDATA, collapse whitespace, ASCII-ify
|
|
text = t.text.strip()
|
|
text = text.encode("ascii", errors="replace").decode("ascii")
|
|
headlines.append(text)
|
|
return headlines
|
|
except Exception as e:
|
|
print(f"[rss error {url}] {e}", file=sys.stderr)
|
|
return []
|
|
|
|
|
|
# ── Rotating daily content ───────────────────────────────────────────────────
|
|
|
|
QUOTES = [
|
|
("The best time to plant a tree was 20 years ago. The second best time is now.",
|
|
"Chinese Proverb"),
|
|
("Every morning we are born again. What we do today matters most.",
|
|
"Buddha"),
|
|
("With the new day comes new strength and new thoughts.",
|
|
"Eleanor Roosevelt"),
|
|
("You have brains in your head. You have feet in your shoes. You can steer yourself any direction you choose.",
|
|
"Dr. Seuss"),
|
|
("Make each day your masterpiece.",
|
|
"John Wooden"),
|
|
("Do something today that your future self will thank you for.",
|
|
"Sean Patrick Flanery"),
|
|
("Start where you are. Use what you have. Do what you can.",
|
|
"Arthur Ashe"),
|
|
("Success is the sum of small efforts repeated day in and day out.",
|
|
"Robert Collier"),
|
|
("Believe you can and you're halfway there.",
|
|
"Theodore Roosevelt"),
|
|
("The secret of getting ahead is getting started.",
|
|
"Mark Twain"),
|
|
("What lies behind us and before us are tiny matters compared to what lies within us.",
|
|
"Ralph Waldo Emerson"),
|
|
("The only way to do great work is to love what you do.",
|
|
"Steve Jobs"),
|
|
("Life is 10% what happens to you and 90% how you react to it.",
|
|
"Charles R. Swindoll"),
|
|
("In the middle of every difficulty lies opportunity.",
|
|
"Albert Einstein"),
|
|
("It always seems impossible until it's done.",
|
|
"Nelson Mandela"),
|
|
("Don't watch the clock; do what it does. Keep going.",
|
|
"Sam Levenson"),
|
|
("Opportunities don't happen, you create them.",
|
|
"Chris Grosser"),
|
|
("Wake up determined. Go to bed satisfied.",
|
|
"Unknown"),
|
|
("Your imagination is your preview of life's coming attractions.",
|
|
"Albert Einstein"),
|
|
("The future belongs to those who believe in the beauty of their dreams.",
|
|
"Eleanor Roosevelt"),
|
|
("It does not matter how slowly you go as long as you do not stop.",
|
|
"Confucius"),
|
|
("Hardships often prepare ordinary people for an extraordinary destiny.",
|
|
"C.S. Lewis"),
|
|
]
|
|
|
|
JOKES = [
|
|
("Why don't scientists trust atoms?",
|
|
"Because they make up everything!"),
|
|
("Why did the scarecrow win an award?",
|
|
"He was outstanding in his field!"),
|
|
("Why can't you give Elsa a balloon?",
|
|
"Because she'll let it go!"),
|
|
("What do you call fake spaghetti?",
|
|
"An impasta!"),
|
|
("Why did the bicycle fall over?",
|
|
"Because it was two-tired!"),
|
|
("What's a skeleton's least favorite room?",
|
|
"The living room!"),
|
|
("Why do cows wear bells?",
|
|
"Because their horns don't work!"),
|
|
("Why don't eggs tell jokes?",
|
|
"They'd crack each other up!"),
|
|
("What do you call a fish without eyes?",
|
|
"A fsh!"),
|
|
("Why did the math book look so sad?",
|
|
"Because it had too many problems!"),
|
|
("What do you get when you cross a snowman and a vampire?",
|
|
"Frostbite!"),
|
|
("Why couldn't the leopard play hide and seek?",
|
|
"Because he was always spotted!"),
|
|
("What do you call cheese that isn't yours?",
|
|
"Nacho cheese!"),
|
|
("Why did the golfer bring extra socks?",
|
|
"In case he got a hole in one!"),
|
|
("What has ears but cannot hear?",
|
|
"A cornfield!"),
|
|
("Why did the coffee file a police report?",
|
|
"It got mugged!"),
|
|
("What do you call a pile of cats?",
|
|
"A meowtain!"),
|
|
("How do trees access the internet?",
|
|
"They log in!"),
|
|
("Why was the belt arrested?",
|
|
"It was holding up a pair of pants!"),
|
|
("What do you call a sleeping dinosaur?",
|
|
"A dino-snore!"),
|
|
("Why did the stadium get hot after the game?",
|
|
"All the fans left!"),
|
|
("What do you call a bear with no teeth?",
|
|
"A gummy bear!"),
|
|
("Why did the tomato turn red?",
|
|
"Because it saw the salad dressing!"),
|
|
]
|
|
|
|
FACTS = [
|
|
"A group of flamingos is called a 'flamboyance.'",
|
|
"Honey never spoils. Edible honey was found in 3,000-year-old Egyptian tombs.",
|
|
"Octopuses have THREE hearts and blue blood.",
|
|
"Crows can recognize and remember human faces.",
|
|
"Bananas are technically berries. Strawberries are not.",
|
|
"A day on Venus is longer than a year on Venus.",
|
|
"Wombats are the only animals that produce cube-shaped droppings.",
|
|
"Sharks existed before trees. Sharks: 450M yrs. Trees: 350M yrs.",
|
|
"Butterflies taste with their feet.",
|
|
"The shortest war in history lasted 38-45 minutes (Anglo-Zanzibar War, 1896).",
|
|
"Oxford University is older than the Aztec Empire.",
|
|
"A snail can sleep for 3 years at a stretch.",
|
|
"Cleopatra lived closer in time to the Moon landing than to the pyramids.",
|
|
"Nintendo was founded in 1889 -- as a playing card company.",
|
|
"The scent of rain on dry earth is called 'petrichor.'",
|
|
"Humans share about 60% of their DNA with bananas.",
|
|
"Sloths can hold their breath longer than dolphins -- up to 40 minutes.",
|
|
"A bolt of lightning is 5x hotter than the surface of the Sun.",
|
|
"The human nose can detect over 1 TRILLION different scents.",
|
|
"Goats have rectangular pupils to give them near-360-degree vision.",
|
|
"A group of owls is called a 'parliament.'",
|
|
"There are more possible chess games than atoms in the observable universe.",
|
|
"Dolphins sleep with one eye open.",
|
|
"Polar bear fur is actually transparent, not white.",
|
|
"Sea otters hold hands while sleeping so they don't drift apart.",
|
|
"An ostrich's eye is bigger than its brain.",
|
|
"Male seahorses carry and give birth to the young.",
|
|
"Platypuses don't have stomachs -- food goes straight to the intestine.",
|
|
"A group of crows is called a 'murder.'",
|
|
"The fingerprints of a koala are virtually identical to human fingerprints.",
|
|
"You can't hum while holding your nose closed.",
|
|
"Cats have fewer toes on their back paws than their front paws.",
|
|
]
|
|
|
|
HOUSTON_TIPS = [
|
|
"IAH tip: Terminal C is usually least crowded for TSA.",
|
|
"Beltway 8 (Sam Houston Tollway) runs 24/7 -- great I-10 bypass.",
|
|
"Bridgeland: Lakeland Activity Center open 5am-10pm weekdays.",
|
|
"H-E-B on Fry Rd is restocked overnight -- mornings = freshest produce.",
|
|
"Houston weather reminder: if you don't like it, wait 20 minutes.",
|
|
"Buc-ee's on I-10 in Katy -- best beaver nuggets in Texas. Just saying.",
|
|
"Bluebonnet season peaks late March -- drive FM 1093 for the show.",
|
|
"Houston Medical Center is the largest in the world. Right in our backyard.",
|
|
"Buffalo Bayou Park has great sunrise walks if you're up early.",
|
|
"Hike-and-bike trails around Bridgeland Lake -- 60+ miles of paths.",
|
|
"Discovery Green downtown hosts free outdoor events most weekends.",
|
|
"NASA Johnson Space Center is 45 min southeast -- worth a visit!",
|
|
"Hermann Park's McGovern Lake paddle boats open at 10am weekends.",
|
|
]
|
|
|
|
ON_THIS_DAY = {
|
|
# month-day: (year, event)
|
|
"04-03": (1973, "First handheld cell phone call made by Martin Cooper."),
|
|
"07-04": (1776, "United States Declaration of Independence adopted."),
|
|
"07-20": (1969, "Apollo 11 lands on the Moon."),
|
|
"12-17": (1903, "Wright Brothers make first successful airplane flight."),
|
|
"01-01": (2000, "Y2K passes without incident -- the world breathes a sigh of relief."),
|
|
"02-14": (1876, "Alexander Graham Bell applies for the telephone patent."),
|
|
"03-14": (1879, "Albert Einstein is born in Ulm, Germany."),
|
|
"10-31": (1517, "Martin Luther posts 95 Theses, sparking the Reformation."),
|
|
"11-22": (1963, "JFK assassinated in Dallas, TX."),
|
|
"05-05": (1961, "Alan Shepard becomes first American in space."),
|
|
"06-06": (1944, "D-Day: Allied forces land at Normandy, France."),
|
|
"08-06": (1945, "Atomic bomb dropped on Hiroshima, Japan."),
|
|
"09-11": (2001, "Terrorist attacks on the World Trade Center, Pentagon."),
|
|
"11-09": (1989, "Berlin Wall falls."),
|
|
"12-25": (0000, "Merry Christmas! Go spend time with family!"),
|
|
}
|
|
|
|
|
|
# ── Formatting helpers ───────────────────────────────────────────────────────
|
|
|
|
def ctr(text, w=WIDTH):
|
|
return text.center(w)[:w]
|
|
|
|
def trunc(text, w=WIDTH):
|
|
"""Truncate to fit on one line, no wrapping."""
|
|
text = text.replace("\n", " ")
|
|
if len(text) > w:
|
|
return text[:w-3] + "..."
|
|
return text
|
|
|
|
def wrap(text, w=WIDTH):
|
|
return textwrap.wrap(text.replace("\n", " "), w)
|
|
|
|
def section(title):
|
|
return [
|
|
"-" * WIDTH,
|
|
ctr(f"[ {title} ]"),
|
|
"-" * WIDTH,
|
|
]
|
|
|
|
def bullet_line(text, w=WIDTH, prefix="* "):
|
|
"""Single-line bullet — truncate to fit, no wrapping."""
|
|
return trunc(prefix + text, w)
|
|
|
|
|
|
# ── Receipt builder ──────────────────────────────────────────────────────────
|
|
|
|
def build_receipt(now):
|
|
L = []
|
|
|
|
doy = now.timetuple().tm_yday
|
|
wday = now.weekday() # 0=Mon .. 6=Sun
|
|
is_weekend = wday >= 5
|
|
month_day = now.strftime("%m-%d")
|
|
|
|
# ── Masthead ──────────────────────────────────────────────────────────
|
|
L += ["=" * WIDTH]
|
|
L += [ctr("* * * MORNING EDITION * * *")]
|
|
L += ["=" * WIDTH]
|
|
L += [ctr(" __ __ ___ ____ _ _")]
|
|
L += [ctr("| \\/ || __|| _ \\| \\| |")]
|
|
L += [ctr("| |\\/| || _| | |_) | ' |")]
|
|
L += [ctr("|_| |_||___||____/|_|\\_|")]
|
|
L += ["=" * WIDTH]
|
|
L += [ctr(f"Good morning, {NAME}!")]
|
|
L += ["=" * WIDTH]
|
|
L += [""]
|
|
|
|
# ── Date / Location ───────────────────────────────────────────────────
|
|
L += [ctr(CITY_LINE)]
|
|
L += [ctr(now.strftime("%A, %B %d, %Y"))]
|
|
L += [ctr(now.strftime("%-I:%M %p"))]
|
|
L += [ctr(f"Day {doy} of {now.year}")]
|
|
if is_weekend:
|
|
L += [""]
|
|
L += [ctr("* * * WEEKEND EDITION * * *")]
|
|
L += [""]
|
|
|
|
# ── On This Day ───────────────────────────────────────────────────────
|
|
if month_day in ON_THIS_DAY:
|
|
yr, evt = ON_THIS_DAY[month_day]
|
|
L += section("ON THIS DAY")
|
|
L += [""]
|
|
if yr:
|
|
L += [ctr(f"In {yr}:")]
|
|
for line in wrap(evt):
|
|
L += [line]
|
|
L += [""]
|
|
|
|
# ── Weather ───────────────────────────────────────────────────────────
|
|
L += section("WEATHER - BRIDGELAND")
|
|
L += [""]
|
|
|
|
weather = get_weather()
|
|
if weather:
|
|
art = weather_art(weather["desc"])
|
|
for a in art:
|
|
L += [ctr(a.strip())]
|
|
L += [""]
|
|
L += [ctr(weather["desc"].upper())]
|
|
L += [""]
|
|
L += [ctr(f"Now: {weather['temp_f']}F Feels: {weather['feels_f']}F")]
|
|
L += [ctr(f"Today: Hi {weather['hi_f']}F / Lo {weather['lo_f']}F")]
|
|
L += [ctr(f"Humidity:{weather['humidity']}% UV: {uv_label(weather['uv'])}")]
|
|
L += [ctr(f"Wind: {weather['wind_mph']} mph {weather['wind_dir']}")]
|
|
L += [ctr(f"Vis: {weather['visibility']} mi")]
|
|
L += [ctr(f"Rise: {weather['sunrise']} Set: {weather['sunset']}")]
|
|
L += [""]
|
|
tip = get_weather_tip(weather)
|
|
for line in wrap(f">> {tip} <<"):
|
|
L += [ctr(line)]
|
|
else:
|
|
L += [ctr("Weather unavailable")]
|
|
L += [ctr("Check weather.gov for updates")]
|
|
|
|
L += [""]
|
|
|
|
# ── Houston News ──────────────────────────────────────────────────────
|
|
L += section("HOUSTON NEWS - ABC13")
|
|
L += [""]
|
|
hou_headlines = fetch_rss_headlines("https://abc13.com/feed/", 5)
|
|
if hou_headlines:
|
|
for h in hou_headlines:
|
|
L += [bullet_line(h)]
|
|
else:
|
|
L += [" (Unable to fetch headlines)"]
|
|
L += [""]
|
|
|
|
# ── World News ────────────────────────────────────────────────────────
|
|
L += section("WORLD NEWS - BBC")
|
|
L += [""]
|
|
bbc_headlines = fetch_rss_headlines("https://feeds.bbci.co.uk/news/rss.xml", 5)
|
|
if bbc_headlines:
|
|
for h in bbc_headlines:
|
|
L += [bullet_line(h)]
|
|
else:
|
|
L += [" (Unable to fetch headlines)"]
|
|
L += [""]
|
|
|
|
# ── Daily Quote ───────────────────────────────────────────────────────
|
|
L += section("THOUGHT FOR THE DAY")
|
|
L += [""]
|
|
q_text, q_author = QUOTES[doy % len(QUOTES)]
|
|
for line in wrap(f'"{q_text}"'):
|
|
L += [line]
|
|
L += [f" -- {q_author}"]
|
|
L += [""]
|
|
|
|
# ── Joke ──────────────────────────────────────────────────────────────
|
|
L += section("MORNING CHUCKLE")
|
|
L += [""]
|
|
setup, punchline = JOKES[doy % len(JOKES)]
|
|
for line in wrap(f"Q: {setup}"):
|
|
L += [line]
|
|
L += [""]
|
|
for line in wrap(f"A: {punchline}"):
|
|
L += [line]
|
|
L += [""]
|
|
|
|
# ── Fun Fact ──────────────────────────────────────────────────────────
|
|
L += section("DID YOU KNOW?")
|
|
L += [""]
|
|
fact = FACTS[doy % len(FACTS)]
|
|
for line in wrap(fact):
|
|
L += [line]
|
|
L += [""]
|
|
|
|
# ── Local Houston Tip ─────────────────────────────────────────────────
|
|
L += section("LOCAL TIP")
|
|
L += [""]
|
|
for line in wrap(HOUSTON_TIPS[doy % len(HOUSTON_TIPS)]):
|
|
L += [line]
|
|
L += [""]
|
|
|
|
# ── Footer ────────────────────────────────────────────────────────────
|
|
L += ["=" * WIDTH]
|
|
if is_weekend:
|
|
L += [ctr("Enjoy your weekend, Rich!")]
|
|
else:
|
|
L += [ctr("Make today count!")]
|
|
L += [ctr("Bridgeland Strong!")]
|
|
L += ["=" * WIDTH]
|
|
L += [ctr(now.strftime("Printed %-I:%M %p"))]
|
|
L += [""]
|
|
L += [""]
|
|
L += [""] # feed before cut
|
|
|
|
text = "\n".join(L)
|
|
|
|
# Wrap text in ESC/POS framing: init → Font A → content → feed → cut
|
|
body = text.encode("ascii", errors="replace")
|
|
return INIT + FONT_A + ALIGN_L + body + b"\n\n\n" + CUT
|
|
|
|
|
|
# ── Print ─────────────────────────────────────────────────────────────────────
|
|
|
|
def print_receipt(data: bytes):
|
|
cmd = ["lpr", "-P", PRINTER, "-o", "raw"]
|
|
result = subprocess.run(cmd, input=data, capture_output=True)
|
|
if result.returncode != 0:
|
|
print(f"lpr error: {result.stderr.decode()}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
|
|
if __name__ == "__main__":
|
|
now = datetime.datetime.now()
|
|
data = build_receipt(now)
|
|
|
|
if "--preview" in sys.argv:
|
|
# Strip ESC/POS bytes for preview — just print the text portion
|
|
sys.stdout.buffer.write(data[len(INIT + FONT_A + ALIGN_L):-len(b"\n\n\n" + CUT)])
|
|
sys.stdout.buffer.write(b"\n")
|
|
else:
|
|
print_receipt(data)
|
|
print(f"Morning Edition printed at {now.strftime('%I:%M %p')} -- have a great day, {NAME}!")
|