#! /usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2011 Sebastian Pipping # 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!")