Compare commits

18 Commits

Author SHA1 Message Date
Martin Quarda
80653dfe11 misplaced start and finish 2025-04-16 15:17:58 +02:00
Martin Quarda
b737934f05 clear usb from applications 2025-04-16 13:09:32 +02:00
Martin Quarda
9227a8bfdb fix 2025-04-09 09:09:28 +02:00
Martin Quarda
5a34e9e4e5 more on_success handlers 2025-04-06 09:20:52 +02:00
Martin Quarda
ef915110a7 registrace.py: update racer only on success 2025-04-06 09:14:13 +02:00
Martin Quarda
19ead426fb reoder to make it works 2025-04-04 18:15:13 +02:00
Martin Quarda
2c9ea8e379 reload_racers button 2025-04-04 11:31:07 +02:00
Martin Quarda
8104096c0b remove /api/station/register at unregister, now server handles it 2025-04-04 10:18:13 +02:00
Martin Quarda
c4d7df8b5d testing dialog 2025-04-04 09:35:03 +02:00
Martin Quarda
03d8045990 small fixes 2025-04-01 20:14:05 +02:00
Martin Quarda
c59b24a561 logs 2025-04-01 11:51:17 +02:00
Martin Quarda
586bd3dc84 unify register and main py worker 2025-04-01 09:35:20 +02:00
Martin Quarda
2e9c11d73b refactor usbcardreader into own file 2025-04-01 09:07:51 +02:00
Martin Quarda
ca8d008e77 first working version 2025-04-01 10:47:02 +02:00
Martin Quarda
bc61d8a0bd small fix 2025-03-28 10:42:29 +01:00
Martin Quarda
2221f5b39d more autonomous 2025-03-28 10:41:17 +01:00
Martin Quarda
9deda461ee added error handling in registrace.py 2025-03-27 17:56:04 +01:00
Martin Quarda
52d81c127d finish up unregister and one variable for host in config 2025-03-27 14:57:32 +01:00
6 changed files with 269 additions and 208 deletions

View File

@@ -1,10 +1,7 @@
{ {
"url": "https://beta.alkator.cz/api/station/register", "station_id": 2,
"station_id": "1", "host": "https://beta.alkator.cz",
"login_url": "https://beta.alkator.cz/api/login",
"racers_url": "https://beta.alkator.cz/api/racers",
"card_register": "https://beta.alkator.cz/api/card/register",
"card_unregister": "https://beta.alkator.cz/api/card/card_unregister",
"login": "station_register@alkator.cz", "login": "station_register@alkator.cz",
"password": "password_heslo" "password": "password_heslo",
"countdown_seconds": 60
} }

1
SW/PC/Stopwatch/log.json Normal file
View File

@@ -0,0 +1 @@
[]

View File

