Created
September 14, 2020 01:56
-
-
Save Arachnid/4ecff2799656ee6db78c4fc17e180b22 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
import cups | |
from datetime import datetime, date | |
from flask import Flask, make_response, request, render_template_string | |
import io | |
import json | |
import logging | |
from reportlab.pdfgen import canvas | |
from reportlab.lib import units | |
import requests | |
LABEL_WIDTH = 19 * units.mm | |
LABEL_HEIGHT = 51 * units.mm | |
MARGIN_TOP = 6 | |
MARGIN_BOTTOM = 17 | |
BREWFATHER_USERID = '' | |
BREWFATHER_API_KEY = '' | |
INDEX_TEMPLATE = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"> | |
<title>Print beer labels</title> | |
<style> | |
.beerLabel { | |
border: 2px solid black; | |
border-radius: 5px; | |
margin-bottom: 1em; | |
} | |
.bottlingDate { | |
float: right; | |
} | |
.bottlingDate, .abv { | |
font-weight: bold; | |
} | |
</style> | |
<script> | |
function printLabels(data, qty) { | |
fetch('/print?qty=' + qty, { | |
method: 'POST', | |
mode: 'same-origin', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(data) | |
}); | |
} | |
</script> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="row"> | |
<div class="col-xs-9 beerLabel"> | |
<h2>Empties</h2> | |
</div> | |
<div class="col-xs-1"> | |
<button onClick='javascript:printLabels({"name": "Empties", "style": "", "bottlingDate": "", "abv": ""}, 1)'>Print 1</button> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-xs-9 beerLabel"> | |
<h2>330ml</h2> | |
</div> | |
<div class="col-xs-1"> | |
<button onClick='javascript:printLabels({"name": "330ml", "style": "", "bottlingDate": "", "abv": ""}, 1)'>Print 1</button> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-xs-9 beerLabel"> | |
<h2>500ml</h2> | |
</div> | |
<div class="col-xs-1"> | |
<button onClick='javascript:printLabels({"name": "500ml", "style": "", "bottlingDate": "", "abv": ""}, 1)'>Print 1</button> | |
</div> | |
</div> | |
</div> | |
{% for batch in batches %} | |
<div class="row"> | |
{% set name = batch.name %} | |
{% if name == "Batch" %} | |
{% set name = batch.recipe.name %} | |
{% endif %} | |
{% set style = "" %} | |
{% if batch.recipe.style is defined %} | |
{% set style = batch.recipe.style.name %} | |
{% endif %} | |
{% set data = {'name': name, 'style': style, 'bottlingDate': batch.bottlingDate|unixDate, 'abv': batch.measuredAbv} %} | |
<div class="col-xs-9 beerLabel"> | |
<h2>{{name}}</h2> | |
<h3>{{style}}</h3> | |
<div class="bottlingDate">{{batch.bottlingDate|unixDate}}</div> | |
<div class="abv">{{batch.measuredAbv}}% ABV</div> | |
</div> | |
<div class="col-xs-1"> | |
<button onClick='javascript:printLabels({{data|tojson}}, 1)'>Print 1</button> | |
<button onClick='javascript:printLabels({{data|tojson}}, 6)'>Print 6</button> | |
</div> | |
</div> | |
{% endfor %} | |
</div> | |
</body> | |
</html> | |
""" | |
parser = argparse.ArgumentParser(description="Print beer labels") | |
parser.add_argument("--printer", default="LabelWriter-450", help="Printer name") | |
parser.add_argument("--copies", default=1, type=int, help="Number of copies per call") | |
parser.add_argument("--port", default=8080, type=int, help="Port number to run on") | |
def drawText(c, font, size, x, y, s, leading=None): | |
text = c.beginText() | |
text.setTextOrigin(x, y) | |
text.setFont(font, size) | |
text.setLeading(leading or size) | |
text.textLine(s) | |
c.drawText(text) | |
def drawCenteredText(c, font, size, x, y, s, leading=None): | |
w = c.stringWidth(s, font, size) | |
drawText(c, font, size, x - w // 2, y, s, leading) | |
def drawRightAlignedText(c, font, size, x, y, s, leading=None): | |
w = c.stringWidth(s, font, size) | |
drawText(c, font, size, x - w, y, s, leading) | |
def generateLabel(beerName, beerStyle, abv, bottlingDate, copies): | |
data = io.BytesIO() | |
c = canvas.Canvas(data, pagesize=(LABEL_WIDTH, LABEL_HEIGHT)) | |
for i in range(copies): | |
c.rotate(90) | |
drawCenteredText(c, "Helvetica-Bold", 14, LABEL_HEIGHT / 2, -16, beerName) | |
drawCenteredText(c, "Helvetica", 10, LABEL_HEIGHT / 2, -28, beerStyle) | |
drawText(c, "Helvetica-Bold", 10, MARGIN_TOP, -48, abv) | |
drawRightAlignedText(c, "Helvetica-Bold", 10, LABEL_HEIGHT - MARGIN_BOTTOM, -48, bottlingDate) | |
c.showPage() | |
c.save() | |
return data.getvalue() | |
def printLabels(printer, beerName, beerStyle, abv, bottlingDate, copies): | |
if abv: | |
abv = "%s%% ABV" % abv | |
doc = generateLabel(beerName, beerStyle, abv, bottlingDate, copies) | |
conn = cups.Connection() | |
job = conn.createJob(printer, 'labels', {'media': 'om_w167h288_58.76x101.6mm'}) | |
conn.startDocument(printer, job, 'label.pdf', 'application/pdf', 1) | |
conn.writeRequestData(doc, len(doc)) | |
conn.finishDocument(printer) | |
app = Flask(__name__) | |
args = parser.parse_args() | |
@app.template_filter('unixDate') | |
def unixDate(ts): | |
return datetime.utcfromtimestamp(ts / 1000).date().isoformat() | |
@app.route('/', methods=['GET']) | |
def index(): | |
batches = requests.get('https://api.brewfather.app/v1/batches?complete=true&status=Completed', auth=(BREWFATHER_USERID, BREWFATHER_API_KEY)).json() | |
batches.sort(key=lambda b: -b['batchNo']) | |
return render_template_string(INDEX_TEMPLATE, batches=batches) | |
@app.route('/print', methods=['POST']) | |
def printLabelsHandler(): | |
data = json.loads(request.data) | |
qty = int(request.args.get('qty', 1)) | |
printLabels(args.printer, data['name'], data['style'], data['abv'], data['bottlingDate'], qty) | |
return '' | |
if __name__ == '__main__': | |
logging.basicConfig(level=logging.DEBUG) | |
app.run(port=args.port) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment