feat(daemon): /print-image — print a caller-rendered bitmap through the same encode->verify->print path
The seam for a separate label app to own LAYOUTS while this daemon stays the single owner of the hardware. Takes a base64 PNG at media pixel size plus sku/releaseId, and runs the identical guarded sequence: read, UHF WRITE, verify, SET RFID OFF, geometry, print. A failed encode still spends no label. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ce5b2b5db5
commit
7d9a2197b2
@ -33,6 +33,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
import label
|
import label
|
||||||
|
from PIL import Image
|
||||||
import dymo as dymo_mod
|
import dymo as dymo_mod
|
||||||
from urovo import Urovo, UrovoError, epc_encode, epc_decode, status_text
|
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}
|
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():
|
def do_calibrate():
|
||||||
with LOCK, Urovo() as p:
|
with LOCK, Urovo() as p:
|
||||||
p.rfid_autocalibrate()
|
p.rfid_autocalibrate()
|
||||||
@ -371,6 +430,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
return self._json(do_print_paper(body))
|
return self._json(do_print_paper(body))
|
||||||
if path == '/tag-and-print':
|
if path == '/tag-and-print':
|
||||||
return self._json(do_tag_and_print(body))
|
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)
|
handler = {'/calibrate': do_calibrate, '/recover': do_recover}.get(path)
|
||||||
if handler:
|
if handler:
|
||||||
return self._json(handler())
|
return self._json(handler())
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user