@@ -2,37 +2,44 @@ import gi
import threading import threading
import time import time
import datetime import datetime
import collections
import usb.core
import usb.util
import requests import requests
import json import json
gi.require_version('Gtk', '4.0') gi.require_version('Gtk', '4.0')
from queue import Queue
from gi.repository import Gtk, Gio, Gdk from gi.repository import Gtk, Gio, Gdk
from requests.adapters import HTTPAdapter, Retry
from usbcardreader import UsbCardReader
with open('config.json', 'r') as f:
config = json.load(f)
activeCards = [] activeCards = []
TIME = 600 TIME = config['countdown_seconds']
USB_IF = 0 # Interface
USB_TIMEOUT = 5 # Timeout in ms
USB_VENDOR = 0xffff # Vendor-ID:
USB_PRODUCT = 0x0035 # Product-ID
db = {} db = {}
queue_to_send = [] queue_to_send = []
with open('config.json', 'r') as f:
config = json.load(f)
session = requests.Session() session = requests.Session()
session.post(config['login_url'], {'email':config['login'], 'password': config['password']})
retries = Retry(total=10,
backoff_factor=1,
status_forcelist=[ 500, 502, 503, 504 ])
session.mount('https://', HTTPAdapter(max_retries=retries))
session.post(config['host'] + '/api/login', {'email':config['login'], 'password': config['password']})
def refreshDb(): def refreshDb():
global db global db
resp = session.get(config['racers_url']) resp = session.get(config['host'] + '/api/racers')
if resp.status_code == 200: if resp.status_code == 200:
json = resp.json() json = resp.json()
db = {} db = {}
@@ -47,82 +54,46 @@ def refreshDbEvery3mins():
time.sleep(3 * 60) time.sleep(3 * 60)
Thread(target=refreshDbEvery3mins).start() threading.Thread(target=refreshDbEvery3mins).start()
log = []
def sendQueueToSend(): with open('log.json', 'r') as f:
global queue_to_send log = json.loads(f.read())
queue = Queue()
def worker():
while True: while True:
if len(queue_to_send) > 0: item = queue.get()
to_send = queue_to_send.pop() log.append(item)
with open('log.json', 'w') as f:
f.write(json.dumps(log))
response = session.post(item['url'], json=item['json'])
print(item, response)
response = session.post(config['url'],
to_send, 'application/json' threading.Thread(target=worker).start()
)
if response.status_code != 200:
if response.status_code == 400: def callbackOnCard(card_id):
continue global win, activeCards, db
queue_to_send.append(to_send) #try again later if card_id not in db:
time.sleep(1) refreshDb()
if card_id in db:
if card_id not in activeCards:
activeCards.append(card_id)
win.arb(db[card_id])
print('Card is active now')
else: else:
time.sleep(1) print('Card is already active')
else:
print(f'Card {card_id} not found in db')
Thread(target=sendQueueToSend).start()
class UsbCardReader():
def __init__(self, **kargs):
# Find the HID device by vendor/product ID
self.dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
# Get and store the endpoint
self.endpoint = self.dev[0][(0,0)][0]
if self.dev.is_kernel_driver_active(USB_IF) is True:
self.dev.detach_kernel_driver(USB_IF)
# Claim the device
usb.util.claim_interface(self.dev, USB_IF)
self.receivedNumber = 0
cardread=threading.Thread(target=self.read)
cardread.start()
def read(self):
global win, activeCards, db
print("Waiting for card")
receivedNumber = 0
while True:
control = None
try:
control = self.dev.read(self.endpoint.bEndpointAddress, self.endpoint.wMaxPacketSize, USB_TIMEOUT)
if (control[2] != 40) & (control[2] != 0):
receivedDigit = control[2] - 29
if receivedDigit == 10:
receivedDigit = 0
receivedNumber = 10 * receivedNumber + receivedDigit
if (( control[0] == 0 )) & (( control[2] == 40 )) & (( not receivedNumber == 0 )):
if receivedNumber not in db:
refreshDb()
if receivedNumber in db:
if receivedNumber not in activeCards:
activeCards.append(receivedNumber)
win.arb(db[receivedNumber])
print('Card is active now')
else:
print('Card is already active')
else:
print(f'Card {receivedNumber} not found in db')
receivedNumber = 0
except KeyboardInterrupt:
exit()
except:
pass
time.sleep(0.001)
class GridRow(): class GridRow():
def __init__(self, parent, racer, index): def __init__(self, parent, racer, index):
global queue_to_send global queue_to_send
@@ -151,11 +122,11 @@ class GridRow():
countdown=threading.Thread(target=self.updateTime) countdown=threading.Thread(target=self.updateTime)
countdown.start() countdown.start()
queue_to_send.append({ queue.put({'url': config['host'] + '/api/station/register', 'json': {
'card_id': racer['card_id'], 'card_id': racer['card_id'],
'station_id': config['station_id'], 'station_id': config['station_id'],
'time': datetime.datetime.now().strftime('%d.%m.%Y %H:%M:%S.%f') 'time': datetime.datetime.now().strftime('%d.%m.%Y %H:%M:%S.%f')
}) }})
def printIndex(self, button): def printIndex(self, button):
print(self.index) print(self.index)
@@ -227,7 +198,7 @@ def on_activate(app):
win = GridWindow(application=app) win = GridWindow(application=app)
win.present() win.present()
UsbCardReader() UsbCardReader(callbackOnCard)
app = Gtk.Application(application_id='com.example.App') app = Gtk.Application(application_id='com.example.App')
app.connect('activate', on_activate) app.connect('activate', on_activate)

View File

