root / pykocard / trunk / pykocard / cartadistcrs.py @ 3543

Revision 3543, 12.1 kB (checked in by jerome, 14 years ago)

Doesn't waste anymore the reading of one character at device opening
time. Activates debug mode in the testing program.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# -*- coding: utf-8 -*-
2#
3# PyKoCard
4#
5# PyKoCard : Smart Card / Vending Card managing library
6#
7# (c) 2010 Jerome Alet <alet@librelogiciel.com>
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20#
21# $Id$
22#
23
24import sys
25
26import serial # On Debian/Ubuntu : apt-get install python-serial
27
28# Some constants : names are mine, not Cartadis.
29#
30# Write errors
31NOERROR = 0
32ERRWRITEERROR = -1
33ERRNOCARD = -2
34ERRCARDBLOCKED = -3
35ERRUNKNOWNCARD = -4
36ERRINVALID = -5
37ERRMAXTRANSACTION = -6
38ERRVALUETOOHIGH = -7
39ERRGROUPNOTALLOWED = -8
40ERRWRITEBEFOREREAD = -9
41ERRREADBEFOREWRITE = -10
42ERRCOMPARISON = -11
43#
44# Read errors
45ERRREADERROR = -1
46#
47# Other errors
48ERRILLEGALGROUP = -1
49ERRNOTADMIN = -9
50ERRLISTFULL = -10
51ERRADMINNOTALLOWED = -11
52
53# Sensor
54SENSORNOCARD=0     # No card present
55SENSORUNKNOWN1=1   # Partially inside the TCRS
56SENSORCARDINSIDE=2 # Card is inside the TCRS
57SENSORUNKNOWN3=3   # Partially inside the TCRS
58
59class CartadisTCRS :
60    """A class to manage Cartadis TCRS vending card readers.
61
62       Documentation was found in a Cartadis TCRS reader's paper manual.
63
64       Cartadis is a registered trademark from Copie Monnaie France (C.M.F.)
65    """
66    def __init__(self, device, timeout=1.0, debug=False) :
67        """Initializes the connection to the TCRS."""
68        self.device = device
69        self.timeout = timeout
70        self.debug = debug
71
72        self.lastcommand = None
73        self.shortprompt = '$'
74        self.sol = chr(13) + chr(10) # start of line (begins each answer)
75        self.sollen = len(self.sol)
76        self.prompt = chr(13) + chr(10) + self.shortprompt # the prompt
77        self.promptlen = len(self.prompt)
78        self.eoc = chr(13) # end of command
79
80        # Each Cartadis vending card contain the following informations :
81        self.cardcontent = { "group" : None, # the card can only be read on a TCRS for which this group number was specifically allowed.
82                             "value" : None, # the number of credits on the card.
83                             "department" : None, # these two fields can be used to identify the owner of the card
84                             "account" : None,
85                             "trnum" : None  # Transaction number : Max 3000 for plastic cars, else 500.
86                           }
87
88        # opens the connection to the TCRS
89        self.tcrs = serial.Serial(device,
90                                  baudrate=9600,
91                                  bytesize=serial.EIGHTBITS,
92                                  parity=serial.PARITY_NONE,
93                                  stopbits=serial.STOPBITS_ONE,
94                                  xonxoff=False,
95                                  rtscts=True,
96                                  timeout=timeout)
97
98        # cleans up any data waiting to be read or written
99        try :
100            self.tcrs.flushInput()
101            self.tcrs.flushOutput()
102        except serial.serialutil.SerialException, msg :
103            self.logError(msg)
104            self.close()
105        else :
106            # Identifies the terminal
107            self.versionNumber = self.version()
108            self.serialNumber = self.serial()
109            self.logDebug("%s TCRS detected on device %s with serial number %s" \
110                              % (self.versionNumber,
111                                 self.device,
112                                 self.serialNumber))
113
114    def __del__(self) :
115        """Ensures the serial link is closed on deletion."""
116        self.close()
117
118    def close(self) :
119        """Closes the serial link if it is open."""
120        if self.tcrs is not None :
121            self.logDebug("Closing serial link...")
122            self.tcrs.close()
123            self.tcrs = None
124            self.logDebug("Serial link closed.")
125
126    def logError(self, message) :
127        """Logs an error message."""
128        sys.stderr.write("%s\n" % message)
129        sys.stderr.flush()
130
131    def logDebug(self, message) :
132        """Logs a debug message."""
133        if self.debug :
134            self.logError(message)
135
136    def sendCommand(self, cmd, param=None) :
137        """Sends a command to the TCRS."""
138        if self.tcrs is not None :
139            if param is not None :
140                command = "%s %s%s" % (cmd, param, self.eoc)
141            else :
142                command = "%s%s" % (cmd, self.eoc)
143            self.logDebug("Sending %s to TCRS" % repr(command))
144            self.tcrs.write(command)
145            self.tcrs.flush()
146            self.lastcommand = command
147            #
148            # IMPORTANT : the following code doesn't work because currently
149            # PySerial doesn't allow an EOL marker to be several chars long.
150            # I've just sent a patch for this to PySerial's author, and we'll
151            # see what happens. If not accepted, I'll write it another way.
152            answer = self.tcrs.readline(eol=self.prompt)
153            self.logDebug("TCRS answered %s" % repr(answer))
154            if answer.startswith(self.shortprompt) :
155                answer = answer[len(self.shortprompt):]
156            if answer.startswith(command) :
157                answer = answer[len(command):]
158            if answer.startswith(self.sol) and answer.endswith(self.prompt) :
159                return answer[self.sollen:-self.promptlen]
160            else :
161                if answer and (answer != self.sol) :
162                    self.logError("Unknown answer %s" % repr(answer))
163                return None
164        else :
165            self.logError("Device %s is not open" % self.device)
166
167    def help(self) :
168        """Returns the list of commands supported by the TCRS."""
169        return self.sendCommand("help")
170
171    def version(self) :
172        """Returns the TCRS' version string."""
173        return self.sendCommand("version")
174
175    def serial(self) :
176        """Returns the TCRS' serial number.'"""
177        return self.sendCommand("serial")
178
179    def read(self) :
180        """Reads the card's content to the TCRS. Returns the type of card or an error value."""
181        return int(self.sendCommand("read") or -1)
182
183    def write(self) :
184        """Writes the TCRS values to the card. Returns 0 or error value."""
185        return int(self.sendCommand("write"))
186
187    def sensor(self) :
188        """Returns 0 if there's no card in TCRS, else 1, 2 or 3."""
189        return int(self.sendCommand("sensor"))
190
191    def eject(self) :
192        """Ejects the card from the TCRS."""
193        return self.sendCommand("eject")
194
195    def trnum(self) :
196        """Returns the number of transactions made with this card."""
197        return int(self.sendCommand("trnum"))
198
199    def value(self, value=None) :
200        """Returns the last value read, or sets the new value of the card, but doesn't write it to the card yet."""
201        if value is None :
202            return int(self.sendCommand("value"))
203        else :
204            return self.sendCommand("value", str(value))
205
206    def account(self, account=None) :
207        """Returns the last account number read, or sets the account number, but doesn't write it to the card yet.'"""
208        if account is None :
209            return int(self.sendCommand("account"))
210        else :
211            return self.sendCommand("account", str(account))
212
213    def department(self, department=None) :
214        """Returns the last department number read, or sets the department number, but doesn't write it to the card yet.'"""
215        if department is None :
216            return int(self.sendCommand("department"))
217        else :
218            return self.sendCommand("department", str(department))
219
220    def group(self, group=None) :
221        """Returns the last group number read, or sets the group number, but doesn't write it to the card yet.'"""
222        if group is None :
223            return int(self.sendCommand("group"))
224        else :
225            return self.sendCommand("group", str(group))
226
227    def addgrp(self, group=None) :
228        """Adds the group to the list of allowed ones. If no group, the one on the admin card is used."""
229        return int(self.sendCommand("addgrp", str(group)))
230
231    def listgrp(self) :
232        """Returns the list of allowed group numbers."""
233        return [int(g) for g in self.sendCommand("listgrp").split()]
234
235    def delgrp(self, group) :
236        """Deletes the group from the list of allowed groups."""
237        return int(self.sendCommand("delgrp", str(group)))
238
239    def cardtype(self, cardtype=None) :
240        """Returns the type of card, or sets it (not clear in the doc if a write call is needed or not)."""
241        # TODO : doesn't seem to return a meaningful answer
242        if cardtype is None :
243            answer = self.sendCommand("cardtype")
244        else :
245            answer = self.sendCommand("cardtype", str(cardtype))
246        try :
247            return int(answer)
248        except ValueError :
249            self.logError("Unknown card type %s" % repr(answer))
250            return None
251
252    def display(self, text) :
253        """Displays a string of text on the TCRS' screen."""
254        return self.sendCommand("display", text)
255
256    def echo(self, echo) :
257        """Changes the type of echo for the TCRS' keyboard."""
258        raise NotImplementedError
259
260    def key(self, key) :
261        """Not really clear what it does..."""
262        raise NotImplementedError
263
264    def getstr(self) :
265        """Returns a string from keyboard or -1 if buffer is empty."""
266        raise NotImplementedError
267
268    def getkey(self) :
269        """Returns the value of the key pressed, or -1 if no key was hit."""
270        raise NotImplementedError
271
272    def prompt1(self, prompt1) :
273        """Changes the 'Introduce card' message."""
274        raise NotImplementedError
275
276    def prompt2(self, prompt2) :
277        """Changes the 'Credit:' message."""
278        raise NotImplementedError
279
280    def prompt3(self, prompt3) :
281        """Changes the text displayed after the value of the card (e.g. 'EURO')."""
282        raise NotImplementedError
283
284if __name__ == "__main__" :
285    # Minimal testing
286    tcrs = CartadisTCRS("/dev/ttyS0", debug=True)
287    try :
288        sys.stdout.write("%s TCRS detected on device %s with serial number %s\n" \
289                              % (tcrs.versionNumber,
290                                 tcrs.device,
291                                 tcrs.serialNumber))
292
293
294        sys.stdout.write("This Cartadis TCRS supports the following commands :\n%s\n" % tcrs.help())
295        sys.stdout.write("Allowed groups : %s\n" % tcrs.listgrp())
296
297        import time
298        sys.stdout.write("Please insert your card into the TCRS...")
299        sys.stdout.flush()
300        while True :
301            cardpresent = tcrs.sensor()
302            tcrs.logDebug("Sensor Status : %i\n" % cardpresent)
303            if cardpresent == SENSORCARDINSIDE :
304                break
305            time.sleep(1.0)
306        sys.stdout.write("\n")
307
308        sys.stdout.write("Card read status : %s\n" % tcrs.read())
309        sys.stdout.write("Group : %s\n" % tcrs.group())
310        value = tcrs.value()
311        tcrs.display("Card has %s credits" % value)
312        sys.stdout.write("Card has %s credits\n" % value)
313        sys.stdout.write("Department : %s\n" % tcrs.department())
314        sys.stdout.write("Account : %s\n" % tcrs.account())
315        sys.stdout.write("Transaction # : %s\n" % tcrs.trnum())
316        #
317        # This block commented out because I don't have many credits for testing ;-)
318        # It seems to work anyway.
319        # Now we decrement the number of credits
320        #tcrs.value(value-1)
321        # And we flush the card's content to the card
322        #sys.stdout.write("Card write status : %s\n" % tcrs.write())
323        # Now we read it back
324        #tcrs.read()
325        #sys.stdout.write("Card now has %s credits\n" % tcrs.value())
326    finally :
327        # We always do an eject, even if card not present
328        tcrs.eject()
329        tcrs.close()
Note: See TracBrowser for help on using the browser.