summaryrefslogtreecommitdiffstats
path: root/bitvend-bill.py
blob: d6795c54d60a7a41cfa44548c2c36640867bcda9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
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()