@@ -1,72 +1,112 @@
import sys import sys
import datetime import datetime
import requests import requests
import threading
import json import json
import usb.core from PyQt6 import QtWidgets, uic, QtGui, QtCore
import usb.util from PyQt6.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
from PyQt6 import QtWidgets, uic, QtGui from queue import Queue
from requests.adapters import HTTPAdapter, Retry
from usbcardreader import UsbCardReader
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
TIME = 600
USB_IF = 0 # Interface
USB_TIMEOUT = 5 # Timeout in ms
USB_VENDOR = 0xffff # Vendor-ID:
USB_PRODUCT = 0x0035 # Product-ID
with open('config.json', 'r') as f: with open('config.json', 'r') as f:
config = json.load(f) config = json.load(f)
session = requests.Session() session = requests.Session()
session.post(config['login_url'], {'email':config['login'], 'password': config['password']}) session.post(config['host'] + '/api/login', {'email':config['login'], 'password': config['password']})
last_card = 1111 retries = Retry(total=10,
backoff_factor=1,
status_forcelist=[ 500, 502, 503, 504 ],)
session.mount('https://', HTTPAdapter(max_retries=retries))
last_card = 0
last_time = datetime.datetime.now() last_time = datetime.datetime.now()
racers = []
response = session.get(config['racers_url']) log = []
racers = response.json()
with open('log.json', 'r') as f:
log = json.loads(f.read())
queue = Queue()
class Worker(QtCore.QThread):
returned_state = Signal(str)
def __init__(self):
super().__init__()
self.returned_state.connect(self.handle_returned_state)
self.worker_thread = threading.Thread(target=self.worker)
self.worker_thread.start()
def worker(self):
while True:
item = queue.get()
log.append({
'url': item['url'],
'json': item['json'],
})
with open('log.json', 'w') as f:
f.write(json.dumps(log))
response = session.post(item['url'], json=item['json'])
if response.status_code == 200:
if item.get('on_success'):
item['on_success']()
if item.get('message'):
self.returned_state.emit(f'{item['message']}')
else:
if item.get('failed'):
self.returned_state.emit(f'{item['failed']}')
print(item, response)
def handle_returned_state(self, state):
mb = QtWidgets.QMessageBox(text=f"{state}")
mb.exec()
worker = Worker()
def register_racer(): def register_racer():
card_id = last_card card_id = last_card
index = window.racers.currentIndex() index = window.racers.currentIndex()
if not index.data():
mb = QtWidgets.QMessageBox(text='Prosím vyberte závodníka!')
mb.exec()
return
racer_id = int(index.data().split(' ')[-1]) racer_id = int(index.data().split(' ')[-1])
try: try:
starting_number = int(window.starting_number.text()) starting_number = max(filter(lambda r: r['starting_number'], racers), key=lambda racer: racer['starting_number'])['starting_number'] + 1
except Exception: except ValueError:
raise starting_number = 1
response = session.post(config['card_register'], json={'racer_id': racer_id, 'starting_number': starting_number, 'card_id': last_card})
if response.status_code != 200:
raise Exception(response.status_code, response.content)
racer = list(filter(lambda x: x['racer_id'] == racer_id, racers))[0] racer = list(filter(lambda x: x['racer_id'] == racer_id, racers))[0]
racer['card_id'] = card_id
racer['starting_number'] = starting_number
updateRacers()
def register_update_racer():
racer['card_id'] = card_id
racer['starting_number'] = starting_number
racer['started'] = False
updateRacers()
def unregister_racer(): queue.put({
card_id = last_card 'url': config['host'] + '/api/card/register',
time = last_time 'json': {'racer_id': racer_id, 'starting_number': starting_number, 'card_id': card_id, 'time': datetime.datetime.now().strftime('%d.%m.%Y %H:%M:%S.%f')},
'message': f"Úspěšné zaregistrování závodníka {racer['first_name']} {racer['last_name']} se startovním číslem {starting_number}!",
response = session.post(config['url'], json={'card_id': card_id, 'time': time.strftime('%d.%m.%Y %H:%M:%S.%f'), 'station_id': config['station_id']}) 'failed': f"Neúspěšná registrace závodníka {racer['first_name']} {racer['last_name']}",
'on_success': register_update_racer,
})
window = uic.loadUi("registrace.ui")
window.register_racer.clicked.connect(register_racer)
window.unregister_racer.clicked.connect(unregister_racer)
window.show()
model = QtGui.QStandardItemModel() model = QtGui.QStandardItemModel()
window.racers.setModel(model)
def updateRacers(): def updateRacers():
model.clear() model.clear()
@@ -80,55 +120,65 @@ def updateRacers():
updateRacers() updateRacers()
def reload_racers():
global racers
response = session.get(config['host'] + '/api/racers')
racers = response.json()
updateRacers()
reload_racers()
def findByCard(card_id):
for racer in racers:
if racer['card_id'] == card_id:
return racer
def updateLastCard(card_id): def updateLastCard(card_id):
global last_card, last_time
if card_id == last_card and datetime.datetime.now() - last_time < datetime.timedelta(seconds=15):
return
time = last_time = datetime.datetime.now()
last_card = card_id
window.lastCard.setText(str(card_id)) window.lastCard.setText(str(card_id))
racer = findByCard(card_id)
if racer:
if racer['started']:
def finish_update_racer():
racer['card_id'] = None
queue.put({
'url': config['host'] + '/api/card/unregister',
'json': {'racer_id': racer['racer_id'], 'starting_number': racer['starting_number'], 'card_id': card_id, 'time': time.strftime('%d.%m.%Y %H:%M:%S.%f')},
'message': f"Úspěšné odhlášení závodníka {racer['starting_number']}!",
'failed': f"Neúspěšné odhlášení karty závodníka {racer['starting_number']}",
'on_success': finish_update_racer,
})
else:
def start_update_racer():
racer['started'] = True
queue.put({
'url': config['host'] + '/api/station/register',
'json': {'card_id': card_id, 'time': time.strftime('%d.%m.%Y %H:%M:%S.%f'), 'station_id': 1},
'message': f"Úspěšné odstartování závodníka {racer['starting_number']}!",
'failed': f"Núspěšné odstartování závodníka {racer['starting_number']}, prosím registraci opakujte!",
'on_success': start_update_racer,
})
class UsbCardReader():
def __init__(self, **kargs):
# Find the HID device by vendor/product ID
self.dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
# Get and store the endpoint
self.endpoint = self.dev[0][(0,0)][0]
if self.dev.is_kernel_driver_active(USB_IF) is True:
self.dev.detach_kernel_driver(USB_IF)
# Claim the device
usb.util.claim_interface(self.dev, USB_IF)
self.receivedNumber = 0
cardread=threading.Thread(target=self.read) UsbCardReader(updateLastCard)
cardread.start()
def read(self):
global last_card, last_time
print("Waiting for card")
receivedNumber = 0
while True:
control = None
try:
control = self.dev.read(self.endpoint.bEndpointAddress, self.endpoint.wMaxPacketSize, USB_TIMEOUT)
if (control[2] != 40) & (control[2] != 0):
receivedDigit = control[2] - 29
if receivedDigit == 10:
receivedDigit = 0
receivedNumber = 10 * receivedNumber + receivedDigit
if (( control[0] == 0 )) & (( control[2] == 40 )) & (( not receivedNumber == 0 )): window = uic.loadUi("registrace.ui")
updateLastCard(receivedNumber) window.racers.setModel(model)
if last_card != receivedNumber: window.register_racer.clicked.connect(register_racer)
last_time = datetime.datetime.now() window.reload_racers.clicked.connect(reload_racers)
last_card = receivedNumber window.show()
receivedNumber = 0
except KeyboardInterrupt:
exit()
except:
pass
time.sleep(0.001) app.exec()
UsbCardReader()
app.exec()

