Cleanup, deployment playbook
parent
c0f8cd27b1
commit
c4d6f5c039
256
bitvend-bill.py
256
bitvend-bill.py
|
@ -1,256 +0,0 @@
|
|||
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()
|
|
@ -0,0 +1,6 @@
|
|||
[defaults]
|
||||
remote_user=root
|
||||
hostfile=hosts
|
||||
|
||||
[ssh_connection]
|
||||
pipelining=True
|
|
@ -0,0 +1,2 @@
|
|||
[bitvend]
|
||||
vending.waw.hackerspace.pl
|
|
@ -0,0 +1,19 @@
|
|||
server {
|
||||
listen 80 default_server;
|
||||
server_name vending.waw.hackerspace.pl;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
listen 443 ssl default_server;
|
||||
|
||||
ssl_certificate /var/lib/dehydrated/certs/vending.waw.hackerspace.pl/fullchain.pem;
|
||||
ssl_certificate_key /var/lib/dehydrated/certs/vending.waw.hackerspace.pl/privkey.pem;
|
||||
|
||||
if ($scheme = http) {
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
- hosts: bitvend
|
||||
tasks:
|
||||
- apt: name="{{ item }}" state=present
|
||||
with_items:
|
||||
- pigpio
|
||||
- python3-pigpio
|
||||
- python3-pip
|
||||
- python3-pillow
|
||||
- nginx
|
||||
- dehydrated
|
||||
- user: name=bitvend home=/var/bitvend system=yes
|
||||
- synchronize: src=../../ dest=/var/bitvend
|
||||
- file: path=/var/bitvend owner=bitvend group=bitvend recurse=yes
|
||||
- pip: requirements=/var/bitvend/requirements.txt executable=/usr/bin/pip3
|
||||
- copy: src=bitvend.service dest=/etc/systemd/system
|
||||
- copy: src=nginx.site dest=/etc/nginx/sites-available/default
|
||||
- service: name=nginx state=reloaded
|
||||
- service: name=pigpiod enabled=yes state=started
|
||||
- service: name=bitvend enabled=yes state=started
|
||||
|
||||
# Cleanup
|
||||
- user: name=pi state=absent
|
|
@ -8,7 +8,7 @@ Flask-WTF==0.14.2
|
|||
itsdangerous==0.24
|
||||
Jinja2==2.9.4
|
||||
MarkupSafe==0.23
|
||||
Pillow==4.0.0
|
||||
Pillow>=4.0.0
|
||||
prometheus-client==0.0.18
|
||||
qrcode==5.3
|
||||
requests==2.12.4
|
||||
|
|
Loading…
Reference in New Issue