summaryrefslogtreecommitdiffstats
path: root/bitvend.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitvend.py')
-rw-r--r--bitvend.py74
1 files changed, 66 insertions, 8 deletions
diff --git a/bitvend.py b/bitvend.py
index 45f6c86..5470679 100644
--- a/bitvend.py
+++ b/bitvend.py
@@ -3,6 +3,8 @@ import struct
import logging
import threading
import flask
+import cachetools
+import requests
try:
import queue
@@ -70,7 +72,29 @@ def compute_chk(data):
return reduce(lambda a, b: (a+b)%256, data, 0)
def compute_checksum(cmd, data):
- return reduce(lambda a, b: (a+b)%256, bytearray([cmd]) + data, 0)
+ return compute_chk(bytearray([cmd]) + data)
+
+def bcd_decode(b):
+ return 10 * ((b & 0xf0) >> 4) + (b & 0x0f)
+
+@cachetools.cached(cachetools.TTLCache(32, 600))
+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 from_local_currency(val):
+ rate = get_exchange_rate()
+ return int(val / rate * 1000000)
+
+def sat_to_btc(amount):
+ return amount / 100000000.0
+
+def format_btc(amount):
+ return (u'฿%.8f' % (sat_to_btc(amount),)).rstrip('0').rstrip('.')
class MDBRequest(object):
timestamp = None
@@ -89,7 +113,7 @@ class MDBRequest(object):
try:
if self.ack:
- if len(self.data) == 1:
+ if len(self.data) == 1 and self.processed:
return True # Only ACK
return self.data[-2] == compute_checksum(self.command, self.data[:-2])
@@ -153,6 +177,8 @@ class MDBDevice(object):
if resp is not None:
self.current_request.processed = True
self.send(resp)
+ except KeyboardInterrupt:
+ raise
except:
self.logger.exception('Request process failed!')
@@ -163,9 +189,9 @@ class MDBDevice(object):
msg = struct.pack('<%dh' % len(data), *data)
- self.send_buffer = msg
+ self.send_buffer = data
- self.logger.debug('>> %s', ' '.join(['0x%02x' % b for b in msg]))
+ self.logger.debug('>> [%d bytes] %s', len(data), ' '.join(['0x%02x' % b for b in data]))
pi.wave_clear()
pi.wave_add_serial(TX_PIN, 9600, msg, bb_bits=9, bb_stop=6)
@@ -245,7 +271,12 @@ class CashlessMDBDevice(MDBDevice):
elif req.command == CASHLESS_VEND:
if req.data[0] == 0x00: # vend request
self.logger.info('VEND: request %r', req)
- return [0x05] # , 0xff, 0xff] # accept
+ value, product_bcd = struct.unpack('>xhhx', req.data)
+ product = bcd_decode(product_bcd)
+ self.logger.info('VEND: requested %d for %d', product, value)
+ # accept. two latter bytes are value subtracted from balance
+ # displayed after purchase
+ return [0x05, 0x00, 0xff]
elif req.data[0] == 0x01: # vend cancel
self.logger.info('VEND: cancel')
@@ -260,7 +291,7 @@ class CashlessMDBDevice(MDBDevice):
return []
elif req.data[0] == 0x04:
- self.logger.info('VEND: complete %r', req)
+ self.logger.info('VEND: session complete %r', req)
self.state = 'IDLE'
return [0x07]
@@ -343,6 +374,24 @@ dev = CashlessMDBDevice()
app = flask.Flask(__name__)
+app.config['TEMPLATES_AUTO_RELOAD'] = True
+
+@app.route('/')
+def index():
+ return flask.render_template(
+ 'index.html', items=[
+ {
+ 'name': 'Club Mate',
+ 'image': 'http://scrummy.pl/2766-thickbox_default/club-mate.jpg',
+ 'value': 500,
+ },
+ {
+ 'name': 'Arduino Pro Micro',
+ 'image': 'https://hackerspace.pl/~informatic/dropbox/1d59bf26f1352da558f249d815db6376.png',
+ 'value': 0,
+ }
+ ])
+
@app.route('/begin/<int:amount>')
def begin_session(amount):
dev.begin_session(amount)
@@ -353,6 +402,15 @@ def cancel_session():
dev.cancel_session();
return 'ok'
+@app.context_processor
+def ctx_utils():
+ return {
+ 'from_local_currency': from_local_currency,
+ 'to_local_currency': to_local_currency,
+ 'format_btc': format_btc,
+ }
+
if __name__ == "__main__":
- threading.Thread(target=app.run, kwargs={'host': '0.0.0.0'}, daemon=True).start()
- dev.run()
+ app.run()
+ #threading.Thread(target=app.run, kwargs={'host': '0.0.0.0'}, daemon=True).start()
+ #dev.run()