Commit 9afecd12 authored by siguhe's avatar siguhe

Merge branch 'master' of https://git.omegav.no/ov/neokomp

parents 06393309 0171f349
......@@ -4,3 +4,5 @@
*/Hardware/History
*/Hardware/__Previews
*/Hardware/Project\ Logs*
*/__pycache__
This diff is collapsed.
......@@ -5,6 +5,7 @@ import urllib.parse
import urllib.request
import time
import json
import traceback
def AJAX_search_component_pos(rom,seksjon,hylle,rad,kolonne,dybde): #rom can be 'OV', 'GM' or 'VendingMachine'
try:
......@@ -64,18 +65,20 @@ def AJAX_search_component_name(navn):
search_content_list.append(search_content[result]['id'])
return search_content_list #list of IDs, may add items which have antall=0, this has not been checked yet.
def API_buy(rfid,rom,sek,hylle,row,col,dybde,api_key):
def API_buy(rfid,rom,sek,hylle,row,col,dybde,api_key,pin_code):
try:
url = 'https://omegav.no:8091/api/komp.php'
data = urllib.parse.urlencode({'handling' : 'kjop',
'rfid' : rfid,
'rom' : rom,
'sek' : sek,
'hylle' : hylle,
'row' : row,
'col' : col,
'dybde' : dybde,
'key' : api_key})
'rfid' : rfid,
'rom' : rom,
'sek' : sek,
'hylle' : hylle,
'row' : row,
'col' : col,
'dybde' : dybde,
'key' : api_key,
'pin' : pin_code
})
data = data.encode('utf-8')
request = urllib.request.Request(url,data)
......@@ -89,7 +92,7 @@ def API_buy(rfid,rom,sek,hylle,row,col,dybde,api_key):
print("exception")
return False
def API_find_user(rfid,api_key):
def API_find_user(rfid, api_key):
url = 'https://omegav.no:8091/api/komp.php'
data = urllib.parse.urlencode({'handling' : 'bruker', 'rfid' : rfid, 'key' : api_key})
data = data.encode('utf-8')
......@@ -99,3 +102,16 @@ def API_find_user(rfid,api_key):
def API_get_balance(rfid, api_key):
try:
url = 'https://omegav.no:8091/api/komp.php'
data = urllib.parse.urlencode({'handling' : 'saldo', 'rfid' : rfid, 'key' : api_key})
data = data.encode('utf-8')
request = urllib.request.Request(url,data)
response = urllib.request.urlopen(request).read().decode('utf8')
user_balance = json.loads(response)
except Exception as exc:
print(traceback.format_exc())
print(exc)
user_balance = { 'error' : "API error." }
return user_balance
import os
import subprocess
import re
import gatt
from PyQt5 import QtCore, QtGui, QtWidgets
SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123"
BUTTON_UUID = "00001524-1212-efde-1523-785feabcd123"
LED_UUID = "00001525-1212-efde-1523-785feabcd123"
class BLEThread(QtCore.QThread):
# This signal will be emitted each time the BLE manager receives a button
# order
button_pressed = QtCore.pyqtSignal("PyQt_PyObject")
def __init__(self, parent, device_mapping):
super().__init__()
self.parent = parent
self.device_mapping = device_mapping
# Find primary Bluetooth controller.
ctrl_info = subprocess.check_output(['btmgmt', 'info']).decode('utf-8')
pattern = r"([a-zA-Z]+[0-9]+):\tPrimary controller"
primary_ctrl = re.search(pattern, ctrl_info).group(1)
# Make sure ble is on and not currently connected
os.system("bluetoothctl power off")
os.system("bluetoothctl power on")
# Create BLE Manager
self.manager = BLEManager(parent=self, adapter_name=primary_ctrl)
# Connect signal
self.button_pressed.connect(parent.ble_button_pressed)
def __del__(self):
self.wait()
def run(self):
self.manager.start_discovery()
self.manager.run()
def ble_order_position(self, position):
print("Button order: %s" % position)
self.button_pressed.emit(position)
class BLEManager(gatt.DeviceManager):
def __init__(self, *args, **kwargs):
self.parent = kwargs.pop('parent')
super().__init__(*args, **kwargs)
def device_discovered(self, device):
if device.mac_address.upper() in self.parent.device_mapping.keys():
module_device = ModuleDevice(parent=self.parent, mac_address=device.mac_address, manager=self)
module_device.connect()
class ModuleDevice(gatt.Device):
def __init__(self, *args, **kwargs):
self.parent = kwargs.pop('parent')
super().__init__(*args, **kwargs)
def connect_succeeded(self):
super().connect_succeeded()
print("[%s] Connected" % (self.mac_address))
def connect_failed(self, error):
super().connect_failed(error)
print("[%s] Connection lost: %s" % (self.mac_address, str(error)))
def disconnect_succeeded(self):
super().disconnect_succeeded()
print("[%s] Disconnected" % (self.mac_address))
def services_resolved(self):
super().services_resolved()
print("[%s] Resolved services" % (self.mac_address))
for service in self.services:
for characteristic in service.characteristics:
if characteristic.uuid == BUTTON_UUID:
dbus_value = characteristic.read_value()
print(dbus_value)
values = [int(v) for v in dbus_value]
print(values)
order = {}
if values != None and len(values) == 4:
order['rom'] = 'OV'
order['seksjon'] = self.parent.device_mapping[self.mac_address.upper()]
order['hylle'] = values[0]
order['rad'] = values[1]
order['kolonne'] = values[2]
order['dybde'] = values[3]
self.parent.ble_order_position(order)
else:
print("Failed to fetch values")
# DBus is taking its time getting back with a reply on self.disconnect(), which blocks
# the whole process, so we offload this work to a different process.
# TODO: Find a nicer way of doing this.
#self.disconnect()
subprocess.Popen(["/usr/bin/bt-device", "--remove=%s" % self.mac_address])
import sys
import traceback
import math
from PyQt5 import QtCore, QtGui, QtWidgets
from frames.FrameBase import GenericFrame, TimedFrame
import api_communication as API
BALANCE_TIMEOUT_MS = 10000
class Balance(GenericFrame, TimedFrame):
def __init__(self, **kwargs):
super(Balance, self).__init__(**dict(kwargs, timeout=BALANCE_TIMEOUT_MS))
self.parent = kwargs['parent']
self.init_layout()
## Install/Enable custom eventFilter()
self.installEventFilter(self)
def init_layout(self):
title_font = QtGui.QFont()
title_font.setPointSize(20)
self.title = QtWidgets.QLabel("Balance")
self.title.setFont(title_font)
self.layout.addWidget(self.title)
self.layout.addStretch()
name_font = QtGui.QFont()
name_font.setPointSize(30)
self.user_name = QtWidgets.QLabel("")
self.user_name.setFont(name_font)
self.user_balance = QtWidgets.QLabel("")
self.user_balance.setFont(name_font)
self.layout.addWidget(self.user_name)
self.layout.addWidget(self.user_balance)
self.layout.addStretch()
## Timeout indicator
timeout_indicator_font = QtGui.QFont()
timeout_indicator_font.setPointSize(14)
self.timeout_indicator = QtWidgets.QLabel("")
self.timeout_indicator.setFont(timeout_indicator_font)
self.layout.addWidget(self.timeout_indicator)
## Help text
self.help_text = """\
Press <Return> to return to Main screen.
Start typing to search for a component."""
self.help_label = QtWidgets.QLabel(self.help_text)
self.layout.addWidget(self.help_label)
# Navigation buttons
self.navigation_buttons = QtWidgets.QHBoxLayout()
self.goto_guide_button = QtWidgets.QPushButton("Go to Guide", self)
self.goto_guide_button.clicked.connect(lambda : self.parent.show_frame('Guide'))
self.navigation_buttons.addWidget(self.goto_guide_button)
self.goto_search_button = QtWidgets.QPushButton("Go to Search", self)
self.goto_search_button.clicked.connect(lambda : self.parent.show_frame('Search'))
self.navigation_buttons.addWidget(self.goto_search_button)
self.goto_cart_button = QtWidgets.QPushButton("Go to Cart", self)
self.goto_cart_button.clicked.connect(lambda : self.parent.show_frame('Cart'))
self.navigation_buttons.addWidget(self.goto_cart_button)
self.goto_receipt_button = QtWidgets.QPushButton("Go to Receipt", self)
#self.goto_receipt_button.clicked.connect(lambda : self.parent.show_frame('Receipt'))
self.goto_receipt_button.clicked.connect(self.show_balance)
self.navigation_buttons.addWidget(self.goto_receipt_button)
self.layout.addLayout(self.navigation_buttons)
# Capture search text and <Return> anywhere
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.ShortcutOverride:
if event.text().isalnum():
self.parent.show_frame('Search')
self.clear_balance()
self.stop_timer()
return False # ?
if event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Return:
self.parent.show_frame('Guide')
self.clear_balance()
self.stop_timer()
return True
# Pass event to QWidget
return super(Balance, self).eventFilter(obj, event)
@QtCore.pyqtSlot()
def timeout(self):
#Clear it and go to guide
self.clear_balance()
self.parent.show_frame('Guide')
@QtCore.pyqtSlot()
def update_timeout_indicator(self):
if self.timeout_timer.isActive():
time = self.timeout_timer.remainingTime()
time = math.ceil(time / 1000) # ms to s
self.timeout_indicator.setText("Timeout in " + str(time) + "s")
else:
self.timeout_indicator.setText("")
def show_balance(self, rfid):
user_balance = API.API_get_balance(rfid, self.parent.API_key)
try:
self.user_name.setText("Username: " + user_balance['brukernavn'])
self.user_balance.setText("Your current balance: " + user_balance['saldo'] + ",-")
except Exception as exc:
traceback.print_exc()
print("user_balance: %s" % user_balance)
self.user_name.setText("RFID %s not found." % rfid)
self.user_balance.setText("Please bother a OV noechelmedlem.")
self.timeout_timer.start(BALANCE_TIMEOUT_MS)
def clear_balance(self):
self.user_name.setText("Username: ")
self.user_balance.setText("Your current balance: ")
def focus(self):
return
This diff is collapsed.
from PyQt5 import QtCore, QtGui, QtWidgets
class GenericFrame(QtWidgets.QWidget):
def __init__(self, **kwargs):
super(GenericFrame, self).__init__(**kwargs)
self.generic_layout_init()
def generic_layout_init(self):
## General vertical layout
self.layout = QtWidgets.QVBoxLayout(self)
# This function should be redefined by those frames that need special
# actions on focus
def focus(self):
return
class TimedFrame(QtWidgets.QWidget):
def __init__(self, **kwargs):
timeout = kwargs.pop('timeout')
super(TimedFrame, self).__init__(**kwargs)
self.parent = kwargs['parent']
print(kwargs)
self.TIMEOUT_MS = timeout
self.timer_init()
def timer_init(self):
# Timeout is SingleShot timer
self.timeout_timer = QtCore.QTimer()
self.timeout_timer.setSingleShot(True)
self.timeout_timer.timeout.connect(self.timeout)
self.parent.indicator_timer.timeout.connect(self.update_timeout_indicator)
def restart_timer(self):
if self.timeout_timer.isActive():
self.timeout_timer.stop()
self.timeout_timer.start(self.TIMEOUT_MS)
def stop_timer(self):
self.timeout_timer.stop()
from PyQt5 import QtCore, QtGui, QtWidgets
from frames.FrameBase import GenericFrame
class Guide(GenericFrame):
"""
The Guide frame displays instructional text on how to use the store
"""
def __init__(self, **kwargs):
super(Guide, self).__init__(**kwargs)
self.parent = kwargs['parent']
self.init_layout()
## Install/Enable custom eventFilter()
self.installEventFilter(self)
def init_layout(self):
## Title
title_font = QtGui.QFont()
title_font.setPointSize(20)
self.title = QtWidgets.QLabel("How-To")
self.title.setFont(title_font)
self.layout.addWidget(self.title)
self.layout.addStretch()
## Help text
self.help_text = """\
This is a little help text to explain how this store works.
Scan your RFID-card to check your balance.
Start typing to search for a component, which can be added to your cart.
TODO: Scan a bar code to add the item to your cart.
Select a component via a section module to add the item to your cart."""
self.help_label = QtWidgets.QLabel(font=title_font)
self.help_label.setText(self.help_text)
self.layout.addWidget(self.help_label)
self.layout.addStretch()
# Navigation buttons
self.navigation_buttons = QtWidgets.QHBoxLayout()
self.goto_search_button = QtWidgets.QPushButton("Go to Search", self)
self.goto_search_button.clicked.connect(lambda : self.parent.show_frame('Search'))
self.navigation_buttons.addWidget(self.goto_search_button)
self.goto_cart_button = QtWidgets.QPushButton("Go to Cart", self)
self.goto_cart_button.clicked.connect(lambda : self.parent.show_frame('Cart'))
self.navigation_buttons.addWidget(self.goto_cart_button)
self.goto_receipt_button = QtWidgets.QPushButton("Go to Receipt", self)
self.goto_receipt_button.clicked.connect(lambda : self.parent.show_frame('Receipt'))
self.navigation_buttons.addWidget(self.goto_receipt_button)
self.goto_balance_button = QtWidgets.QPushButton("Go to Balance", self)
self.goto_balance_button.clicked.connect(lambda : self.parent.show_frame('Balance'))
self.navigation_buttons.addWidget(self.goto_balance_button)
self.layout.addLayout(self.navigation_buttons)
# Install a custom eventFilter to focus switch to search when starting to write
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.ShortcutOverride:
if event.text().isalnum():
self.parent.show_frame('Search')
return False # ?
# Pass other events to widget
return super(Guide, self).eventFilter(obj, event)
import sys
import math
from PyQt5 import QtCore, QtGui, QtWidgets
from frames.FrameBase import GenericFrame, TimedFrame
import api_communication as API
RECEIPT_TIMEOUT_MS = 10000
class Receipt(GenericFrame, TimedFrame):
def __init__(self, **kwargs):
super(Receipt, self).__init__(**dict(kwargs, timeout=RECEIPT_TIMEOUT_MS))
self.parent = kwargs['parent']
self.init_layout()
## Install/Enable custom eventFilter()
self.installEventFilter(self)
def init_layout(self):
title_font = QtGui.QFont()
title_font.setPointSize(20)
self.title = QtWidgets.QLabel("Receipt", font=title_font)
self.layout.addWidget(self.title)
## TODO: Add image?
self.layout.addStretch(1)
## Purchase information
receipt_font = QtGui.QFont()
receipt_font.setPointSize(50)
self.purchase_info = QtWidgets.QHBoxLayout()
self.purchase_heads = QtWidgets.QVBoxLayout()
self.purchase_tails = QtWidgets.QVBoxLayout()
self.head1 = QtWidgets.QLabel("", font=title_font)
self.head2 = QtWidgets.QLabel("", font=title_font)
self.head3 = QtWidgets.QLabel("", font=title_font)
self.tail1 = QtWidgets.QLabel("", font=title_font)
self.tail2 = QtWidgets.QLabel("", font=title_font)
self.tail3 = QtWidgets.QLabel("", font=title_font)
self.purchase_heads.addWidget(self.head1)
self.purchase_heads.addWidget(self.head2)
self.purchase_heads.addWidget(self.head3)
self.purchase_tails.addWidget(self.tail1)
self.purchase_tails.addWidget(self.tail2)
self.purchase_tails.addWidget(self.tail3)
self.purchase_info.addStretch(1)
self.purchase_info.addLayout(self.purchase_heads)
self.purchase_info.addLayout(self.purchase_tails)
self.purchase_info.addStretch(1)
self.layout.addLayout(self.purchase_info)
self.layout.addStretch(1)
self.timeout_indicator = QtWidgets.QLabel("")
self.layout.addWidget(self.timeout_indicator)
## Help text
self.help_text = "\
Press <Return> to return to Main screen.\n\
Start typing to search for a component.\n\
TODO: Scan RFID card to show Balance."
self.help_label = QtWidgets.QLabel(self.help_text)
self.layout.addWidget(self.help_label)
# Navigation buttons
self.navigation_buttons = QtWidgets.QHBoxLayout()
self.goto_guide_button = QtWidgets.QPushButton("Go to Guide", self)
self.goto_guide_button.clicked.connect(lambda : self.parent.show_frame('Guide'))
self.navigation_buttons.addWidget(self.goto_guide_button)
self.goto_search_button = QtWidgets.QPushButton("Go to Search", self)
self.goto_search_button.clicked.connect(lambda : self.parent.show_frame('Search'))
self.navigation_buttons.addWidget(self.goto_search_button)
self.goto_cart_button = QtWidgets.QPushButton("Go to Cart", self)
self.goto_cart_button.clicked.connect(lambda : self.parent.show_frame('Cart'))
self.navigation_buttons.addWidget(self.goto_cart_button)
self.goto_balance_button = QtWidgets.QPushButton("Go to Balance", self)
self.goto_balance_button.clicked.connect(lambda : self.parent.show_frame('Balance'))
self.navigation_buttons.addWidget(self.goto_balance_button)
self.layout.addLayout(self.navigation_buttons)
# Capture search text and <Return> anywhere
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.ShortcutOverride:
if event.text().isalnum():
self.parent.show_frame('Search')
self.clear_receipt()
self.stop_timer()
return False # ?
if event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Return:
self.parent.show_frame('Guide')
self.clear_receipt()
self.stop_timer()
return True
# Pass event to QWidget
return super(Receipt, self).eventFilter(obj, event)
@QtCore.pyqtSlot()
def timeout(self):
#Clear it and go to guide
self.clear_receipt()
self.parent.show_frame('Guide')
@QtCore.pyqtSlot()
def update_timeout_indicator(self):
if self.timeout_timer.isActive():
time = self.timeout_timer.remainingTime()
time = math.ceil(time / 1000) # ms to s
self.timeout_indicator.setText("Timeout in " + str(time) + "s")
else:
self.timeout_indicator.setText("")
def attempt_to_buy(self, rfid, items):
# Calculate the total sum of the items
total = 0
for item in items:
try:
price = int(item['pris'])
if price < 0:
price = 0
except:
price = 0
total += price
print("Price for all items: " + str(total))
# Fetch balance of user
user_balance_data = self.get_user_and_balance(rfid)
try:
balance = int(user_balance_data['saldo'])
except:
print("balance not number! setting it to 0")
balance = 0 # TODO: show more specific error
# Display error message if not enough money
if total > balance:
self.show_insuccifient_balance(user_balance_data, total)
return
# Start buying sequence if not
self.do_buy(rfid, user_balance_data, items)
def do_buy(self, rfid, user_balance_data, items):
bought = []
failed = []
# Loop through and order each item in turn
# For each item, check that it was bought
for item in items:
lager = item['lager'][item['lager_choice']]
buy_success = API.API_buy(rfid, lager['rom'], lager['seksjon'], lager['hylle'], lager['rad'], lager['kolonne'], lager['dybde'], self.parent.API_key, None)
if buy_success:
bought.append(item)
else:
failed.append(item)
if len(failed):
print("Failed to buy:")
for item in failed:
print(item)
# Show receipt for the amount of items that were actually bought
self.show_receipt(rfid, bought, failed)
# Check if all items were bought. If not, display huge warning!!
if len(failed):
self.show_failed_items(failed)
def get_user_and_balance(self, rfid):
user_balance = API.API_get_balance(rfid, self.parent.API_key)
if (not isinstance(user_balance, dict)):
print("get_balance: Item not a dict")
return None
if 'brukernavn' not in user_balance.keys() or 'saldo' not in user_balance.keys():
print("get_balance: missing brukernavn or saldo")
print(user_balance)
return None
return user_balance
def show_receipt(self, rfid, bought, failed):