View File

@@ -14,44 +14,10 @@
<string>Registrace závodníků</string> <string>Registrace závodníků</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="5" column="0">
<widget class="QLineEdit" name="lastCard"/>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="register_racer">
<property name="text">
<string>Registrovat</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="unregister_racer">
<property name="text">
<string>Dokončit</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="starting_number"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>startovní číslo</string>
</property>
</widget>
</item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>číslo karty</string>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@@ -61,9 +27,33 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="0" column="1" rowspan="7"> <item row="0" column="0">
<widget class="QPushButton" name="register_racer">
<property name="text">
<string>Registrovat</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="lastCard"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>číslo karty</string>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="5">
<widget class="QListView" name="racers"/> <widget class="QListView" name="racers"/>
</item> </item>
<item row="3" column="0">
<widget class="QPushButton" name="reload_racers">
<property name="text">
<string>Obnovit Závodníky</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@@ -0,0 +1,52 @@
import usb.core
import usb.util
import threading
import time
USB_IF = 0 # Interface
USB_TIMEOUT = 5 # Timeout in ms
USB_VENDOR = 0xffff # Vendor-ID:
USB_PRODUCT = 0x0035 # Product-ID
class UsbCardReader:
def __init__(self, callback):
#fn called on found card
self.callback = callback
# Find the HID device by vendor/product ID
self.dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
# Get and store the endpoint
self.endpoint = self.dev[0][(0,0)][0]
if self.dev.is_kernel_driver_active(USB_IF) is True:
self.dev.detach_kernel_driver(USB_IF)
# Claim the device
usb.util.claim_interface(self.dev, USB_IF)
self.receivedNumber = 0
cardread=threading.Thread(target=self.read)
cardread.start()
def read(self):
receivedNumber = 0
while True:
control = None
try:
control = self.dev.read(self.endpoint.bEndpointAddress, self.endpoint.wMaxPacketSize, USB_TIMEOUT)
if (control[2] != 40) and (control[2] != 0):
receivedDigit = control[2] - 29
if receivedDigit == 10:
receivedDigit = 0
receivedNumber = 10 * receivedNumber + receivedDigit
if ( control[0] == 0 ) and ( control[2] == 40 ) and ( not receivedNumber == 0 ):
self.callback(receivedNumber)
receivedNumber = 0
except KeyboardInterrupt:
exit()
except:
receivedNumber = 0
pass
time.sleep(0.001)