Initial commit

This commit is contained in:
Mario Voigt 2025-01-06 20:02:09 +01:00
commit c0f2c0dfe6
10 changed files with 607 additions and 0 deletions

Binary file not shown.

Binary file not shown.

0
README.md Normal file
View File

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

44
icon.svg Normal file
View 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

Binary file not shown.

View 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
View 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
View 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
View 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