25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
k4ever/client-barcode/freitagskasse.py

583 lines
15 KiB

#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
# Licensed under GPL v3 or later
from __future__ import print_function
import freitagslib.network as net
from freitagslib.commands import BuyCommand, DepositCommand
import colorama
from colorama import Fore, Style
import sys
from decimal import Decimal
import os
import time
import urllib2
from thread import start_new_thread, allocate_lock
import time
COLOR_HINT = Fore.YELLOW + Style.BRIGHT
COLOR_ERROR = Fore.RED
COLOR_DEPOSIT = Fore.GREEN + Style.BRIGHT
COLOR_TO_GO = Fore.MAGENTA + Style.BRIGHT
COLOR_WITHDRAW = Fore.RED + Style.BRIGHT
COLOR_SOME = Fore.WHITE + Style.BRIGHT
COLOR_MUCH = Fore.YELLOW + Style.BRIGHT
COLOR_RESET = Style.RESET_ALL
display_fifo = None
scroll_line1 = None
scroll_line2 = None
idlemessage = None
offset_line1 = 0
offset_line2 = 0
brightness = 5
screensaver = 0
SCREENSAVER_DIM = 30
SCREENSAVER_TIMEOUT = 90
SCREENSAVER_OFF = 300
lock = allocate_lock()
#Helper-Function, scrolls text in a specific line.
def display_scroll_text(line,text):
global scroll_line1,scroll_line2
global offset_line1,offset_line2
lock.acquire()
if (line==1): #clear the line on invocation:
send_display("\x1f\x24\x01%c\x18" % chr(line) )
scroll_line1 = text
offset_line1=0
if (line==2): #clear the line on invocation:
send_display("\x1f\x24\x01%c\x18" % chr(line) )
scroll_line2 = text
offset_line2=0
lock.release()
def display_handle_scroll(line,text,offset):
if (text):
l = len(text)
if (l<21):
if (offset == 0):
send_display("\x1f\x24\x01%c%s" % (chr(line),text[offset:(20+offset)]))
offset=1
else:
send_display("\x1f\x24\x01%c%s" % (chr(line),text[offset:(20+offset)]))
missing_chars=20+offset-l
if (missing_chars>0):
send_display(text[:missing_chars])
offset=((offset+1)%l)
else:
offset=0 #reset offset
return offset
def display_cmd_dim(x):
global brightness
if (brightness!=x):
if (x==0): #turn off:
send_display("\x1F\x45%c" % chr(255) )
brightness = x
return
else:
if (brightness==0): #turn on, then set wanted brightness:
send_display("\x1F\x45%c" % chr(0) )
brightness = x
send_display("\x1F\x58%c" % chr(x-1) )
def display_thread(x):
global scroll_line1,scroll_line2
global offset_line1,offset_line2
global screensaver
global idlemessage
offset_line1=0
offset_line2=0
while(True):
lock.acquire()
offset_line1 = display_handle_scroll(1,scroll_line1,offset_line1)
offset_line2 = display_handle_scroll(2,scroll_line2,offset_line2)
if (screensaver <= SCREENSAVER_OFF):
screensaver=screensaver+1
if ((screensaver >= SCREENSAVER_DIM) and (screensaver <= (SCREENSAVER_DIM+7))): #activate first stage of screensaver:
x = (8-( screensaver - SCREENSAVER_DIM))/2
display_cmd_dim(1+x)
if (screensaver == SCREENSAVER_TIMEOUT):
now = time.localtime()
send_display("\x0c\x1F\x54%c%c\x1f\x03" % (chr(now.tm_hour),chr(now.tm_min)));
if (scroll_line2):
scroll_line1=scroll_line2
else:
scroll_line1=idlemessage
scroll_line2=None
offset_line1=0
offset_line2=0
if (screensaver == SCREENSAVER_OFF):
display_cmd_dim(0)
lock.release()
time.sleep(.5)
def send_display(s):
global display_fifo
if not display_fifo:
try:
fd = os.open( "/tmp/display", os.O_WRONLY)
display_fifo = os.fdopen(fd,"w")
except OSError:
display_fifo = None
pass
return
except IOError:
display_fifo = None
pass
return
try:
display_fifo.write(s)
display_fifo.flush()
except IOError:
display_fifo = None
pass
return
#Front-End Funtion to display a Screen
# with heading
def display_screen(title,message):
global scroll_line1,scroll_line2
global offset_line1,offset_line2
global screensaver
offset_line1=0
offset_line2=0
screensaver=0
display_cmd_dim(5)
if (len(title)<21):
scroll_line1=None
send_display("\x1f\x24\x01%c\x18%s" % (chr(1),'\xdb'*20) )
if (len(title)<20):
pos=1+(20-len(title))/2
else:
pos=1
send_display("\x1f\x24%c%c%s" % (chr(pos),chr(1),title) )
else:
display_scroll_text(1,title)
display_scroll_text(2,message)
#Front-End function to send data to the display.
def print_display(s):
global scroll_line1,scroll_line2
global offset_line1,offset_line2
global screensaver
lock.acquire()
screensaver=0
offset_line1=0
offset_line2=0
scroll_line1=None
scroll_line2=None
display_cmd_dim(5)
send_display(s)
lock.release()
def clear():
os.system('clear')
TO_GO_ONLY = 2
TO_GO_ALL = 1
TO_GO_NONE = 0
TO_GO_PREV = -1
CODES = {
'UNDO':('undo',),
'COMMIT':('commit',),
'TO GO ALL':('to_go', TO_GO_ALL),
'TO GO NONE':('to_go', TO_GO_NONE),
'TO GO PREV':('to_go', TO_GO_PREV),
'TO GO ONLY':('to_go', TO_GO_ONLY),
'DEPOSIT 0.01':('deposit', Decimal('0.01')),
'DEPOSIT 0.05':('deposit', Decimal('0.05')),
'DEPOSIT 0.10':('deposit', Decimal('0.10')),
'DEPOSIT 0.50':('deposit', Decimal('0.50')),
'DEPOSIT 1.00':('deposit', Decimal('1.00')),
'DEPOSIT 5.00':('deposit', Decimal('5.00')),
'DEPOSIT 10.00':('deposit', Decimal('10.00')),
'DEPOSIT 50.00':('deposit', Decimal('50.00')),
}
def delay(what, seconds):
for i in xrange(seconds, 0, -1):
if i < seconds:
sys.stdout.write('\r')
sys.stdout.write('%s in %d Sekunden... ' % (what, i))
sys.stdout.flush()
time.sleep(1)
def warn_balance():
print('Kontostand im Minus, bitte Geld aufladen.')
def error_page(error_message, hint_message=None):
clear()
print(COLOR_ERROR + error_message + COLOR_RESET)
print()
delay_seconds = 3
if hint_message is not None:
print(COLOR_HINT + hint_message + COLOR_RESET)
print()
delay_seconds += 3
delay('Weiter', delay_seconds)
class Status:
def __init__(self):
self._reset()
self.item_cache = dict()
def _reset(self):
self.auth_blob = None
self.login_name = None
self.balance = None
self.transfers = None
def dump(self):
def sign(amount, plus='+'):
return '-' if amount < 0 else plus
def color(amount):
return COLOR_WITHDRAW if amount < 0 else COLOR_DEPOSIT
def show_total(balance, plus=' '):
print('%3s %-40s %s%c %6.2f Euro%s' \
% ('', '', color(balance), sign(balance, plus),
abs(balance), COLOR_RESET))
def show_item(position, diff, label, color):
print('%2d) %-40s %s%c %6.2f Euro%s' \
% (position, label, color, sign(diff),
abs(diff), COLOR_RESET))
def show_bar():
print('%3s %-40s %13s' % ('', '', '============='))
if self.logged_in():
print('Eingeloggt als: %s%s%s' % (COLOR_SOME, self.login_name, COLOR_RESET))
print()
print_display('\x0cHallo %-14s' % (self.login_name[:13]+"!") )
if self.transfers:
initial_command, initial_balance = self.transfers[0]
print('Geplante Änderungen:')
show_total(initial_balance)
i = 1
for command, dummy in self.transfers:
if isinstance(command, BuyCommand):
if (command.includes_commodity()):
show_item(i, -command.commodity_value(), command.commodity_label(), COLOR_WITHDRAW)
i += 1
if (command.includes_deposit()):
show_item(i, -command.deposit_value(), command.deposit_label(), COLOR_TO_GO)
i += 1
else:
show_item(i, command.difference(), command.label(), COLOR_DEPOSIT)
i += 1
show_bar()
if isinstance(command, BuyCommand):
mycmd = 0;
if (command.includes_commodity()):
mycmd+=1;
if (command.includes_deposit()):
mycmd+=2;
if (mycmd==1):
mylabel=command.item_name()
if (mycmd==2):
mylabel="Pfand "+command.commodity_label()[:9]
if (mycmd==3):
mylabel=("%-13s" % (command.commodity_label()[:13]))+"+P"
print_display('\x0b%-15s %4.2f' % (mylabel[:15],abs(command.difference())));
print_display('\x0b\nSUMME: {%02i} %8.2f' % ((i-1),initial_balance - self.balance));
else:
print_display('\x0b%-15s %4.2f' % (command.label()[:15],abs(command.difference())));
print_display('\x0b\nSUMME: {%02i} %8.2f' % ((i-1),initial_balance - self.balance));
if len(self.transfers) > 1:
show_total(self.balance - initial_balance, plus='+')
show_bar()
show_total(self.balance)
if self.balance < 0:
warn_balance()
print_display('\x0b\nKonto: %5.2f!' % (self.balance) )
print()
print(COLOR_SOME + 'Committen nicht vergessen.' + COLOR_RESET)
else:
print('Kontostand beträgt: %s%.2f Euro%s' % (COLOR_MUCH, self.balance, COLOR_RESET))
display_screen("KONTOSTAND","%s: %.2f Euro" % (self.login_name,self.balance))
if self.balance < 0:
print()
warn_balance()
else:
print(COLOR_MUCH + 'Bitte einloggen.' + COLOR_RESET)
print()
print('Scanne dazu deine ID-Karte mit dem Barcode-Leser.')
display_screen("LOGIN","Bitte scanne Deinen login-Token! *** ")
print()
def logged_in(self):
return self.auth_blob is not None
def login(self, auth_blob):
assert(not self.logged_in())
user_name = net.get_user_name_from_auth_blob(auth_blob)
balance = net.get_balance(user_name)
self.auth_blob = auth_blob
self.login_name = user_name
self.balance = balance
self.transfers = list()
def commit(self):
assert(self.logged_in())
# Process command queue
for (command, balance_backup) in list(self.transfers):
try:
command.run(self.login_name)
except urllib2.HTTPError as e:
print_display('\x0cFEHLER: Server Error%20s' % str(e)[:20])
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
break
else:
self.transfers.pop(0)
if not self.transfers:
# Show final balance for some time
clear()
self.dump()
print_display('\x0bDanke, %-12s' % (self.login_name[:12]+"!") )
delay('Logout', 3)
# Logout
self._reset()
def find(self, barcode):
try:
return self.item_cache[barcode]
except KeyError:
item = net.get_item(barcode)
self.item_cache[barcode] = item
return item
def buy(self, item):
assert(self.logged_in())
log_entry = (BuyCommand(item), self.balance)
self.transfers.append(log_entry)
self.balance = self.balance - item.price
def deposit(self, amount):
assert(self.logged_in())
log_entry = (DepositCommand(amount), self.balance)
self.transfers.append(log_entry)
self.balance = self.balance + amount
def to_go(self, scope):
_PRODUCT_FIRST = 'FEHLER: Bitte zuerst den betreffenden Artikel scannen.'
if not self.transfers:
error_page(_PRODUCT_FIRST)
return
if scope == TO_GO_ALL:
"""
Makes all BuyCommands with deposit > 0 include deposit
...and updates future balance accordingly.
"""
dummy, initial_balance = self.transfers[0]
balance_before = initial_balance
for command, dummy in self.transfers:
if isinstance(command, BuyCommand) \
and not command.includes_deposit() \
and command.deposit_value() > 0:
command.include_deposit(True)
balance_before += command.difference()
self.balance = balance_before
elif scope == TO_GO_NONE:
"""
Makes all BuyCommands that include commodity
not include deposit any more
...and updates future balance accordingly.
"""
first_command, initial_balance = self.transfers[0]
balance_before = initial_balance
for command, dummy in self.transfers:
if isinstance(command, BuyCommand) \
and command.includes_commodity():
command.include_deposit(False)
balance_before += command.difference()
self.balance = balance_before
elif scope == TO_GO_PREV:
"""
Makes the last BuyCommand include deposit
...and updates future balance accordingly.
"""
prev, balance_backup = self.transfers[-1]
if not isinstance(prev, BuyCommand):
error_page(_PRODUCT_FIRST)
return
if prev.includes_deposit():
print_display('\x0cFEHLER: schon Pfand %20s' % prev.item_name()[:20])
error_page('FEHLER: Pfand für Produkt "%s" bereits aktiviert' % prev.item_name())
return
if prev.deposit_value() <= 0:
print_display('\x0cFEHLER: Pfandfrei! %20s' % prev.item_name()[:20])
error_page('FEHLER: Produkt "%s" hat kein Pfand' % prev.item_name())
return
before = prev.difference()
prev.include_deposit(True)
after = prev.difference()
self.balance += (after - before)
elif scope == TO_GO_ONLY:
"""
Makes all BuyCommand that include commodity
be deposit only
...and updates future balance accordingly.
"""
dummy, initial_balance = self.transfers[0]
balance_before = initial_balance
for command, dummy in self.transfers:
if isinstance(command, BuyCommand) \
and command.deposit_value() > 0 \
and command.includes_commodity():
command.include_commodity(False)
command.include_deposit(True)
balance_before += command.difference()
self.balance = balance_before
def undo(self):
assert(self.logged_in())
if self.transfers:
dummy, balance_backup = self.transfers[-1]
self.transfers.pop()
self.balance = balance_backup
else:
error_page('FEHLER: Nichts da, was ich rückgängig machen könnte.')
def print_prompt():
sys.stdout.write(">>> ")
def handle(line, status):
global idlemessage
if line == 'exit':
clear()
print_display("\x0c")
sys.exit(0)
if status.logged_in():
idlemessage=" Comitten nicht vergessen! ***"
if line in CODES:
call = CODES[line]
method = call[0]
params = call[1:]
getattr(status, method)(*params)
else:
try:
item = status.find(line)
except urllib2.HTTPError as e:
if e.code == 404: # URL not found == item not found with REST
print_display('\x0cERROR: %13sCode ist unbekannt' % ( line[:13]))
error_page('FEHLER: Aktion oder Ware "%s" nicht bekannt' % line)
else:
print_display('\x0cERROR: Server Error%20s' % str(e)[:20])
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
else:
status.buy(item)
else:
idlemessage="Mir ist langweilig!"
try:
status.login(line)
except urllib2.HTTPError as e:
if e.code == 404: # URL not found == user unknown
display_screen("FEHLER","Nutzer ist unbekannt: '%s' *** " % line)
error_page('FEHLER: Benutzer "%s" nicht bekannt' % line,
hint_message='Ist in der WebApp unter "Einstellungen" ' \
'für Ihren Account Plugin "BarcodePlugin" ' \
'als erlaubt markiert?')
else:
print_display('\x0cFEHLER: Server Error%20s' % str(e)[:20])
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
except urllib2.URLError as e:
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
def main():
colorama.init()
status = Status()
global scroll_line1,scroll_line2
print_display("\x1b\x40\x1f\x43\x00")
start_new_thread(display_thread,(1,))
#display_scroll_text(1,"Line1: Text, dumm scrollend!")
#display_scroll_text(4,"Line2: Und hier Text, auch dumm scrollend!")
display_screen("HINWEIS","Herzlich willkommen bei der Freitagsrunde! *** ")
while True:
clear()
status.dump()
print_prompt()
l = sys.stdin.readline()
if not l:
break
line = l.rstrip()
print_display('\x0cBarcode:\n%20s' % line[:20])
if line:
handle(line, status)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
pass
print_display("\x1b\x40Goodbye!")