summaryrefslogtreecommitdiffstats
path: root/bitvend-bill.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitvend-bill.py')
-rw-r--r--bitvend-bill.py256
1 files changed, 256 insertions, 0 deletions
diff --git a/bitvend-bill.py b/bitvend-bill.py
new file mode 100644
index 0000000..d6795c5
--- /dev/null
+++ b/bitvend-bill.py
@@ -0,0 +1,256 @@
+import pigpio
+import time
+import struct
+
+from prometheus_client import start_http_server, Counter
+#start_http_server(8000)
+
+coin_counter = Counter('coins_inserted', 'Number of coins inserted into machine')
+purchase_counter = Counter('purchases', 'Number of purchases')
+
+RX_PIN = 4
+TX_PIN = 17
+
+CASHLESS_RESET = 0x10
+CASHLESS_SETUP = 0x11
+CASHLESS_POLL = 0x12
+CASHLESS_VEND = 0x13
+CASHLESS_READER = 0x14
+CASHLESS_REVALUE = 0x15
+
+CASHLESS_EXP = 0x17
+CASHLESS_EXP_REQ_ID = 0x00
+#CASHLESS_EXP_READ = 0x01
+#CASHLESS_EXP_WRITE = 0x02
+CASHLESS_EXP_WRITE_TIME = 0x03
+CASHLESS_EXP_FEATURE_ENABLE = 0x04
+
+BILL_RESET = 0x30
+BILL_SETUP = 0x31
+BILL_POLL = 0x33
+BILL_TYPE = 0x34
+BILL_STACKER = 0x36
+BILL_EXP = 0x37
+BILL_EXP_ID = 0x00
+
+
+# Page 26 - peripheral addresses
+
+pi = pigpio.pi()
+pi.wave_clear()
+pi.set_mode(TX_PIN, pigpio.INPUT)
+
+try:
+ pi.bb_serial_read_close(RX_PIN)
+except:
+ pass
+
+status = pi.bb_serial_read_open(RX_PIN, 9600, 9)
+
+if status != 0:
+ print 'Open failed:', status
+ exit(1)
+
+
+def compute_chk(data):
+ return reduce(lambda a, b: (a+b)%256, data)
+
+def compute_checksum(cmd, data):
+ return reduce(lambda a, b: (a+b)%256, chr(cmd) + data)
+
+class MDBRequest(object):
+ timestamp = None
+ data = None
+ command = None
+ processed = False
+
+ def __init__(self, command):
+ self.timestamp = time.time()
+ self.command = command
+ self.data = bytearray()
+
+ def validate_checksum(self):
+ if not self.data:
+ return False
+ try:
+ if self.ack:
+ return self.data[-2] == compute_checksum(self.command, self.data[:-2])
+ else:
+ return self.data[-1] == compute_checksum(self.command, self.data[:-1])
+ except:
+ return False
+ @property
+ def ack(self):
+ return self.data[-1] == 0x00
+
+ def __repr__(self):
+ return '<MDBRequest 0x%02x [%s] chk:%r>' % (
+ self.command,
+ ' '.join(['0x%02x' % b for b in self.data]),
+ self.validate_checksum()
+ )
+
+class MDBDevice(object):
+ base_address = BILL_RESET
+ current_request = None
+
+ state = 'IDLE'
+
+ config_data = [
+ 0x01, # READER config data
+ 0x01, # Feature level
+ 0xff, 0xff, # UNKNOWN
+ #0x19, 0x85, # PLN x---DD
+ 0x01, # scale factor
+ 0x02, # decimal places factor
+ 0x01, # 1s response time
+ 0x00, # misc options...
+ ]
+
+ scaling_factor = 50
+ bills = [
+ 50, 100, 200, 500, 1000, 2000, 5000, 10000,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ ]
+
+ feed_bills = []
+
+ def __init__(self):
+ pass
+
+ def feed_amount(self, amount):
+ if amount % self.scaling_factor:
+ raise Exception('Invalid amount')
+
+ while amount > 0:
+ bills_list = filter(lambda v: v <= amount, self.bills)
+ bills_list.sort()
+ print (bills_list, amount)
+
+ self.feed_bills.append(self.bills.index(bills_list[-1]))
+ amount -= bills_list[-1]
+
+ def run(self):
+ pass
+ while True:
+ cnt, data = pi.bb_serial_read(RX_PIN)
+ for b in range(0, cnt, 2):
+ if data[b+1]:
+ if self.current_request: # and not self.current_request.processed:
+ print(self.current_request)
+
+ self.current_request = MDBRequest(data[b])
+ elif self.current_request:
+ self.current_request.data.append(data[b])
+ else:
+ print '[unexpected data: %02x]' % (data[b])
+
+ if self.current_request and not self.current_request.processed:
+ self.process_request(self.current_request)
+
+ def process_request(self, req):
+ if (req.command & self.base_address) != self.base_address:
+ return
+
+ if not req.validate_checksum():
+ return
+
+ if req.command == BILL_RESET:
+ req.processed = True
+ self.state = 'RESET'
+ print 'reset lol'
+ self.send([0x100])
+
+ elif req.command == BILL_POLL:
+ req.processed = True
+ print 'poll', self.state
+ #self.send([0b10000000], checksum=True)
+ if self.state == 'RESET':
+ self.send([0b110, 0x100]) # validator was reset
+ self.state = 'SETUP'
+ elif self.feed_bills:
+ self.send([0b10000000 + self.feed_bills[-1]], checksum=True)
+ else:
+ self.send([0x100])
+
+ elif req.command == BILL_SETUP:
+ print 'setup', self.state
+ req.processed = True
+ self.send([
+ 0x01, # level
+ 0x19, 0x85, # currency
+ 0x00, self.scaling_factor, # scaling factor
+ 0x02, # decimals
+ 0x00, 0x01, # stacker size
+ 0xff, 0xff, # security levels
+ 0x00, # no escrow
+ ] +
+ [b / self.scaling_factor for b in self.bills], checksum=True)
+ elif req.command == BILL_EXP and req.data[0] == BILL_EXP_ID:
+ self.send([
+ ord('G'), ord('M'), ord('D'), # manufacturer
+ ] +
+ [ord('0')] * 12 + # SN
+ [ord('0')] * 12 + # Model
+ [0x21, 0x37], checksum=True)
+ req.processed = True
+ elif req.command == BILL_TYPE and len(req.data) == 5:
+ req.processed = True
+ self.send([0x100]) # just confirm
+ elif req.command == BILL_STACKER:
+ req.processed = True
+ self.send([0b10000000, 0b00000001], checksum=True)
+ self.feed_bills.pop()
+
+ '''
+ elif req.command == CASHLESS_POLL:
+ print 'POLL', self.state
+ req.processed = True
+ if self.state == 'RESET':
+ #self.send([0x0a, 0x00], checksum=True)
+ self.send([0x00, 0x100])
+ self.state = 'IDLE'
+ #self.send([0x03, 0x00, 0x10], checksum=True)
+ #self.send([0x02, 0x05, ord('x'), ord('D')], checksum=True)
+ elif self.state == 'SETUP':
+ self.send(self.config_data, checksum=True)
+ self.state = 'IDLE'
+ elif self.state == 'IDLE':
+ self.send([0x100])
+
+ elif req.command == CASHLESS_SETUP and len(req.data) == 6 and req.validate_checksum():
+ req.processed = True
+ print 'VMC setup data:', req.data[1:-1]
+ vmc_level, disp_cols, disp_rows, disp_info = req.data[1:-1]
+ print 'vmc_level:', vmc_level
+ print 'disp_cols:', disp_cols
+ print 'disp_rows:', disp_rows
+ print 'disp_info:', disp_info
+
+ self.send(self.config_data, checksum=True)
+
+ self.state = 'SETUP'
+ '''
+
+ def send(self, data, checksum=False):
+ msg = ''.join([struct.pack('<h', n) for n in data])
+ if checksum:
+ msg += chr(compute_chk(data)) + '\x01'
+
+ print(', '.join('%02x' % ord(b) for b in msg))
+
+ pi.wave_clear()
+ pi.wave_add_serial(TX_PIN, 9600, msg, bb_bits=9, bb_stop=6)
+ wid = pi.wave_create()
+
+ pi.set_mode(TX_PIN, pigpio.OUTPUT)
+ pi.wave_send_once(wid) # transmit serial data
+ while pi.wave_tx_busy(): # wait until all data sent
+ pass
+
+ pi.wave_delete(wid)
+ pi.set_mode(TX_PIN, pigpio.INPUT)
+
+dev = MDBDevice()
+dev.feed_amount(150)
+dev.run()