diff --git a/mac/daemon.py b/mac/daemon.py index f9f6f0d..6da5587 100644 --- a/mac/daemon.py +++ b/mac/daemon.py @@ -33,6 +33,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import urlparse, parse_qs import label +from PIL import Image import dymo as dymo_mod from urovo import Urovo, UrovoError, epc_encode, epc_decode, status_text @@ -277,6 +278,64 @@ def do_read_raw(): return {'ok': any(banks.values()), 'banks': banks} +def do_print_image(body): + """Print a caller-rendered 1-bit bitmap (base64 PNG), optionally encoding first. + + This is the seam that lets a separate app own LAYOUTS while this daemon stays the single + owner of the hardware. do_print renders the Discogs-shaped label itself; anything else -- + a book, a comic, a toy -- arrives here already drawn, at the media's pixel size, and goes + through the exact same encode -> verify -> SET RFID OFF -> print sequence. Same guards, + same "no label spent on a failed encode" promise.""" + import base64, io + prof = label.profile(body.get('size'), web_mm=body.get('w'), pitch_mm=body.get('h'), + gap_mm=body.get('gap')) + encode = bool(body.get('encode', True)) + sku = str(body.get('sku', '') or '').strip() + release_id = str(body.get('releaseId', '') or '0').strip() or '0' + try: + img = Image.open(io.BytesIO(base64.b64decode(body['png']))).convert('1') + except Exception as e: + return {'ok': False, 'error': 'bad png: %s' % e} + epc = None + if encode: + try: + epc = epc_encode(sku, release_id) + except ValueError as e: + return {'ok': False, 'error': str(e)} + with LOCK, Urovo() as p: + before = verified = None + if encode: + before = p.read_uhf() + p.write_uhf(epc) + time.sleep(1.0) + verified = p.read_uhf() == epc + if not verified: + return {'ok': False, 'error': 'RFID encode failed verification', 'epc': epc, + 'epcBefore': before, 'readBack': p.read_uhf(), + 'note': 'nothing printed -- no label wasted'} + p.cmd('SET RFID OFF'); time.sleep(0.4) + p.ribbon_off(); time.sleep(0.4) + if p.wait_ready(3.0) != 0x00: + st = p.recover() + if st != 0x00: + return {'ok': False, 'error': 'printer not ready', 'status': st, + 'statusText': status_text(st)} + geometry(p, prof); p.cls() + p.write(label.to_tspl(img)) + p.ribbon_off(); p.printlabel(1, 1) + time.sleep(2.0) + st = p.wait_ready(10.0) + nxt = None + try: + time.sleep(0.3); p.flush_in(); nxt = p.read_uhf() + except Exception: + pass + return {'ok': True, 'sku': sku, 'releaseId': release_id, 'epc': epc, 'epcBefore': before, + 'verified': verified, 'nextTagAtAntenna': nxt, 'status': st, + 'statusText': status_text(st), 'profile': prof['name'], + 'size': [prof['web_mm'], prof['pitch_mm']], 'mode': 'image+encode' if encode else 'image'} + + def do_calibrate(): with LOCK, Urovo() as p: p.rfid_autocalibrate() @@ -371,6 +430,8 @@ class Handler(BaseHTTPRequestHandler): return self._json(do_print_paper(body)) if path == '/tag-and-print': return self._json(do_tag_and_print(body)) + if path == '/print-image': + return self._json(do_print_image(body)) handler = {'/calibrate': do_calibrate, '/recover': do_recover}.get(path) if handler: return self._json(handler())