Initial commit
This commit is contained in:
commit
c0f2c0dfe6
BIN
Eyoyo EY-009P Barcode Scanner.pdf
Normal file
BIN
Eyoyo EY-009P Barcode Scanner.pdf
Normal file
Binary file not shown.
BIN
Eyoyo EY-009P YH1705 Handbuch.pdf
Normal file
BIN
Eyoyo EY-009P YH1705 Handbuch.pdf
Normal file
Binary file not shown.
44
icon.svg
Normal file
44
icon.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="36.999809mm"
|
||||
height="36.999809mm"
|
||||
viewBox="0 0 139.84181 139.84181"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="icon.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||
inkscape:export-filename="../../../../Downloads/icon.png"
|
||||
inkscape:export-xdpi="36.124447"
|
||||
inkscape:export-ydpi="36.124447"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#ffffff"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.5505346"
|
||||
inkscape:cx="9.409792"
|
||||
inkscape:cy="101.54734"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1014"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4"
|
||||
inkscape:document-units="mm" />
|
||||
<path
|
||||
id="text1"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.6096px;line-height:1.15;font-family:Helvetica-Condensed-Black-Se;-inkscape-font-specification:'Helvetica-Condensed-Black-Se, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1"
|
||||
d="m 69.919928,0 c 38.638632,0 69.921882,31.281302 69.921882,69.919928 0,38.638632 -31.28325,69.921882 -69.921882,69.921882 C 31.281302,139.84181 0,108.55856 0,69.919928 0,31.281302 31.281302,0 69.919928,0 Z m 0,2.833985 c -37.039573,0 -67.085943,30.04637 -67.085943,67.085943 0,37.039572 30.04637,67.087902 67.085943,67.087902 37.039572,0 67.087902,-30.04833 67.087902,-67.087902 0,-37.039573 -30.04833,-67.085943 -67.087902,-67.085943 z m 0,1.888672 c 10.262397,0 19.96439,2.376869 28.59961,6.597656 -4.46436,0.177004 -8.19836,2.790811 -11.9707,4.902345 -4.838403,1.61394 -8.895454,4.734278 -13.763676,6.275391 -4.754523,1.924389 -10.048265,2.342406 -14.371095,5.34375 -5.310871,2.994792 -6.541031,-2.388045 -7.867188,-6.392578 -1.679764,-6.07719 -8.964356,-4.793269 -12.000001,-0.728516 -2.558323,4.438313 1.536663,8.933545 4.273438,12.166017 -2.339046,2.624683 -7.46563,5.866414 -9.972657,9.72461 -3.081006,2.61974 -7.339638,5.950634 -6.968751,10.353516 3.469063,5.73139 11.152861,0.531368 15.328127,-1.84375 3.717254,-3.974267 7.217753,3.789734 8.460938,6.798829 1.527154,6.960805 -1.187409,13.991986 -2.65625,20.748048 -1.236262,7.547093 -6.060992,13.662993 -8.966798,20.558593 -2.434163,3.906302 -0.831754,10.128292 -4.306641,12.998052 -0.898026,2.54272 -3.716623,6.13182 -3.853516,9.14063 C 14.576295,109.43683 4.722657,90.838958 4.722657,69.919928 c 0,-36.018273 29.178998,-65.197271 65.197271,-65.197271 z m 32.917972,8.921875 c 19.30903,11.315744 32.2793,32.26968 32.2793,56.275396 0,21.06546 -9.98995,39.779802 -25.48242,51.697272 -0.18336,-1.65965 -2.43845,-3.95592 -4.6543,-4.36914 -6.024932,-2.73427 -8.103702,-8.62582 -8.562502,-14.90039 -1.53701,-5.434192 -2.09141,-11.577742 -5.82031,-16.058602 -4.15407,-6.121553 -8.218239,-12.344072 -12.199224,-18.591797 -3.103884,-3.748903 -6.025598,-7.579198 -6,-12.732423 -0.06228,-5.043057 -3.146214,-9.151304 -4.021485,-13.955079 -0.06795,-3.057216 -3.750204,-8.088045 1.294922,-8.675782 3.555145,-1.406461 6.560425,-2.571485 10.017579,-3.894531 3.274414,-1.767945 6.077998,-4.441804 8.753908,-7.015626 2.87529,-4.611925 8.43712,-2.795275 12.871092,-4.230469 1.74867,-0.212986 2.0422,-1.978193 1.52344,-3.548829 z M 46.54102,35.24805 c 2.067979,-3.54e-4 3.744495,1.676162 3.744141,3.744141 3.54e-4,2.067979 -1.676162,3.744495 -3.744141,3.744141 -2.067216,-7.24e-4 -3.742542,-1.676925 -3.742188,-3.744141 -3.54e-4,-2.067216 1.674972,-3.743417 3.742188,-3.744141 z m 17.500001,41.509769 c 5.023145,0.920742 7.070934,6.624143 11.439454,8.935549 2.987557,3.74045 9.513793,4.46952 9.921873,9.88086 1.23106,4.874012 2.64392,9.665512 6.26758,13.349612 3.01524,3.49008 1.15548,8.09926 1.99219,12.11328 4.69543,-1.0159 8.181492,2.30859 12.658202,2.26953 0.346,0.0409 0.64917,0.0528 0.93555,0.0547 -10.576172,7.40122 -23.442171,11.75586 -37.335942,11.75586 -13.366493,0 -25.786001,-4.02477 -36.126956,-10.91992 0.877573,-0.0558 1.703318,-0.27568 2.316406,-0.91602 1.19461,-4.09198 3.230511,-7.42127 4.066407,-11.54492 6.903689,-12.299192 13.712406,-24.985652 23.865236,-34.978521 z"
|
||||
sodipodi:nodetypes="ssssssssssscccccccccccccccsscsccccccccccccccccccccccccscccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
BIN
kugelstossmeeting-prod.db
Normal file
BIN
kugelstossmeeting-prod.db
Normal file
Binary file not shown.
8
kugelstossmeeting-ticket-scanner.desktop
Normal file
8
kugelstossmeeting-ticket-scanner.desktop
Normal file
@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Exec=python3 /opt/kugelstossmeeting-software/sqlite.py
|
||||
Icon=/opt/kugelstossmeeting-software/icon.svg
|
||||
Type=Application
|
||||
Name=kugelstossmeeting Ticket Scanner
|
||||
GenericName=kugelstossmeeting Ticket Scanner
|
||||
Categories=Utility;
|
||||
|
377
sqlite.py
Executable file
377
sqlite.py
Executable file
@ -0,0 +1,377 @@
|
||||
'''
|
||||
TODOS
|
||||
- UI aufhübschen
|
||||
- Suchfunktion einbauen
|
||||
- usbscanner.py journal log automatisch öffnen per Button (soll separate Shell öffnen)
|
||||
'''
|
||||
|
||||
import sqlite3
|
||||
from sqlite3 import Error
|
||||
from contextlib import closing
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
import tkinter as tk
|
||||
#from tkinter import *
|
||||
from tkinter import ttk, messagebox
|
||||
import tkinter.font as font
|
||||
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
db_file = os.path.join(BASE_DIR, "kugelstossmeeting-prod.db")
|
||||
if os.path.isfile(db_file) is False:
|
||||
error = "Error: Datenbankdatei '{}' konnte nicht gefunden werden!".format(db_file)
|
||||
print(error)
|
||||
messagebox.showerror("Error", error)
|
||||
exit(1)
|
||||
if os.path.getsize(db_file) == 0:
|
||||
error = "Error: Datenbankdatei '{}' ist 0 Byte groß!".format(db_file)
|
||||
print(error)
|
||||
messagebox.showerror("Error", error)
|
||||
exit(1)
|
||||
|
||||
title = "Kugelstoßmeeting Rochlitz"
|
||||
|
||||
timeoffset = 20 #seconds
|
||||
autoscroll = True
|
||||
lastSelectedTicket = None #das letzte manuell ausgewählte. Müssen wir merken, sonst wird es alle 2 Sekunden deselektiert
|
||||
|
||||
window = tk.Tk()
|
||||
#window.geometry('800x600+0+0')
|
||||
window.attributes('-zoomed', True)
|
||||
window.title(title)
|
||||
window.iconphoto(False, tk.PhotoImage(file=os.path.join(BASE_DIR, "icon.png")))
|
||||
|
||||
currentScan = None
|
||||
|
||||
def treeview_sort_column(tv, col, reverse):
|
||||
l = [(tv.set(k, col), k) for k in tv.get_children('')]
|
||||
l.sort(key=lambda t: int(t[0]), reverse=reverse)
|
||||
for index, (val, k) in enumerate(l):
|
||||
tv.move(k, '', index)
|
||||
tv.heading(col, command=lambda: treeview_sort_column(tv, col, not reverse))
|
||||
|
||||
|
||||
def insertManualSelectionAsScan(mb):
|
||||
sql = "INSERT INTO scans(unixtimestamp_created,barcode,validated,hint,unixtimestamp_checked,skipped) VALUES(?,?,?,?,?,?)"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
ts = time.time_ns()
|
||||
res = cur.execute(sql,(int(ts),mb,0,"Manuelle Auswahl",None,0))
|
||||
print(res)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def getLastScan():
|
||||
#sql = "SELECT * FROM scans WHERE skipped = 0 AND validated = 0 ORDER BY unixtimestamp_created DESC LIMIT 1;"
|
||||
sql = "SELECT * FROM scans WHERE (julianday('now') - julianday(CAST(unixtimestamp_created AS float) / 1e9,'unixepoch'))*24*60*60 < ? ORDER BY unixtimestamp_created DESC LIMIT 1;"
|
||||
#sql = "SELECT * FROM scans ORDER BY unixtimestamp_created DESC LIMIT 1;"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
res = cur.execute(sql, (timeoffset, ))
|
||||
#res = cur.execute(sql)
|
||||
rows = res.fetchall()
|
||||
if len(rows) > 0:
|
||||
return rows[-1]
|
||||
else:
|
||||
return []
|
||||
|
||||
def getValidatedCount():
|
||||
sql = "SELECT COUNT(*) FROM scans WHERE validated = 1 and skipped = 0;"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
res = cur.execute(sql)
|
||||
rows = res.fetchall()
|
||||
validated=rows[-1][0]
|
||||
sql = "SELECT COUNT(*) FROM tickets;"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
res = cur.execute(sql)
|
||||
rows = res.fetchall()
|
||||
total=rows[-1][0]
|
||||
return "{}/{} eingecheckt".format(validated, total)
|
||||
|
||||
#Anzahl der Barcodes, die mehr als 1x eingescannt wurden (nur für Statistik)
|
||||
def getScannedDuplicates():
|
||||
sql = 'SELECT COUNT(*) FROM (SELECT COUNT(*) as "ct",* FROM scans GROUP BY barcode) WHERE ct > 1;'
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
res = cur.execute(sql)
|
||||
rows = res.fetchall()
|
||||
total=rows[-1][0]
|
||||
return "{} Barcodes mehr als 1x gescannt".format(total)
|
||||
|
||||
def isAlreadyCheckedIn(barcode):
|
||||
sql = "SELECT * FROM scans WHERE validated = 1 AND barcode = ? ORDER BY unixtimestamp_checked LIMIT 1;"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
res = cur.execute(sql, (barcode, ))
|
||||
rows = res.fetchall()
|
||||
return True if len(rows) > 0 else False
|
||||
|
||||
def getScans(tv):
|
||||
#sql = "SELECT * FROM scans WHERE skipped = 0 AND validated = 0;" # Scans, die nicht übersprungen und noch nicht validiert wurden
|
||||
sql = "SELECT * FROM scans;" # alle Scans
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
res = cur.execute(sql)
|
||||
rows = res.fetchall()
|
||||
for i in tv.get_children():
|
||||
tv.delete(i)
|
||||
k=1
|
||||
for i in rows:
|
||||
unixtimestamp_created_c = datetime.fromtimestamp(int(str(i[0])[0:10]), pytz.timezone("Europe/Berlin")).strftime('%d.%m.%Y %H:%M:%S')
|
||||
try:
|
||||
unixtimestamp_checked_c = datetime.fromtimestamp(int(str(i[4])[0:10]), pytz.timezone("Europe/Berlin")).strftime('%d.%m.%Y %H:%M:%S')
|
||||
except:
|
||||
unixtimestamp_checked_c = ""
|
||||
barcode = i[1]
|
||||
validated = "Ja" if i[2] == 1 else "Nein"
|
||||
hint = "" if i[3] == None else i[3]
|
||||
#unixtimestamp_skipped = i[4]
|
||||
skipped = "Ja" if i[5] == 1 else "Nein"
|
||||
unixtimestamp_created = i[0]
|
||||
if k % 2 == 0:
|
||||
evenodd = "evenrow"
|
||||
else:
|
||||
evenodd = "oddrow"
|
||||
tv.insert('', 1, text=k, values=(unixtimestamp_created_c, barcode, validated, hint, unixtimestamp_checked_c, skipped, unixtimestamp_created), tags=(evenodd, ))
|
||||
k+=1
|
||||
treeview_sort_column(tv, 6, False) #wir sortieren nach unixtimestamp_created, weil unixtimestamp_created_c einen Fehler bringt
|
||||
global autoscroll
|
||||
if autoscroll is True:
|
||||
if len(tv.get_children()) > 0:
|
||||
lastItem = tv.get_children()[-1]
|
||||
tv.focus(lastItem)
|
||||
tv.selection_set(lastItem)
|
||||
tv.yview_moveto(1)
|
||||
|
||||
#Tickets
|
||||
def getTickets(tv):
|
||||
global lastSelectedTicket
|
||||
lastSelectedTicket = tv.index(tv.focus())
|
||||
|
||||
#sql = "SELECT * FROM tickets;"
|
||||
sql = "SELECT DISTINCT tickets.*, CASE WHEN RES.validated = 1 THEN 'Ja' ELSE 'Nein' END FROM tickets FULL JOIN (SELECT * FROM scans WHERE validated = 1 and skipped = 0) AS RES ON RES.barcode = tickets.BarcodeContent;"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
res = cur.execute(sql)
|
||||
rows = res.fetchall()
|
||||
for i in tv.get_children():
|
||||
tv.delete(i)
|
||||
k=1
|
||||
for i in rows:
|
||||
if k % 2 == 0:
|
||||
evenodd = "evenrow"
|
||||
else:
|
||||
evenodd = "oddrow"
|
||||
tv.insert('', 1, text=k, values=(i), tags=(evenodd, ))
|
||||
k+=1
|
||||
treeview_sort_column(tv, 1, False)
|
||||
if len(tvScans.get_children()) > 0:
|
||||
global autoscroll
|
||||
if autoscroll is True:
|
||||
Barcode = tvScans.item(tvScans.focus())['values'][1] #get row von Scan, wo Barcode dem Barcode in Ticket entspr
|
||||
col=0 #Barcode-Spalte
|
||||
l = [(tvTickets.set(k, col), k) for k in tvTickets.get_children('')]
|
||||
for index, (val, k) in enumerate(l):
|
||||
if val == Barcode:
|
||||
tv.focus(tv.get_children()[index])
|
||||
tv.selection_set(tv.get_children()[index])
|
||||
tv.yview_moveto(0)
|
||||
tv.yview_scroll(index, "units")
|
||||
else:
|
||||
tv.focus(tv.get_children()[lastSelectedTicket])
|
||||
tv.selection_set(tv.get_children()[lastSelectedTicket])
|
||||
|
||||
def validate():
|
||||
if len(currentScan) > 0:
|
||||
sql = "UPDATE scans set unixtimestamp_checked = ?, hint = ?, validated = 1 WHERE unixtimestamp_created = ? AND barcode = ? AND validated = 0 AND skipped = 0;"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
ts = time.time_ns()
|
||||
res = cur.execute(sql,(int(ts), hintTextInput.get(1.0, "end-1c"), currentScan[0],currentScan[1]))
|
||||
print(res)
|
||||
conn.commit()
|
||||
hintTextInput.delete(1.0,tk.END) #reset hintTextInput
|
||||
|
||||
def skip():
|
||||
if len(currentScan) > 0:
|
||||
sql = "UPDATE scans set unixtimestamp_checked = ?, hint = ?, skipped = 1 WHERE unixtimestamp_created = ? AND barcode = ? AND validated = 0;"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
ts = time.time_ns()
|
||||
res = cur.execute(sql,(int(ts), hintTextInput.get(1.0, "end-1c"), currentScan[0],currentScan[1]))
|
||||
print(res)
|
||||
conn.commit()
|
||||
hintTextInput.delete(1.0,tk.END) #reset hintTextInput
|
||||
|
||||
toggleScrollbarButton_t1 = "Umschalten zu: Ticket manuell auswählen"
|
||||
toggleScrollbarButton_t2 = "Umschalten zu: Ticket automatisch per Scan auswählen (Standard)"
|
||||
|
||||
def manual():
|
||||
if toggleScrollbarButton.config('text')[-1] == toggleScrollbarButton_t2:
|
||||
global lastSelectedTicket
|
||||
mBarcode = None
|
||||
if len(tvScans.get_children()) > 0:
|
||||
item = tvTickets.item(tvTickets.focus())
|
||||
row = 0
|
||||
|
||||
for (val, k) in enumerate(item.values()):
|
||||
row += 1
|
||||
if row == 3:
|
||||
mBarcode = k[0]
|
||||
print(mBarcode)
|
||||
insertManualSelectionAsScan(mBarcode) #insert a new "lastScan"
|
||||
|
||||
def ScrollToggle():
|
||||
global autoscroll
|
||||
if toggleScrollbarButton.config('text')[-1] == toggleScrollbarButton_t1:
|
||||
toggleScrollbarButton.config(text=toggleScrollbarButton_t2)
|
||||
autoscroll = False
|
||||
tvTickets['selectmode'] = "browse"
|
||||
else:
|
||||
toggleScrollbarButton.config(text=toggleScrollbarButton_t1)
|
||||
autoscroll = True
|
||||
tvTickets['selectmode'] = "none"
|
||||
|
||||
toggleScrollbarButton = tk.Button(text=toggleScrollbarButton_t1, width=70, relief="raised", bg='pink', command=ScrollToggle)
|
||||
toggleScrollbarButton.pack(padx=10, pady=5)
|
||||
|
||||
f1=tk.Frame(window)
|
||||
f1.pack(expand=1)
|
||||
|
||||
tk.Label(f1, text="Die Anzeige des aktuellen Barcodes setzt sich automatisch nach {} Sekunden zurück".format(timeoffset), padx=10, pady=5).pack()
|
||||
|
||||
manualButton = tk.Button(f1, text="Manuelle Auswahl", command=manual)
|
||||
manualButton.pack(side=tk.LEFT)
|
||||
manualButton['font']=font.Font(size=35)
|
||||
|
||||
barcodeLabel = tk.Label(f1, text="<>", bg='#000000', fg='#00ff00', padx=10, pady=5)
|
||||
barcodeLabel.config(font=(None, 35))
|
||||
barcodeLabel.pack(side=tk.LEFT)
|
||||
|
||||
checkinButton = tk.Button(f1, text="Einchecken/entwerten", bg='red', fg='white', command=validate)
|
||||
checkinButton.pack(side=tk.RIGHT)
|
||||
checkinButton['font']=font.Font(size=35)
|
||||
skipButton = tk.Button(f1, text="Überspringen", bg='red', fg='white', command=skip)
|
||||
skipButton.pack(side=tk.RIGHT)
|
||||
skipButton['font']=font.Font(size=35)
|
||||
|
||||
commentLabel = tk.Label(window, text="Kommentar", padx=10, pady=5)
|
||||
commentLabel.config(font=(None, 15))
|
||||
commentLabel.pack()
|
||||
|
||||
hintTextInput = tk.Text(window, height = 3, width = 100)
|
||||
hintTextInput.pack()
|
||||
totalValidatedLabel = tk.Label(window, text="/", fg='#008000', padx=10, pady=5, width=100)
|
||||
totalValidatedLabel.config(font=(None, 35))
|
||||
totalValidatedLabel.pack()
|
||||
|
||||
tabControl = ttk.Notebook(window)
|
||||
tabScans = ttk.Frame(tabControl)
|
||||
tabTickets = ttk.Frame(tabControl)
|
||||
|
||||
tabControl.add(tabTickets, text='Alle Tickets')
|
||||
tabControl.add(tabScans, text='Alle Scans')
|
||||
tabControl.pack(fill='both')
|
||||
|
||||
tk.Label(tabScans, text="Scans").pack()
|
||||
totalDuplicateCodeScansLabel = tk.Label(tabScans, text="/", padx=10, pady=5)
|
||||
totalDuplicateCodeScansLabel.pack()
|
||||
tk.Label(tabTickets, text="Tickets").pack()
|
||||
|
||||
def updateBarcodeLabel(label, scan):
|
||||
if len(scan) == 0:
|
||||
labeltext="Bitte scannen!"
|
||||
label.config(fg='#0000ff')
|
||||
else:
|
||||
barcode = scan[1]
|
||||
alreadyCheckedIn = isAlreadyCheckedIn(barcode)
|
||||
if alreadyCheckedIn is False:
|
||||
if toggleScrollbarButton.config('text')[-1] == toggleScrollbarButton_t2:
|
||||
ScrollToggle() #toggle zurück zu Standardmodus, wenn ein normaler Scan gekommen ist
|
||||
labeltext="{0} ready!".format(barcode)
|
||||
label.config(fg='#00ff00')
|
||||
skip = scan[4]
|
||||
if skip is not None:
|
||||
labeltext="{0} übersprungen!".format(barcode)
|
||||
label.config(fg='#ffffff')
|
||||
else:
|
||||
labeltext="{0} entwertet!".format(barcode)
|
||||
label.config(fg='#ff0000')
|
||||
label.config(text=labeltext)
|
||||
|
||||
tvScans = ttk.Treeview(tabScans, height=50, selectmode="browse")
|
||||
tvScans["columns"] = ("c1", "c2", "c3", "c4", "c5", "c6", "c7") #eine Spalte mehr (unixtimestamp_created, welche aber ausgeblendet ist und nur zum Sortieren genutzt wird)
|
||||
tvScans["displaycolumns"]=("c1", "c2", "c3", "c6", "c4", "c5")
|
||||
tvScans.column("c1", width=100)
|
||||
tvScans.column("c2", anchor='center', width=100)
|
||||
tvScans.column("c3", anchor='center', width=100)
|
||||
tvScans.column("c4", anchor='center', width=100)
|
||||
tvScans.column("c5", anchor='center', width=100)
|
||||
tvScans.column("c6", anchor='center', width=100)
|
||||
tvScans.heading("#0", text="Nr.")
|
||||
tvScans.heading("c1", text="TS Gescannt", anchor='w')
|
||||
tvScans.heading("c2", text="Barcode")
|
||||
tvScans.heading("c3", text="Entwertet")
|
||||
tvScans.heading("c4", text="Anmerkung/Hinweise")
|
||||
tvScans.heading("c5", text="TS Bearbeitet")
|
||||
tvScans.heading("c6", text="Übersprungen")
|
||||
tvScans.tag_configure('oddrow', background='white', font=("monospace", 10))
|
||||
tvScans.tag_configure('evenrow', background='lightblue', font=("monospace", 10))
|
||||
tvScansBar = ttk.Scrollbar(tabScans, orient="vertical", command=tvScans.yview)
|
||||
tvScansBar.pack(side='right', fill='x')
|
||||
tvScans.configure(yscrollcommand = tvScansBar.set)
|
||||
tvScans.pack(fill='both')
|
||||
|
||||
tvTickets = ttk.Treeview(tabTickets, height=50, selectmode="none")
|
||||
tvTickets["columns"] = ("c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13")
|
||||
tvTickets["displaycolumns"]=("c1", "c3", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13")
|
||||
tvTickets.column("c1", width=100)
|
||||
tvTickets.column("c2", anchor='center', width=100)
|
||||
tvTickets.column("c3", anchor='center', width=100)
|
||||
tvTickets.column("c4", anchor='center', width=100)
|
||||
tvTickets.column("c5", anchor='center', width=100)
|
||||
tvTickets.column("c6", anchor='center', width=100)
|
||||
tvTickets.column("c7", anchor='center', width=100)
|
||||
tvTickets.column("c8", anchor='center', width=100)
|
||||
tvTickets.column("c9", anchor='center', width=100)
|
||||
tvTickets.column("c10", anchor='center', width=100)
|
||||
tvTickets.column("c11", anchor='center', width=100)
|
||||
tvTickets.column("c12", anchor='center', width=100)
|
||||
tvTickets.column("c13", anchor='center', width=100)
|
||||
tvTickets.heading("#0", text="Nr.")
|
||||
tvTickets.heading("c1", text="Barcode", anchor='w')
|
||||
tvTickets.heading("c2", text="Nr")
|
||||
tvTickets.heading("c3", text="VIP")
|
||||
tvTickets.heading("c4", text="Ticketbeschriftung")
|
||||
tvTickets.heading("c5", text="BarcodeFile")
|
||||
tvTickets.heading("c6", text="Medium")
|
||||
tvTickets.heading("c7", text="Aushändigung")
|
||||
tvTickets.heading("c8", text="Bestellnummer/Verwendungszweck")
|
||||
tvTickets.heading("c9", text="Bezahlt am")
|
||||
tvTickets.heading("c10", text="Hinweise")
|
||||
tvTickets.heading("c11", text="Besteller")
|
||||
tvTickets.heading("c12", text="Typ")
|
||||
tvTickets.heading("c13", text="Entwertet")
|
||||
tvTickets.tag_configure('oddrow', background='white', font=("monospace", 10))
|
||||
tvTickets.tag_configure('evenrow', background='lightblue', font=("monospace", 10))
|
||||
#tvTicketsBar = ttk.Scrollbar(tvTickets, orient="vertical", command=tvTickets.yview)
|
||||
#tvTicketsBar.pack(side='right', fill='x')
|
||||
#tvTickets.configure(yscrollcommand = tvTicketsBar.set)
|
||||
tvTickets.pack(fill='both')
|
||||
|
||||
def update():
|
||||
getScans(tvScans)
|
||||
getTickets(tvTickets)
|
||||
global currentScan
|
||||
currentScan=getLastScan() #bekomme den letzten, unbearbeiteten Scan
|
||||
updateBarcodeLabel(barcodeLabel, currentScan)
|
||||
totalValidatedLabel.config(text=getValidatedCount())
|
||||
totalDuplicateCodeScansLabel.config(text=getScannedDuplicates())
|
||||
window.after(2000, update)
|
||||
|
||||
update() # run first time
|
||||
window.mainloop()
|
162
usb-scanner.py
Normal file
162
usb-scanner.py
Normal file
@ -0,0 +1,162 @@
|
||||
'''
|
||||
Hilfe:
|
||||
- Scanner wird nicht mehr erkannt? Abziehen vom USB-Slot für 1-2 Minuten hilft + Stoppen dieses Programms für die gleiche Zeit
|
||||
- Scanner-Wert wird nicht übertragen? -> Knopf ca. 1 Sekunde gedrückt halten. Nicht bloß antippen!
|
||||
'''
|
||||
import usb.core
|
||||
import usb.util
|
||||
import time
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
from systemd import journal
|
||||
import sqlite3
|
||||
from sqlite3 import Error
|
||||
from contextlib import closing
|
||||
import os.path
|
||||
|
||||
idVendor=0x0581
|
||||
idProduct=0x0115
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
db_file = os.path.join(BASE_DIR, "kugelstossmeeting-prod.db")
|
||||
|
||||
def printwrite(str): #write to log and to stdout
|
||||
journal.write(str)
|
||||
print(str)
|
||||
|
||||
def hid2ascii(lst):
|
||||
#array('B', [0, 0, 0, 0, 0, 0, 0, 0]) # nothing, ignore
|
||||
conv_table = {
|
||||
0:['', ''],
|
||||
4:['a', 'A'],
|
||||
5:['b', 'B'],
|
||||
6:['c', 'C'],
|
||||
7:['d', 'D'],
|
||||
8:['e', 'E'],
|
||||
9:['f', 'F'],
|
||||
10:['g', 'G'],
|
||||
11:['h', 'H'],
|
||||
12:['i', 'I'],
|
||||
13:['j', 'J'],
|
||||
14:['k', 'K'],
|
||||
15:['l', 'L'],
|
||||
16:['m', 'M'],
|
||||
17:['n', 'N'],
|
||||
18:['o', 'O'],
|
||||
19:['p', 'P'],
|
||||
20:['q', 'Q'],
|
||||
21:['r', 'R'],
|
||||
22:['s', 'S'],
|
||||
23:['t', 'T'],
|
||||
24:['u', 'U'],
|
||||
25:['v', 'V'],
|
||||
26:['w', 'W'],
|
||||
27:['x', 'X'],
|
||||
28:['z', 'Z'],
|
||||
29:['y', 'Y'],
|
||||
30:['1', '!'],
|
||||
31:['2', '@'],
|
||||
32:['3', '#'],
|
||||
33:['4', '$'],
|
||||
34:['5', '%'],
|
||||
35:['6', '^'],
|
||||
36:['7' ,'&'],
|
||||
37:['8', '*'],
|
||||
38:['9', '('],
|
||||
39:['0', ')'],
|
||||
40:['\n', '\n'],
|
||||
41:['\x1b', '\x1b'],
|
||||
42:['\b', '\b'],
|
||||
43:['\t', '\t'],
|
||||
44:[' ', ' '],
|
||||
45:['_', '_'],
|
||||
46:['=', '+'],
|
||||
47:['[', '{'],
|
||||
48:[']', '}'],
|
||||
49:['\\', '|'],
|
||||
50:['#', '~'],
|
||||
51:[';', ':'],
|
||||
52:["'", '"'],
|
||||
53:['`', '~'],
|
||||
54:[',', '<'],
|
||||
55:['.', '>'],
|
||||
56:['/', '?'],
|
||||
100:['\\', '|'],
|
||||
103:['=', '='],
|
||||
}
|
||||
|
||||
line = ''
|
||||
char = ''
|
||||
shift = 0
|
||||
|
||||
#print(lst)
|
||||
|
||||
for byte in lst: #raw byte array
|
||||
if byte == 2:
|
||||
shift = 1
|
||||
if byte != 0:
|
||||
if byte not in conv_table and byte != 2:
|
||||
printwrite("Warning: byte {0} not in conversion table".format(byte))
|
||||
char = ''
|
||||
line += char
|
||||
elif byte != 40 and byte != 2: #skip newline character
|
||||
char = conv_table[byte][shift]
|
||||
line += char
|
||||
shift = 0 #reset shift
|
||||
return line
|
||||
|
||||
sleepTime = 5 #secs
|
||||
def mainloop():
|
||||
while True:
|
||||
dev = usb.core.find(idVendor=idVendor, idProduct=idProduct)
|
||||
time.sleep(sleepTime) #in jedem Fall etwas warten
|
||||
if dev is None:
|
||||
printwrite("No USB Scanner. Restarting mainloop {0}".format(sleepTime))
|
||||
mainloop()
|
||||
else:
|
||||
try:
|
||||
detached = dev.is_kernel_driver_active(0)
|
||||
if detached is False:
|
||||
printwrite("USB Scanner found! Device is already detached")
|
||||
if detached is True:
|
||||
dev.detach_kernel_driver(0)
|
||||
#journal.write("Detached USB device from kernel driver")
|
||||
printwrite("USB Scanner found! Detached from kernel driver")
|
||||
|
||||
cfg = dev.get_active_configuration()
|
||||
intf = cfg[(0,0)]
|
||||
ep = usb.util.find_descriptor(intf, custom_match = lambda e: \
|
||||
usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
|
||||
assert ep is not None, "Endpoint for USB device not found. Something is wrong."
|
||||
while cfg is not None:
|
||||
print("... in the mainloop")
|
||||
# Wait up to 1 seconds for data
|
||||
data = ep.read(1000)
|
||||
line = hid2ascii(data)
|
||||
if len(line) == 5: #wir akzepieren nur Codes, die 5-stellig sind (so, wie sie auf dem Ticket sind)
|
||||
#printwrite(line)
|
||||
sql = "INSERT INTO scans(unixtimestamp_created,barcode,validated,hint,unixtimestamp_checked,skipped) VALUES(?,?,?,?,?,?)"
|
||||
with closing(sqlite3.connect(db_file)) as conn:
|
||||
with closing(conn.cursor()) as cur:
|
||||
#standardmäßig validated=0 und leerer Hinweis. Das wird vom GUI befüllt!
|
||||
ts = time.time_ns()
|
||||
res = cur.execute(sql,(int(ts),line,0,None,None,0))
|
||||
printwrite("{0}: Inserting {1} into database".format(
|
||||
datetime.fromtimestamp(int(str(ts)[0:10]), pytz.timezone("Europe/Berlin")).strftime('%d.%m.%Y %H:%M:%S'),
|
||||
line))
|
||||
conn.commit()
|
||||
time.sleep(0.1)
|
||||
except KeyboardInterrupt:
|
||||
printwrite("Stopping program")
|
||||
break
|
||||
except usb.core.USBError as e:
|
||||
printwrite("Device disappeared. Restarting mainloop | Error: {}".format(e))
|
||||
#Error: [Errno None] Configuration not set
|
||||
#Error: [Errno 5] Input/Output Error
|
||||
#Error: [Errno 16] Resource busy
|
||||
#Error: [Errno 13] Access denied (insufficient permissions)
|
||||
#Error: [Errno 19] No such device (it may have been disconnected)
|
||||
#Error: [Errno 110] Operation timed out
|
||||
time.sleep(sleepTime)
|
||||
#continue
|
||||
mainloop()
|
||||
mainloop()
|
16
usb-scanner.service
Normal file
16
usb-scanner.service
Normal file
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=USB Scanner Eyoyo Kugelstossmeeting Rochlitz
|
||||
After=network.target
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
Type=simple
|
||||
Restart=always
|
||||
#RestartSec=1
|
||||
ExecStartPre=/usr/bin/sleep 1
|
||||
ExecStart=/opt/Kugelstossmeeting-Scanner/venv/bin/python3 "/opt/Kugelstossmeeting-Scanner/usb-scanner.py"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
Loading…
x
Reference in New Issue
Block a user