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