commit c0f2c0dfe631c7ea57ab6d5e75b298519782555d Author: Mario Voigt Date: Mon Jan 6 20:02:09 2025 +0100 Initial commit diff --git a/Eyoyo EY-009P Barcode Scanner.pdf b/Eyoyo EY-009P Barcode Scanner.pdf new file mode 100644 index 0000000..fc88f0e Binary files /dev/null and b/Eyoyo EY-009P Barcode Scanner.pdf differ diff --git a/Eyoyo EY-009P YH1705 Handbuch.pdf b/Eyoyo EY-009P YH1705 Handbuch.pdf new file mode 100644 index 0000000..09c7ad7 Binary files /dev/null and b/Eyoyo EY-009P YH1705 Handbuch.pdf differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..74a38df Binary files /dev/null and b/icon.png differ diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..a8e1ad9 --- /dev/null +++ b/icon.svg @@ -0,0 +1,44 @@ + + + + + + diff --git a/kugelstossmeeting-prod.db b/kugelstossmeeting-prod.db new file mode 100644 index 0000000..9f4dcdf Binary files /dev/null and b/kugelstossmeeting-prod.db differ diff --git a/kugelstossmeeting-ticket-scanner.desktop b/kugelstossmeeting-ticket-scanner.desktop new file mode 100644 index 0000000..0cabf8b --- /dev/null +++ b/kugelstossmeeting-ticket-scanner.desktop @@ -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; + diff --git a/sqlite.py b/sqlite.py new file mode 100755 index 0000000..7145524 --- /dev/null +++ b/sqlite.py @@ -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() diff --git a/usb-scanner.py b/usb-scanner.py new file mode 100644 index 0000000..5984250 --- /dev/null +++ b/usb-scanner.py @@ -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() diff --git a/usb-scanner.service b/usb-scanner.service new file mode 100644 index 0000000..9be7572 --- /dev/null +++ b/usb-scanner.service @@ -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