From d5896d5d161ee7b476c43958204eb19eb977caf7 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Fri, 14 Jul 2023 23:33:45 +0200 Subject: [PATCH] *: reformat using black --- accept.py | 168 ++++++++++++++++--------------- bitvend-run.py | 15 ++- bitvend/__init__.py | 46 +++++---- bitvend/admin.py | 38 +++---- bitvend/default_settings.py | 44 ++++---- bitvend/forms.py | 30 ++++-- bitvend/graphs.py | 47 +++++---- bitvend/mdb.py | 24 +++-- bitvend/models.py | 87 ++++++++-------- bitvend/processor.py | 88 ++++++++-------- bitvend/stats.py | 8 +- bitvend/utils.py | 11 +- bitvend/views.py | 120 ++++++++++++---------- cygpio/cygpio_test.py | 1 + cygpio/setup.py | 4 +- mdb/backend.py | 17 ++-- mdb/constants.py | 6 +- mdb/device.py | 194 ++++++++++++++++++++++-------------- mdb/utils.py | 16 +-- setup.py | 2 +- 20 files changed, 543 insertions(+), 423 deletions(-) diff --git a/accept.py b/accept.py index 71aa1ca..80f687f 100644 --- a/accept.py +++ b/accept.py @@ -5,127 +5,129 @@ import threading import pprint import requests -INPUT_ADDR = '12fkW5EBb3uBy1zD8pan4TcbabP5Fjato7' # bitvend addr -#INPUT_ADDR = '1MZ6UbznUjoc34pkYyyofWJY42fAoA6k22' # test addr +INPUT_ADDR = "12fkW5EBb3uBy1zD8pan4TcbabP5Fjato7" # bitvend addr +# INPUT_ADDR = '1MZ6UbznUjoc34pkYyyofWJY42fAoA6k22' # test addr + + +def get_exchange_rate(currency="PLN"): + return requests.get("https://blockchain.info/pl/ticker").json()[currency]["last"] -def get_exchange_rate(currency='PLN'): - return requests.get('https://blockchain.info/pl/ticker').json()[currency]['last'] def to_local_currency(sat): # Returns satoshi in local lowest denomination currency (grosze) rate = get_exchange_rate() return int(sat / 1000000.0 * rate) + def process_transaction(tx): - tx_size = tx['x']['size'] - tx_hash = tx['x']['hash'] - tx_value = sum([ - o['value'] for o in tx['x']['out'] if o['addr'] == INPUT_ADDR - ], 0) - fee = sum([i['prev_out']['value'] for i in tx['x']['inputs']]) - \ - sum([o['value'] for o in tx['x']['out']]) + tx_size = tx["x"]["size"] + tx_hash = tx["x"]["hash"] + tx_value = sum([o["value"] for o in tx["x"]["out"] if o["addr"] == INPUT_ADDR], 0) + fee = sum([i["prev_out"]["value"] for i in tx["x"]["inputs"]]) - sum( + [o["value"] for o in tx["x"]["out"]] + ) fee_byte = fee / tx_size print(tx_size, tx_hash, tx_value, fee, fee_byte) print(to_local_currency(tx_value)) + def on_message(ws, message): - #print message + # print message data = json.loads(message) - if data['op'] == 'utx': + if data["op"] == "utx": process_transaction(data) pprint.pprint(data) - for d in data['x']['out']: + for d in data["x"]["out"]: pprint.pprint(d) + def on_error(ws, error): print(error) + def on_close(ws): print("### closed ###") + def on_open(ws): print("### connected ###") - ws.send(json.dumps({ - "op": "addr_sub", - "addr": INPUT_ADDR - })) - ws.send(json.dumps({ - "op": "addr_sub", - "addr": "1MZ6UbznUjoc34pkYyyofWJY42fAoA6k22" - })) + ws.send(json.dumps({"op": "addr_sub", "addr": INPUT_ADDR})) + ws.send( + json.dumps({"op": "addr_sub", "addr": "1MZ6UbznUjoc34pkYyyofWJY42fAoA6k22"}) + ) def run(*args): while True: time.sleep(20) - ws.send(json.dumps({ - "op": "ping" - })) + ws.send(json.dumps({"op": "ping"})) threading.Thread(target=run, daemon=True).start() if __name__ == "__main__": - ''' - process_transaction({'op': 'utx', - 'x': {'hash': '03ed0015c29dfc3bfd3d8a215490d82e3562fe4e8bf5f2ffa737ac8fdc850cc4', - 'inputs': [{'prev_out': {'addr': '1infuHrvt7tuGY8nJcfTwB3wwAR1XwUcW', - 'n': 0, - 'script': '76a91407e72dad6fc3a60ae5c4973bf2a23750713870b188ac', - 'spent': False, - 'tx_index': 93845150, - 'type': 0, - 'value': 300000}, - 'script': '483045022100980b532c7b417b6a7ce4ef1b5e509dda61762ccbbfd35189c2ed7985e6a35d1c02205afacbda7b0e10c6b41100ecee5fc05ab47ca00c85b842923324d55c0db529f8014104e2a76bdeaa387cae1cf920a9a8b54ee4c5e7378ea1985a638f2f3ec609a1d6a54e49e85d10bec55ce9321c5a45e2b21ca8eb1a3e18635405c4812d8467339e9c', - 'sequence': 4294967295}, - {'prev_out': {'addr': '1infuHrvt7tuGY8nJcfTwB3wwAR1XwUcW', - 'n': 0, - 'script': '76a91407e72dad6fc3a60ae5c4973bf2a23750713870b188ac', - 'spent': False, - 'tx_index': 182282259, - 'type': 0, - 'value': 2978800}, - 'script': '483045022100a560606252d83635c784139ae271db498e6528d561105d72c892dc80369036d3022048bfe431f3055d115f8faf0bf2b582912fa226a2f871b44f8bef348a278c8cea014104e2a76bdeaa387cae1cf920a9a8b54ee4c5e7378ea1985a638f2f3ec609a1d6a54e49e85d10bec55ce9321c5a45e2b21ca8eb1a3e18635405c4812d8467339e9c', - 'sequence': 4294967295}, - {'prev_out': {'addr': '17qC9bmMCyaReEPRWksYt99tr8s8YWLrDK', - 'n': 0, - 'script': '76a9144aee06e79fe2f92c3916b0cc0a78478e2434680e88ac', - 'spent': False, - 'tx_index': 93651246, - 'type': 0, - 'value': 407500}, - 'script': '47304402207b0946f973566ec65f4fb3878285274410e10b758806d8b46407d5219ffb36a1022017fdde92072e94bde68271754000636aa94952b91c2d818ed6d88a08cbf900fb0121029f8261faed04d668a78eadf7a3ef845de409e500dace54e604c547702a67b4fc', - 'sequence': 4294967295}], - 'lock_time': 0, - 'out': [{'addr': '1MZ6UbznUjoc34pkYyyofWJY42fAoA6k22', - 'n': 0, - 'script': '76a914e17465c3ef40d44508c7e923140d54c3cd8ced3d88ac', - 'spent': True, - 'tx_index': 209616370, - 'type': 0, - 'value': 1251574}, - {'addr': '1infuHrvt7tuGY8nJcfTwB3wwAR1XwUcW', - 'n': 1, - 'script': '76a91407e72dad6fc3a60ae5c4973bf2a23750713870b188ac', - 'spent': True, - 'tx_index': 209616370, - 'type': 0, - 'value': 2424726}], - 'relayed_by': '5.189.53.123', - 'size': 585, - 'time': 1484319619, - 'tx_index': 209616370, - 'ver': 1, - 'vin_sz': 3, - 'vout_sz': 2}}) - exit(0) - ''' + """ + process_transaction({'op': 'utx', + 'x': {'hash': '03ed0015c29dfc3bfd3d8a215490d82e3562fe4e8bf5f2ffa737ac8fdc850cc4', + 'inputs': [{'prev_out': {'addr': '1infuHrvt7tuGY8nJcfTwB3wwAR1XwUcW', + 'n': 0, + 'script': '76a91407e72dad6fc3a60ae5c4973bf2a23750713870b188ac', + 'spent': False, + 'tx_index': 93845150, + 'type': 0, + 'value': 300000}, + 'script': '483045022100980b532c7b417b6a7ce4ef1b5e509dda61762ccbbfd35189c2ed7985e6a35d1c02205afacbda7b0e10c6b41100ecee5fc05ab47ca00c85b842923324d55c0db529f8014104e2a76bdeaa387cae1cf920a9a8b54ee4c5e7378ea1985a638f2f3ec609a1d6a54e49e85d10bec55ce9321c5a45e2b21ca8eb1a3e18635405c4812d8467339e9c', + 'sequence': 4294967295}, + {'prev_out': {'addr': '1infuHrvt7tuGY8nJcfTwB3wwAR1XwUcW', + 'n': 0, + 'script': '76a91407e72dad6fc3a60ae5c4973bf2a23750713870b188ac', + 'spent': False, + 'tx_index': 182282259, + 'type': 0, + 'value': 2978800}, + 'script': '483045022100a560606252d83635c784139ae271db498e6528d561105d72c892dc80369036d3022048bfe431f3055d115f8faf0bf2b582912fa226a2f871b44f8bef348a278c8cea014104e2a76bdeaa387cae1cf920a9a8b54ee4c5e7378ea1985a638f2f3ec609a1d6a54e49e85d10bec55ce9321c5a45e2b21ca8eb1a3e18635405c4812d8467339e9c', + 'sequence': 4294967295}, + {'prev_out': {'addr': '17qC9bmMCyaReEPRWksYt99tr8s8YWLrDK', + 'n': 0, + 'script': '76a9144aee06e79fe2f92c3916b0cc0a78478e2434680e88ac', + 'spent': False, + 'tx_index': 93651246, + 'type': 0, + 'value': 407500}, + 'script': '47304402207b0946f973566ec65f4fb3878285274410e10b758806d8b46407d5219ffb36a1022017fdde92072e94bde68271754000636aa94952b91c2d818ed6d88a08cbf900fb0121029f8261faed04d668a78eadf7a3ef845de409e500dace54e604c547702a67b4fc', + 'sequence': 4294967295}], + 'lock_time': 0, + 'out': [{'addr': '1MZ6UbznUjoc34pkYyyofWJY42fAoA6k22', + 'n': 0, + 'script': '76a914e17465c3ef40d44508c7e923140d54c3cd8ced3d88ac', + 'spent': True, + 'tx_index': 209616370, + 'type': 0, + 'value': 1251574}, + {'addr': '1infuHrvt7tuGY8nJcfTwB3wwAR1XwUcW', + 'n': 1, + 'script': '76a91407e72dad6fc3a60ae5c4973bf2a23750713870b188ac', + 'spent': True, + 'tx_index': 209616370, + 'type': 0, + 'value': 2424726}], + 'relayed_by': '5.189.53.123', + 'size': 585, + 'time': 1484319619, + 'tx_index': 209616370, + 'ver': 1, + 'vin_sz': 3, + 'vout_sz': 2}}) + exit(0) + """ websocket.enableTrace(True) - ws = websocket.WebSocketApp("wss://ws.blockchain.info/inv", - on_message = on_message, - on_error = on_error, - on_close = on_close) + ws = websocket.WebSocketApp( + "wss://ws.blockchain.info/inv", + on_message=on_message, + on_error=on_error, + on_close=on_close, + ) ws.on_open = on_open print("### running... ###") ws.run_forever() diff --git a/bitvend-run.py b/bitvend-run.py index 49cb17c..bcc017e 100644 --- a/bitvend-run.py +++ b/bitvend-run.py @@ -2,13 +2,14 @@ import logging -logging.basicConfig(level=logging.DEBUG) # noqa +logging.basicConfig(level=logging.DEBUG) # noqa import threading from bitvend import create_app, dev, proc, db if __name__ == "__main__": from prometheus_client import start_http_server + start_http_server(8000) app = create_app() @@ -16,8 +17,12 @@ if __name__ == "__main__": with app.app_context(): db.create_all() - threading.Thread(target=app.run, kwargs={ - 'host': '0.0.0.0', - }, daemon=True).start() - #proc.start() + threading.Thread( + target=app.run, + kwargs={ + "host": "0.0.0.0", + }, + daemon=True, + ).start() + # proc.start() dev.run() diff --git a/bitvend/__init__.py b/bitvend/__init__.py index 2ab00a6..cf2480f 100644 --- a/bitvend/__init__.py +++ b/bitvend/__init__.py @@ -9,13 +9,13 @@ dev = BitvendCashlessMDBDevice() proc = PaymentProcessor(dev) spaceauth = SpaceAuth() -from bitvend.utils import to_local_currency, from_local_currency, format_btc, \ - sat_to_btc +from bitvend.utils import to_local_currency, from_local_currency, format_btc, sat_to_btc from bitvend.models import db, Transaction, User import bitvend.views import bitvend.admin + @spaceauth.user_loader def bitvend_user_loader(username, profile=None): u = User.find(username) @@ -27,15 +27,21 @@ def bitvend_user_loader(username, profile=None): return u + def create_app(): app = flask.Flask(__name__) - app.config.from_object('bitvend.default_settings') - print('Loading extra settings from {}...'.format(os.environ.get('BITVEND_SETTINGS', ''))) - app.config.from_pyfile(os.environ.get('BITVEND_SETTINGS', ''), silent=True) + app.config.from_object("bitvend.default_settings") + print( + "Loading extra settings from {}...".format( + os.environ.get("BITVEND_SETTINGS", "") + ) + ) + app.config.from_pyfile(os.environ.get("BITVEND_SETTINGS", ""), silent=True) # Use proper proxy headers, this fixes invalid scheme in # url_for(_external=True) from werkzeug.contrib.fixers import ProxyFix + app.wsgi_app = ProxyFix(app.wsgi_app) db.init_app(app) @@ -44,27 +50,31 @@ def create_app(): proc.init_app(app) app.register_blueprint(bitvend.views.bp) - app.register_blueprint(bitvend.admin.bp, url_prefix='/admin') + app.register_blueprint(bitvend.admin.bp, url_prefix="/admin") @app.context_processor def ctx_utils(): return { - 'from_local_currency': from_local_currency, - 'to_local_currency': to_local_currency, - 'format_btc': format_btc, - 'sat_to_btc': sat_to_btc, - 'qrcode': lambda data: flask.url_for('bitvend.qrcode_gen', data=data), - 'current_transaction': Transaction.query.filter(Transaction.finished == False).first(), - 'mdb_online': dev.online, - 'proc_online': proc.online, - 'static': lambda fn, **kwargs: flask.url_for('static', filename=fn, - **kwargs) + "from_local_currency": from_local_currency, + "to_local_currency": to_local_currency, + "format_btc": format_btc, + "sat_to_btc": sat_to_btc, + "qrcode": lambda data: flask.url_for("bitvend.qrcode_gen", data=data), + "current_transaction": Transaction.query.filter( + Transaction.finished == False + ).first(), + "mdb_online": dev.online, + "proc_online": proc.online, + "static": lambda fn, **kwargs: flask.url_for( + "static", filename=fn, **kwargs + ), } def url_for_other_page(page): args = flask.request.view_args.copy() - args['page'] = page + args["page"] = page return flask.url_for(flask.request.endpoint, **args) - app.jinja_env.globals['url_for_other_page'] = url_for_other_page + + app.jinja_env.globals["url_for_other_page"] = url_for_other_page return app diff --git a/bitvend/admin.py b/bitvend/admin.py index e36412e..9498c20 100644 --- a/bitvend/admin.py +++ b/bitvend/admin.py @@ -7,46 +7,46 @@ from bitvend.forms import ManualForm from spaceauth import cap_required -admin_required = cap_required('staff') -bp = Blueprint('admin', __name__) +admin_required = cap_required("staff") +bp = Blueprint("admin", __name__) -@bp.route('/manual', methods=['GET', 'POST']) + +@bp.route("/manual", methods=["GET", "POST"]) @fresh_login_required @admin_required def manual(): form = ManualForm() if form.validate_on_submit(): - current_user.transactions.append(Transaction( - amount=form.amount.data - )) + current_user.transactions.append(Transaction(amount=form.amount.data)) db.session.commit() - flash('Operation successful.', 'success') + flash("Operation successful.", "success") - return render_template('admin/manual.html', form=form) + return render_template("admin/manual.html", form=form) -@bp.route('/transactions/', defaults={'page': 1}) -@bp.route('/transactions/p/') + +@bp.route("/transactions/", defaults={"page": 1}) +@bp.route("/transactions/p/") @fresh_login_required @admin_required def transactions(page): - return render_template('admin/transactions.html', - transactions=Transaction.query.paginate(page) - ) + return render_template( + "admin/transactions.html", transactions=Transaction.query.paginate(page) + ) -@bp.route('/begin') +@bp.route("/begin") @fresh_login_required @admin_required def begin(): dev.begin_session(500) - flash('Operation successful.', 'success') - return redirect('/') + flash("Operation successful.", "success") + return redirect("/") -@bp.route('/cancel') +@bp.route("/cancel") @fresh_login_required @admin_required def cancel(): dev.cancel_session() - flash('Operation successful.', 'success') - return redirect('/') + flash("Operation successful.", "success") + return redirect("/") diff --git a/bitvend/default_settings.py b/bitvend/default_settings.py index 290679f..dad58d7 100644 --- a/bitvend/default_settings.py +++ b/bitvend/default_settings.py @@ -1,44 +1,44 @@ import platform -SECRET_KEY = 'testing' +SECRET_KEY = "testing" SQLALCHEMY_TRACK_MODIFICATIONS = False -SQLALCHEMY_DATABASE_URI = 'sqlite:///storage-%s.db' % (platform.node(),) +SQLALCHEMY_DATABASE_URI = "sqlite:///storage-%s.db" % (platform.node(),) -INPUT_ADDRESS = '12fkW5EBb3uBy1zD8pan4TcbabP5Fjato7' -BLOCKCYPHER_CHAIN = 'btc/main' +INPUT_ADDRESS = "12fkW5EBb3uBy1zD8pan4TcbabP5Fjato7" +BLOCKCYPHER_CHAIN = "btc/main" -#INPUT_ADDRESS ='n2SYFMbgfG4LXkvB4VgF4SeSEqQE28NAuV' -#BLOCKCYPHER_CHAIN = 'btc/test3' +# INPUT_ADDRESS ='n2SYFMbgfG4LXkvB4VgF4SeSEqQE28NAuV' +# BLOCKCYPHER_CHAIN = 'btc/test3' -BLOCKCYPHER_TOKEN = '918ddf2a06184ec295ca1cb636db20b5' +BLOCKCYPHER_TOKEN = "918ddf2a06184ec295ca1cb636db20b5" TEMPLATES_AUTO_RELOAD = True ITEMS = [ { - 'name': 'Club Mate', - 'image': '/static/img/club-mate.png', - 'value': 500, + "name": "Club Mate", + "image": "/static/img/club-mate.png", + "value": 500, }, { - 'name': 'Mate Mate', - 'image': '/static/img/mate-mate.png', - 'value': 600, + "name": "Mate Mate", + "image": "/static/img/mate-mate.png", + "value": 600, }, { - 'name': 'Arduino Pro Micro', - 'image': '/static/img/promicro.png', - 'value': 1600, + "name": "Arduino Pro Micro", + "image": "/static/img/promicro.png", + "value": 1600, }, { - 'name': 'Arduino Pro Mini', - 'image': '/static/img/promini.png', - 'value': 750, + "name": "Arduino Pro Mini", + "image": "/static/img/promini.png", + "value": 750, }, { - 'name': 'NodeMCU (ESP8266)', - 'image': '/static/img/nodemcu.png', - 'value': 1700, + "name": "NodeMCU (ESP8266)", + "image": "/static/img/nodemcu.png", + "value": 1700, }, ] diff --git a/bitvend/forms.py b/bitvend/forms.py index 84805e1..bd034aa 100644 --- a/bitvend/forms.py +++ b/bitvend/forms.py @@ -16,8 +16,8 @@ class DecimalUnityField(DecimalField): def _value(self): if self.data is not None: - format = '%%0.%df' % self.places - return (format % (Decimal(self.data) / self.unity,)) + format = "%%0.%df" % self.places + return format % (Decimal(self.data) / self.unity,) elif self.raw_data: return self.raw_data[0] @@ -28,21 +28,29 @@ class DecimalUnityField(DecimalField): except InvalidOperation: self.data = None + def UserExists(form, field): if not User.query.get(field.data): - raise ValidationError('User does not exist.') + raise ValidationError("User does not exist.") + def NotCurrentUser(form, field): if field.data == current_user.uid: - raise ValidationError('Are you serious?') + raise ValidationError("Are you serious?") + class TransferForm(FlaskForm): - target = StringField("Target user", validators=[ - DataRequired(), UserExists, NotCurrentUser]) - amount = DecimalUnityField("Amount", default=0, validators=[ - NumberRange(min=1), - ]) + target = StringField( + "Target user", validators=[DataRequired(), UserExists, NotCurrentUser] + ) + amount = DecimalUnityField( + "Amount", + default=0, + validators=[ + NumberRange(min=1), + ], + ) + class ManualForm(FlaskForm): - amount = DecimalUnityField("Amount", default=0, validators=[ - ]) + amount = DecimalUnityField("Amount", default=0, validators=[]) diff --git a/bitvend/graphs.py b/bitvend/graphs.py index a6e4bd1..c898e71 100644 --- a/bitvend/graphs.py +++ b/bitvend/graphs.py @@ -6,12 +6,14 @@ from bitvend.models import db, Transaction def daterange(start_date, end_date): - for n in range(int((end_date - start_date).days)+1): + for n in range(int((end_date - start_date).days) + 1): yield (start_date + timedelta(n)).date() def gen_graph_dataset(resultset, date_from=None, date_to=None, default=0): - date_hash = {datetime.strptime(xdate, '%Y-%m-%d').date(): count for xdate, count in resultset} + date_hash = { + datetime.strptime(xdate, "%Y-%m-%d").date(): count for xdate, count in resultset + } if not date_from: date_from = min(date_hash.keys()) @@ -22,18 +24,16 @@ def gen_graph_dataset(resultset, date_from=None, date_to=None, default=0): return [ type(default)(date_hash[d]) if d in date_hash else default for d in daterange(date_from, date_to) - ] + ] def gen_database_graph(title, query, date_from, date_to, default=0): - date_column = query.column_descriptions[0]['expr'] - resultset = query \ - .group_by( - date_column - ).filter( - date_column >= date_from, - date_column <= date_to - ).all() + date_column = query.column_descriptions[0]["expr"] + resultset = ( + query.group_by(date_column) + .filter(date_column >= date_from, date_column <= date_to) + .all() + ) return [title] + gen_graph_dataset(resultset, date_from, date_to, default) @@ -55,13 +55,18 @@ def gen_main_graph(date_from=None, date_to=None): date_from = datetime.combine(date_from, datetime.min.time()) date_to = datetime.combine(date_to, datetime.max.time()) - return zip_graph([ - ['date'] + [n.strftime('%Y-%m-%d') - for n in daterange(date_from, date_to)], - - gen_database_graph('purchases', db.session.query( - #func.date_trunc('day', Transaction.created), - func.strftime('%Y-%m-%d', Transaction.created), - func.count(Transaction.created), - ).filter(Transaction.type == 'purchase'), date_from, date_to), - ]) + return zip_graph( + [ + ["date"] + [n.strftime("%Y-%m-%d") for n in daterange(date_from, date_to)], + gen_database_graph( + "purchases", + db.session.query( + # func.date_trunc('day', Transaction.created), + func.strftime("%Y-%m-%d", Transaction.created), + func.count(Transaction.created), + ).filter(Transaction.type == "purchase"), + date_from, + date_to, + ), + ] + ) diff --git a/bitvend/mdb.py b/bitvend/mdb.py index d6d3b4b..5c34b8d 100644 --- a/bitvend/mdb.py +++ b/bitvend/mdb.py @@ -22,11 +22,11 @@ class BitvendCashlessMDBDevice(CashlessMDBDevice): def vend_request(self, product, value): # FIXME we report success here, because database write takes too much # time to respond in 5ms. - self.send([0x05, 0x00, 0xff]) + self.send([0x05, 0x00, 0xFF]) self.poll_queue.put([0x05]) self.current_request.processed = True - self.logger.info('got vend request: %r', self.current_tx_id) + self.logger.info("got vend request: %r", self.current_tx_id) if self.current_tx_id: with self.app.app_context(): @@ -48,20 +48,30 @@ class BitvendCashlessMDBDevice(CashlessMDBDevice): last_purchase = 0 def process_request(self, req): - if req.command == COIN_EXP and req.validate_checksum() and req.data[0] == COIN_EXP_PAYOUT and not req.processed: - self.logger.info('Purchase with change detected') + if ( + req.command == COIN_EXP + and req.validate_checksum() + and req.data[0] == COIN_EXP_PAYOUT + and not req.processed + ): + self.logger.info("Purchase with change detected") req.processed = True if time.time() - self.last_purchase > 5: purchase_counter.inc() self.last_purchase = time.time() elif req.command == COIN_TYPE and req.validate_checksum() and not req.processed: - self.logger.info('Purchase without detected') + self.logger.info("Purchase without detected") req.processed = True if time.time() - self.last_purchase > 5: purchase_counter.inc() self.last_purchase = time.time() - elif req.command == COIN_POLL and req.validate_checksum() and not req.processed and req.ack: - self.logger.info('Coin detected') + elif ( + req.command == COIN_POLL + and req.validate_checksum() + and not req.processed + and req.ack + ): + self.logger.info("Coin detected") req.processed = True coin_counter.inc() diff --git a/bitvend/models.py b/bitvend/models.py index b505fab..7ed9485 100644 --- a/bitvend/models.py +++ b/bitvend/models.py @@ -7,21 +7,26 @@ from sqlalchemy.sql import func, select, and_ db = SQLAlchemy() -class TransferException(Exception): pass -class NoFunds(TransferException): pass + +class TransferException(Exception): + pass + + +class NoFunds(TransferException): + pass class User(db.Model): - __tablename__ = 'users' + __tablename__ = "users" uid = db.Column(db.String(64), primary_key=True) - transactions = db.relationship('Transaction', backref='user', lazy='dynamic') + transactions = db.relationship("Transaction", backref="user", lazy="dynamic") def __str__(self): return self.uid def __repr__(self): - return ''.format(self) + return "".format(self) @hybrid_property def balance(self): @@ -29,51 +34,53 @@ class User(db.Model): @balance.expression def balance(self): - return (select([func.sum(Transaction.amount)]). - where(Transaction.uid == User.uid). - label("balance") - ) + return ( + select([func.sum(Transaction.amount)]) + .where(Transaction.uid == User.uid) + .label("balance") + ) @hybrid_property def purchase_count(self): - return self.transactions.filter(Transaction.type == 'purchase').count() + return self.transactions.filter(Transaction.type == "purchase").count() @purchase_count.expression def purchase_count(self): - return (select([func.count(Transaction.amount)]). - where(and_( - Transaction.uid == User.uid, - Transaction.type == 'purchase')). - label("purchase_count") - ) + return ( + select([func.count(Transaction.amount)]) + .where(and_(Transaction.uid == User.uid, Transaction.type == "purchase")) + .label("purchase_count") + ) @hybrid_property def purchase_amount(self): - return -sum((tx.amount or 0) for tx in self.transactions.filter(Transaction.type == 'purchase')) + return -sum( + (tx.amount or 0) + for tx in self.transactions.filter(Transaction.type == "purchase") + ) @purchase_amount.expression def purchase_amount(self): - return (select([-func.sum(Transaction.amount)]). - where(and_( - Transaction.uid == User.uid, - Transaction.type == 'purchase')). - label("purchase_amount") - ) + return ( + select([-func.sum(Transaction.amount)]) + .where(and_(Transaction.uid == User.uid, Transaction.type == "purchase")) + .label("purchase_amount") + ) def transfer(self, target, amount): if amount > self.amount_available: raise NoFunds() - self.transactions.append(Transaction( - amount=-amount, type='transfer', related=target.uid - )) - target.transactions.append(Transaction( - amount=amount, type='transfer', related=self.uid - )) + self.transactions.append( + Transaction(amount=-amount, type="transfer", related=target.uid) + ) + target.transactions.append( + Transaction(amount=amount, type="transfer", related=self.uid) + ) @property def debt_limit(self): - return app.config.get('DEBT_LIMIT', 5000) + return app.config.get("DEBT_LIMIT", 5000) @hybrid_property def amount_available(self): @@ -96,22 +103,24 @@ class User(db.Model): class Transaction(db.Model): - __tablename__ = 'transactions' + __tablename__ = "transactions" id = db.Column(db.Integer, primary_key=True) tx_hash = db.Column(db.String) - uid = db.Column(db.String(64), db.ForeignKey('users.uid')) + uid = db.Column(db.String(64), db.ForeignKey("users.uid")) amount = db.Column(db.Integer) - type = db.Column(db.String(32), default='manual') + type = db.Column(db.String(32), default="manual") related = db.Column(db.String) - related_user = db.relationship('User', foreign_keys=[related], primaryjoin=related==User.uid) + related_user = db.relationship( + "User", foreign_keys=[related], primaryjoin=related == User.uid + ) created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) - #value = db.Column(db.Integer) + # value = db.Column(db.Integer) @hybrid_property def value(self): return self.amount @@ -121,11 +130,9 @@ class Transaction(db.Model): @hybrid_property def finished(self): - return (self.type != 'purchase') | (self.product_id != None) + return (self.type != "purchase") | (self.product_id != None) - __mapper_args__ = { - "order_by": created.desc() - } + __mapper_args__ = {"order_by": created.desc()} def __repr__(self): - return ''.format(self) + return "".format(self) diff --git a/bitvend/processor.py b/bitvend/processor.py index 2dc4ce0..0e6cae1 100644 --- a/bitvend/processor.py +++ b/bitvend/processor.py @@ -15,9 +15,7 @@ class PaymentProcessor(threading.Thread): last_pong = 0 app = None - def __init__( - self, device, input_address=None, chain_id=None, app=None, - token=None): + def __init__(self, device, input_address=None, chain_id=None, app=None, token=None): super(PaymentProcessor, self).__init__() self.device = device self.input_address = input_address @@ -32,97 +30,103 @@ class PaymentProcessor(threading.Thread): self.app = app if not self.input_address: - self.input_address = self.app.config['INPUT_ADDRESS'] - self.chain_id = self.app.config['BLOCKCYPHER_CHAIN'] - self.token = self.app.config['BLOCKCYPHER_TOKEN'] + self.input_address = self.app.config["INPUT_ADDRESS"] + self.chain_id = self.app.config["BLOCKCYPHER_CHAIN"] + self.token = self.app.config["BLOCKCYPHER_TOKEN"] def run(self): - self.logger.info('Starting...') + self.logger.info("Starting...") while True: try: ws = websocket.WebSocketApp( - "wss://socket.blockcypher.com/v1/%s?token=%s" \ - % (self.chain_id, self.token), + "wss://socket.blockcypher.com/v1/%s?token=%s" + % (self.chain_id, self.token), on_message=self.on_message, on_error=self.on_error, on_close=self.on_close, - on_open=self.on_open) + on_open=self.on_open, + ) ws.run_forever(ping_timeout=20, ping_interval=30) except: - self.logger.exception('run_forever failed') + self.logger.exception("run_forever failed") time.sleep(1) def process_transaction(self, tx): - tx_size = tx['size'] - tx_hash = tx['hash'] - tx_value = sum([ - o['value'] for o in tx['outputs'] if self.input_address in o['addresses'] - ], 0) - fee = tx['fees'] + tx_size = tx["size"] + tx_hash = tx["hash"] + tx_value = sum( + [o["value"] for o in tx["outputs"] if self.input_address in o["addresses"]], + 0, + ) + fee = tx["fees"] fee_byte = fee / tx_size - self.logger.info('%r %r %r %r %r', tx_size, tx_hash, tx_value, fee, fee_byte) - self.logger.info('In local currency: %r', to_local_currency(tx_value)) + self.logger.info("%r %r %r %r %r", tx_size, tx_hash, tx_value, fee, fee_byte) + self.logger.info("In local currency: %r", to_local_currency(tx_value)) with self.app.app_context(): intx = Transaction( - uid='__bitcoin__', - type='bitcoin', + uid="__bitcoin__", + type="bitcoin", amount=to_local_currency(tx_value), tx_hash=tx_hash, - ) + ) db.session.add(intx) db.session.commit() outtx = Transaction( - uid='__bitcoin__', - type='purchase', - ) + uid="__bitcoin__", + type="purchase", + ) db.session.add(outtx) db.session.commit() tx_id = outtx.id if to_local_currency(tx_value) < 100: - self.logger.warning('Whyyyy so low...') + self.logger.warning("Whyyyy so low...") return if fee_byte < 15: - self.logger.warning('Fee too low...') + self.logger.warning("Fee too low...") return - self.logger.info('Transaction %d ok, going to device...', tx_id) + self.logger.info("Transaction %d ok, going to device...", tx_id) # FIXME we need better handling of ACK on POLL responses... self.device.begin_session(to_local_currency(tx_value), tx_id) def on_message(self, ws, message): - #print message + # print message data = json.loads(message) - self.logger.info('msg: %r', data) + self.logger.info("msg: %r", data) - if 'inputs' in data: + if "inputs" in data: self.process_transaction(data) - elif data.get('event') == 'pong': + elif data.get("event") == "pong": self.last_pong = time.time() def on_error(self, ws, error): self.logger.error(error) def on_close(self, ws): - self.logger.info('Connection closed') + self.logger.info("Connection closed") def on_open(self, ws): - self.logger.info('Connected, registering for: %r', self.input_address) + self.logger.info("Connected, registering for: %r", self.input_address) - ws.send(json.dumps({ - "event": "tx-confidence", - "address": self.input_address, - "confidence": 0.9, - })) + ws.send( + json.dumps( + { + "event": "tx-confidence", + "address": self.input_address, + "confidence": 0.9, + } + ) + ) threading.Thread(target=self.keepalive, args=(ws,), daemon=True).start() @@ -130,13 +134,11 @@ class PaymentProcessor(threading.Thread): # Keepalive thread target, just send ping once in a while while True: # FIXME check last ping time - ws.send(json.dumps({ - "event": "ping" - })) + ws.send(json.dumps({"event": "ping"})) time.sleep(20) if time.time() - self.last_pong > 60: - self.logger.warning('Closing socket for inactivity') + self.logger.warning("Closing socket for inactivity") ws.close() return diff --git a/bitvend/stats.py b/bitvend/stats.py index a00cce1..cbe9f92 100644 --- a/bitvend/stats.py +++ b/bitvend/stats.py @@ -1,5 +1,7 @@ from prometheus_client import start_http_server, Counter -coin_counter = Counter('coins_inserted', 'Number of coins inserted into machine') -purchase_counter = Counter('purchases', 'Number of purchases') -cashless_purchase_counter = Counter('cashless_purchases', 'Number of cashless (BTC) purchases') +coin_counter = Counter("coins_inserted", "Number of coins inserted into machine") +purchase_counter = Counter("purchases", "Number of purchases") +cashless_purchase_counter = Counter( + "cashless_purchases", "Number of cashless (BTC) purchases" +) diff --git a/bitvend/utils.py b/bitvend/utils.py index 24914cf..3a17739 100644 --- a/bitvend/utils.py +++ b/bitvend/utils.py @@ -3,10 +3,12 @@ import cachetools import requests + @cachetools.cached(cachetools.TTLCache(32, 600)) -def get_exchange_rate(currency='PLN'): +def get_exchange_rate(currency="PLN"): # Returns current exchange rate for selected currency - return requests.get('https://blockchain.info/pl/ticker').json()[currency]['last'] + return requests.get("https://blockchain.info/pl/ticker").json()[currency]["last"] + def to_local_currency(sat, safe=False): # Returns satoshi in local lowest denomination currency (grosze) @@ -18,6 +20,7 @@ def to_local_currency(sat, safe=False): raise return int(sat / 1000000.0 * rate) + def from_local_currency(val, safe=False): # Returns satoshi value from local currency try: @@ -29,10 +32,12 @@ def from_local_currency(val, safe=False): return int(val / rate * 1000000) + def sat_to_btc(amount): # Converts satoshi to BTC return amount / 100000000.0 + def format_btc(amount): # Formats satoshi to human-readable format - return (u'฿%.8f' % (sat_to_btc(amount),)).rstrip('0').rstrip('.') + return ("฿%.8f" % (sat_to_btc(amount),)).rstrip("0").rstrip(".") diff --git a/bitvend/views.py b/bitvend/views.py index 23e8147..0621065 100644 --- a/bitvend/views.py +++ b/bitvend/views.py @@ -1,5 +1,4 @@ -from flask import Blueprint, render_template, redirect, request, flash, \ - url_for, jsonify +from flask import Blueprint, render_template, redirect, request, flash, url_for, jsonify from flask import current_app as app import six @@ -13,92 +12,103 @@ from bitvend.graphs import gen_main_graph from spaceauth import login_required, current_user, cap_required -bp = Blueprint('bitvend', __name__, template_folder='templates') +bp = Blueprint("bitvend", __name__, template_folder="templates") -@bp.route('/') + +@bp.route("/") def index(): transactions = [] - hallofshame = User.query \ - .with_entities(User, User.balance) \ - .order_by(User.balance.asc()) \ - .filter(User.balance < 0) \ - .limit(5) \ + hallofshame = ( + User.query.with_entities(User, User.balance) + .order_by(User.balance.asc()) + .filter(User.balance < 0) + .limit(5) .all() + ) - hallofaddicts = User.query \ - .with_entities(User, User.purchase_amount, User.purchase_count) \ - .order_by(User.purchase_amount.desc()) \ - .filter(User.purchase_amount > 0) \ - .limit(5) \ + hallofaddicts = ( + User.query.with_entities(User, User.purchase_amount, User.purchase_count) + .order_by(User.purchase_amount.desc()) + .filter(User.purchase_amount > 0) + .limit(5) .all() + ) - bottles_purchased = Transaction.query \ - .filter(Transaction.amount.in_([-500, -600]), Transaction.type == 'purchase') \ - .count() + bottles_purchased = Transaction.query.filter( + Transaction.amount.in_([-500, -600]), Transaction.type == "purchase" + ).count() if current_user.is_authenticated: - transactions = current_user.transactions.order_by(Transaction.created.desc()).limit(10) + transactions = current_user.transactions.order_by( + Transaction.created.desc() + ).limit(10) return render_template( - 'index.html', - items=app.config['ITEMS'], + "index.html", + items=app.config["ITEMS"], transactions=transactions, transfer_form=TransferForm(), hallofshame=hallofshame, hallofaddicts=hallofaddicts, bottles_purchased=bottles_purchased, - ) + ) -@bp.route('/transactions/', defaults={'page': 1}) -@bp.route('/transactions/p/') + +@bp.route("/transactions/", defaults={"page": 1}) +@bp.route("/transactions/p/") def transactions(page): - return render_template('transactions.html', - transactions=current_user.transactions.paginate(page) - ) + return render_template( + "transactions.html", transactions=current_user.transactions.paginate(page) + ) -@bp.route('/transfer', methods=['GET', 'POST']) + +@bp.route("/transfer", methods=["GET", "POST"]) def transfer(): transfer_form = TransferForm() if transfer_form.validate_on_submit(): try: - current_user.transfer(User.query.get(transfer_form.target.data), transfer_form.amount.data) + current_user.transfer( + User.query.get(transfer_form.target.data), transfer_form.amount.data + ) db.session.commit() - flash('Transfer succeeded.', 'success') + flash("Transfer succeeded.", "success") except NoFunds: - flash('No funds.', 'danger') - return redirect(url_for('.index')) + flash("No funds.", "danger") + return redirect(url_for(".index")) - flash('; '.join(sum(transfer_form.errors.values(), [])), 'danger') + flash("; ".join(sum(transfer_form.errors.values(), [])), "danger") - return redirect(url_for('.index')) + return redirect(url_for(".index")) -@bp.route('/log') + +@bp.route("/log") @login_required -@cap_required('staff') +@cap_required("staff") def log(): - return render_template( - 'log.html', transactions=Transaction.query.all()) + return render_template("log.html", transactions=Transaction.query.all()) -@bp.route('/begin') + +@bp.route("/begin") @login_required def begin(): if Transaction.query.filter(Transaction.finished == False).count(): - flash('Nope xD', 'danger') - return redirect(url_for('.index')) + flash("Nope xD", "danger") + return redirect(url_for(".index")) if current_user.amount_available <= 0: - flash('Nope xD', 'danger') - return redirect(url_for('.index')) + flash("Nope xD", "danger") + return redirect(url_for(".index")) - tx = Transaction(type='purchase') + tx = Transaction(type="purchase") current_user.transactions.append(tx) db.session.commit() dev.begin_session(current_user.amount_available, tx.id) - return redirect(url_for('.index')) + return redirect(url_for(".index")) -@bp.route('/cancel') + +@bp.route("/cancel") @login_required def cancel(): dev.cancel_session() @@ -108,20 +118,26 @@ def cancel(): Transaction.query.filter(Transaction.finished == False).delete() db.session.commit() - return redirect(url_for('.index')) + return redirect(url_for(".index")) -@bp.route('/qrcode/') + +@bp.route("/qrcode/") def qrcode_gen(data): bio = six.BytesIO() qr = qrcode.QRCode(border=0, box_size=50) qr.add_data(data) img = qr.make_image(image_factory=qrcode.image.svg.SvgPathFillImage) img.save(bio) - return bio.getvalue(), 200, { - 'Content-Type': 'image/svg+xml', - 'Cache-Control': 'public,max-age=3600', - } + return ( + bio.getvalue(), + 200, + { + "Content-Type": "image/svg+xml", + "Cache-Control": "public,max-age=3600", + }, + ) -@bp.route('/api/1/history.json') + +@bp.route("/api/1/history.json") def history(): return jsonify(gen_main_graph()) diff --git a/cygpio/cygpio_test.py b/cygpio/cygpio_test.py index a9d8108..45dc8e5 100644 --- a/cygpio/cygpio_test.py +++ b/cygpio/cygpio_test.py @@ -1,2 +1,3 @@ import cygpio + cygpio.test() diff --git a/cygpio/setup.py b/cygpio/setup.py index 4ce5599..f97dfd9 100644 --- a/cygpio/setup.py +++ b/cygpio/setup.py @@ -3,5 +3,5 @@ from distutils.extension import Extension from Cython.Build import cythonize setup( - ext_modules = cythonize([Extension("cygpio", ["cygpio.pyx"], libraries=["pigpio"])]) - ) + ext_modules=cythonize([Extension("cygpio", ["cygpio.pyx"], libraries=["pigpio"])]) +) diff --git a/mdb/backend.py b/mdb/backend.py index 903c87f..c6763a7 100644 --- a/mdb/backend.py +++ b/mdb/backend.py @@ -20,7 +20,7 @@ class Backend(object): pass def read(self): - return b'' + return b"" def write(self, data): pass @@ -32,7 +32,7 @@ class Backend(object): class DummyBackend(Backend): def read(self): time.sleep(0.005) - return b'' + return b"" # @@ -55,7 +55,7 @@ class RaspiBackend(Backend): status = self.pi.bb_serial_read_open(self.rx_pin, 9600, 9) if status: - raise Exception('Port open failed: %d', status) + raise Exception("Port open failed: %d", status) def read(self): while True: @@ -70,9 +70,9 @@ class RaspiBackend(Backend): wid = self.pi.wave_create() self.pi.set_mode(self.tx_pin, pigpio.OUTPUT) - self.pi.wave_send_once(wid) # transmit serial data + self.pi.wave_send_once(wid) # transmit serial data - while self.pi.wave_tx_busy(): # wait until all data sent + while self.pi.wave_tx_busy(): # wait until all data sent time.sleep(0.001) self.pi.wave_delete(wid) @@ -83,11 +83,13 @@ class RaspiBackend(Backend): # Backend device based on STM32F1 MDB-USB adapter (to be actually designed...) # class SerialBackend(Backend): - def __init__(self, device='/dev/serial/by-id/usb-vuko@hackerspace.pl_flowMeter_00001-if00'): + def __init__( + self, device="/dev/serial/by-id/usb-vuko@hackerspace.pl_flowMeter_00001-if00" + ): self.device = device def read(self): - buf = b'' + buf = b"" while len(buf) < 2: buf += self.ser.read(1) @@ -103,6 +105,7 @@ class SerialBackend(Backend): def open(self): import serial + self.ser = serial.Serial(self.device) # FIXME clear buffer diff --git a/mdb/constants.py b/mdb/constants.py index f6231ef..0f402b5 100644 --- a/mdb/constants.py +++ b/mdb/constants.py @@ -1,8 +1,8 @@ # Page 26 - peripheral addresses -COIN_TYPE = 0x0c -COIN_POLL = 0x0b +COIN_TYPE = 0x0C +COIN_POLL = 0x0B -COIN_EXP = 0x0f +COIN_EXP = 0x0F COIN_EXP_PAYOUT = 0x02 CASHLESS_RESET = 0x10 diff --git a/mdb/device.py b/mdb/device.py index 8df408e..fa5d97f 100644 --- a/mdb/device.py +++ b/mdb/device.py @@ -16,6 +16,7 @@ from mdb.utils import compute_checksum, compute_chk, bcd_decode from mdb.constants import * from mdb.backend import RaspiBackend, DummyBackend, SerialBackend, pigpio + class MDBRequest(object): timestamp = None data = None @@ -38,7 +39,7 @@ class MDBRequest(object): try: if self.ack: if len(self.data) == 1 and self.processed: - return True # Only ACK + return True # Only ACK return self.data[-2] == compute_checksum(self.command, self.data[:-2]) else: @@ -46,18 +47,21 @@ class MDBRequest(object): except KeyboardInterrupt: raise except: - logging.exception('Checksum validation failed for: %r %r', self.command, self.data) + logging.exception( + "Checksum validation failed for: %r %r", self.command, self.data + ) return False + @property def ack(self): return len(self.data) and self.data[-1] == 0x00 def __repr__(self): - return '' % ( + return "" % ( self.command, - ' '.join(['0x%02x' % b for b in self.data]), - self.validate_checksum() - ) + " ".join(["0x%02x" % b for b in self.data]), + self.validate_checksum(), + ) class MDBDevice(object): @@ -71,15 +75,15 @@ class MDBDevice(object): self.poll_queue = queue.Queue() if cygpio: self.backend = cygpio.CythonRaspiBackend() - self.logger.warning('Running with FAST CYTHON BACKEND') + self.logger.warning("Running with FAST CYTHON BACKEND") elif pigpio: self.backend = RaspiBackend() else: - self.logger.warning('Running with dummy backend device') + self.logger.warning("Running with dummy backend device") self.backend = SerialBackend() def initialize(self): - self.logger.info('Initializing... %r backend', self.backend) + self.logger.info("Initializing... %r backend", self.backend) self.backend.open() # here should IO / connection initizliation go @@ -92,24 +96,34 @@ class MDBDevice(object): while True: data = self.backend.read() for b in range(0, len(data), 2): - if data[b+1]: - if self.current_request: # and not self.current_request.processed: - if self.current_request.command not in [0xf2]: + if data[b + 1]: + if self.current_request: # and not self.current_request.processed: + if self.current_request.command not in [0xF2]: self.logger.debug(self.current_request) if self.current_request.processed and self.current_request.ack: - self.logger.info('Got response: %d',self.current_request.data[-1]) + self.logger.info( + "Got response: %d", self.current_request.data[-1] + ) self.poll_msg = [] self.current_request.reset(data[b]) self.send_buffer = None elif self.current_request: - if self.current_request.processed and data[b] == 0xaa and self.send_buffer: - self.logger.warning('Received RETRANSMIT %r %r', self.current_request, self.send_buffer) - #self.send(self.send_buffer, checksum=False) + if ( + self.current_request.processed + and data[b] == 0xAA + and self.send_buffer + ): + self.logger.warning( + "Received RETRANSMIT %r %r", + self.current_request, + self.send_buffer, + ) + # self.send(self.send_buffer, checksum=False) else: self.current_request.data.append(data[b]) else: - self.logger.warning('Received unexpected data: 0x%02x', data[b]) + self.logger.warning("Received unexpected data: 0x%02x", data[b]) if self.current_request and not self.current_request.processed: try: @@ -121,18 +135,20 @@ class MDBDevice(object): except KeyboardInterrupt: raise except: - self.logger.exception('Request process failed!') + self.logger.exception("Request process failed!") def send(self, data, checksum=True): data = list(data) if checksum: data.append(0x100 | compute_chk(data)) - msg = struct.pack('<%dh' % len(data), *data) + msg = struct.pack("<%dh" % len(data), *data) self.send_buffer = data - self.logger.debug('>> [%d bytes] %s', len(data), ' '.join(['0x%02x' % b for b in data])) + self.logger.debug( + ">> [%d bytes] %s", len(data), " ".join(["0x%02x" % b for b in data]) + ) self.backend.write(msg) @@ -140,46 +156,49 @@ class MDBDevice(object): # Unimplemented... return + # # This is mostly working cashless device implementation # + class CashlessMDBDevice(MDBDevice): base_address = CASHLESS_RESET - state = 'IDLE' + state = "IDLE" last_poll = 0 config_data = [ - 0x01, # Feature level - 0x19, 0x85, # PLN x---DD - 1, # scaling factor - 0x02, # decimal places factor - 10, # 10s response time - 0x00, # misc options... - ] + 0x01, # Feature level + 0x19, + 0x85, # PLN x---DD + 1, # scaling factor + 0x02, # decimal places factor + 10, # 10s response time + 0x00, # misc options... + ] - manufacturer = 'GMD' - serial_number = '123456789012' - model_number = '123456789012' + manufacturer = "GMD" + serial_number = "123456789012" + model_number = "123456789012" software_version = (0x21, 0x37) lockup_counter = 0 def process_request(self, req): # FIXME this shouldn't be required... - #if req.command == 0x30 and req.validate_checksum(): + # if req.command == 0x30 and req.validate_checksum(): # self.lockup_counter += 1 # if self.lockup_counter % 50 == 0: # self.logger.info('YOLO') # return [] # return - #if req.command == 0x31 and req.validate_checksum(): + # if req.command == 0x31 and req.validate_checksum(): # return [] - #if req.command == 0x37 and req.validate_checksum(): + # if req.command == 0x37 and req.validate_checksum(): # return [] - #if req.command == 0x36 and req.validate_checksum(): + # if req.command == 0x36 and req.validate_checksum(): # return [] - #if req.command == 0x34 and req.validate_checksum(): + # if req.command == 0x34 and req.validate_checksum(): # return [] if (req.command & self.base_address) != self.base_address: @@ -193,8 +212,8 @@ class CashlessMDBDevice(MDBDevice): self.lockup_counter = 0 if req.command == CASHLESS_RESET: - self.state = 'RESET' - self.logger.info('RESET: Device reset') + self.state = "RESET" + self.logger.info("RESET: Device reset") self.poll_queue.put([0x00]) return [] @@ -209,89 +228,95 @@ class CashlessMDBDevice(MDBDevice): pass if self.poll_msg: - self.logger.info('Sending POLL response: %r', self.poll_msg) + self.logger.info("Sending POLL response: %r", self.poll_msg) return self.poll_msg elif req.command == CASHLESS_VEND: - if req.data[0] == 0x00: # vend request - self.logger.info('VEND: request %r', req) - value, product_bcd = struct.unpack('>xhhx', req.data) + if req.data[0] == 0x00: # vend request + self.logger.info("VEND: request %r", req) + value, product_bcd = struct.unpack(">xhhx", req.data) product = bcd_decode(product_bcd) - self.logger.info('VEND: requested %d (%04x) for %d', product, product_bcd, value) + self.logger.info( + "VEND: requested %d (%04x) for %d", product, product_bcd, value + ) if self.vend_request(product, value): # accept. two latter bytes are value subtracted from balance # displayed after purchase FIXME - return [0x05, 0x00, 0xff] + return [0x05, 0x00, 0xFF] else: # welp? return [0x06] - elif req.data[0] == 0x01: # vend cancel - self.logger.info('VEND: cancel') - return [0x06] # deny == ok + elif req.data[0] == 0x01: # vend cancel + self.logger.info("VEND: cancel") + return [0x06] # deny == ok - elif req.data[0] == 0x02: # vend succ - self.logger.info('VEND: success %r', req) + elif req.data[0] == 0x02: # vend succ + self.logger.info("VEND: success %r", req) return [] elif req.data[0] == 0x03: - self.logger.info('VEND: failure') + self.logger.info("VEND: failure") return [] elif req.data[0] == 0x04: - self.logger.info('VEND: session complete %r', req) - self.state = 'OK' + self.logger.info("VEND: session complete %r", req) + self.state = "OK" return [0x07] elif req.data[0] == 0x05: - self.logger.info('VEND: cash sale') + self.logger.info("VEND: cash sale") return [] elif req.data[0] == 0x06: - self.logger.info('VEND: negative vend request') - return [0x06] # deny + self.logger.info("VEND: negative vend request") + return [0x06] # deny else: - self.logger.warning('VEND: unknown command %r', req) + self.logger.warning("VEND: unknown command %r", req) elif req.command == CASHLESS_EXP and req.data[0] == CASHLESS_EXP_ID: - self.logger.info('EXP_ID request: %r', req) + self.logger.info("EXP_ID request: %r", req) return ( - bytearray([0x09]) + # peripheral ID - bytearray(self.manufacturer.rjust(3, ' ').encode('ascii')) + - bytearray(self.serial_number.rjust(12, '0').encode('ascii')) + - bytearray(self.model_number.rjust(12, '0').encode('ascii')) + - bytearray(self.software_version) + bytearray([0x09]) + + bytearray( # peripheral ID + self.manufacturer.rjust(3, " ").encode("ascii") ) + + bytearray(self.serial_number.rjust(12, "0").encode("ascii")) + + bytearray(self.model_number.rjust(12, "0").encode("ascii")) + + bytearray(self.software_version) + ) - elif req.command == CASHLESS_SETUP and req.data[0] == 0x00 and len(req.data) == 6: + elif ( + req.command == CASHLESS_SETUP and req.data[0] == 0x00 and len(req.data) == 6 + ): vmc_level, disp_cols, disp_rows, disp_info = req.data[1:-1] - self.logger.info('SETUP config') - self.logger.info(' -> VMC level: %d', vmc_level) - self.logger.info(' -> Disp cols: %d', disp_cols) - self.logger.info(' -> Disp rows: %d', disp_rows) - self.logger.info(' -> Disp info: %d', disp_info) + self.logger.info("SETUP config") + self.logger.info(" -> VMC level: %d", vmc_level) + self.logger.info(" -> Disp cols: %d", disp_cols) + self.logger.info(" -> Disp rows: %d", disp_rows) + self.logger.info(" -> Disp info: %d", disp_info) - self.state = 'OK' + self.state = "OK" return [0x01] + self.config_data elif req.command == CASHLESS_SETUP and req.data[0] == 0x01: - self.logger.info('SETUP max/min price: %r', req) + self.logger.info("SETUP max/min price: %r", req) return [] elif req.command == CASHLESS_READER: - self.logger.info('READER update: %r', req.data[0]) + self.logger.info("READER update: %r", req.data[0]) return [] def begin_session(self, amount): - self.logger.info('Beginning session for %d', amount) + self.logger.info("Beginning session for %d", amount) # Begins new session with balance provided if amount > 10000: amount = 10000 - self.poll_queue.put([0x03, (amount >> 8) & 0xff, amount & 0xff]) + self.poll_queue.put([0x03, (amount >> 8) & 0xFF, amount & 0xFF]) def cancel_session(self): # Cancels current session @@ -310,19 +335,34 @@ class CashlessMDBDevice(MDBDevice): # This is mostly unfinished Bill validator implementation # + class BillMDBDevice(MDBDevice): scaling_factor = 50 bills = [ - 50, 100, 200, 500, 1000, 2000, 5000, 10000, - 0, 0, 0, 0, 0, 0, 0, 0, - ] + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ] feed_bills = [] def feed_amount(self, amount): if amount % self.scaling_factor: - raise Exception('Invalid amount') + raise Exception("Invalid amount") while amount > 0: bills_list = filter(lambda v: v <= amount, self.bills) diff --git a/mdb/utils.py b/mdb/utils.py index 902bd2c..786c96f 100644 --- a/mdb/utils.py +++ b/mdb/utils.py @@ -1,14 +1,18 @@ from functools import reduce + def compute_chk(data): - return reduce(lambda a, b: (a+b)%256, data, 0) + return reduce(lambda a, b: (a + b) % 256, data, 0) + def compute_checksum(cmd, data): return compute_chk(bytearray([cmd]) + data) + def bcd_decode(b): - return \ - 1000 * ((b & 0xf000) >> 12) + \ - 100 * ((b & 0xf00) >> 8) + \ - 10 * ((b & 0xf0) >> 4) + \ - (b & 0x0f) + return ( + 1000 * ((b & 0xF000) >> 12) + + 100 * ((b & 0xF00) >> 8) + + 10 * ((b & 0xF0) >> 4) + + (b & 0x0F) + ) diff --git a/setup.py b/setup.py index eb8c91b..4e7ba90 100644 --- a/setup.py +++ b/setup.py @@ -5,5 +5,5 @@ setup( version="1.0", packages=find_packages(), include_package_data=True, - scripts=['bitvend-run.py'], + scripts=["bitvend-run.py"], )