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

Revision 3540, 9.6 kB (checked in by jerome, 14 years ago)

API should be completely there, although partially implemented (no
handling for the terminal's keyboard for now).
TODO : define some additional constants.
TODO : check boundaries when setting values.
TODO : test with the real terminal.

  • 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
54class CartadisTCRS :
55    """A class to manage Cartadis TCRS vending card readers.
56
57       Documentation was found in a Cartadis TCRS reader's paper manual.
58
59       Cartadis is a registered trademark from Copie Monnaie France (C.M.F.)
60    """
61    def __init__(self, device, timeout=5.0, debug=False) :
62        """Initializes the connection to the TCRS."""
63        self.device = device
64        self.timeout = timeout
65        self.debug = debug
66
67        self.lastcommand = None
68        self.sol = chr(13) + chr(10) # start of line (begins each answer)
69        self.sollen = len(self.sol)
70        self.prompt = chr(13) + chr(10) + '$' # the prompt
71        self.promptlen = len(self.prompt)
72        self.eoc = chr(13) # end of command
73
74        # Each Cartadis vending card contain the following informations :
75        self.cardcontent = { "group" : None, # the card can only be read on a TCRS for which this group number was specifically allowed.
76                             "value" : None, # the number of credits on the card.
77                             "department" : None, # these two fields can be used to identify the owner of the card
78                             "account" : None,
79                             "trnum" : None  # Transaction number : Max 3000 for plastic cars, else 500.
80                           }
81
82        # opens the connection to the TCRS
83        self.tcrs = serial.Serial(device,
84                                  baudrate=9600,
85                                  bytesize=serial.EIGHTBITS,
86                                  parity=serial.PARITY_NONE,
87                                  stopbits=serial.STOPBITS_ONE,
88                                  xonxoff=False,
89                                  rtscts=True,
90                                  timeout=timeout)
91
92        # cleans up any data waiting to be read
93        try :
94            self.tcrs.flushInput()
95        except serial.serialutil.SerialException, msg :
96            self.logError(msg)
97            self.close()
98        else :
99            # Identifies the terminal
100            self.versionNumber = self.version()
101            self.serialNumber = self.serial()
102            self.logDebug("%s terminal detected on device %s with serial number %s" \
103                              % (self.versionNumber,
104                                 self.device,
105                                 self.serialNumber))
106            self.supportedCommands = self.help()
107            self.logDebug("Supported commands : %s" % self.supportedCommands)
108
109    def __del__(self) :
110        """Ensures the serial link is closed on deletion."""
111        self.close()
112
113    def close(self) :
114        """Closes the serial link if it is open."""
115        if self.tcrs is not None :
116            self.logDebug("Closing serial link...")
117            self.tcrs.close()
118            self.tcrs = None
119            self.logDebug("Serial link closed.")
120
121    def logError(self, message) :
122        """Logs an error message."""
123        sys.stderr.write("%s\n" % message)
124        sys.stderr.flush()
125
126    def logDebug(self, message) :
127        """Logs a debug message."""
128        if self.debug :
129            self.logError(message)
130
131    def sendCommand(self, cmd, param=None) :
132        """Sends a command to the TCRS."""
133        if self.tcrs is not None :
134            if param is not None :
135                command = "%s %s%s" % (cmd, param, self.eoc)
136            else :
137                command = "%s%s" % (cmd, self.eoc)
138            self.logDebug("Sending %s to TCRS" % repr(command))
139            self.tcrs.write(command)
140            self.tcrs.flush()
141            self.lastcommand = command
142            #
143            # IMPORTANT : the following code doesn't work because currently
144            # PySerial doesn't allow an EOL marker to be several chars long.
145            # I've just sent a patch for this to PySerial's author, and we'll
146            # see what happens. If not accepted, I'll write it another way.
147            answer = self.tcrs.readline(eol=self.prompt)
148            self.logDebug("TCRS answered %s" % repr(answer))
149            if answer.startswith(self.sol) and answer.endswith(self.prompt) :
150                return answer[self.sollen:-self.promptlen]
151            else :
152                self.logError("Unknown answer %s" % repr(answer))
153                return None
154        else :
155            self.logError("Device %s is not open" % self.device)
156
157    def help(self) :
158        """Returns the list of commands supported by the TCRS."""
159        return self.sendCommand("help")
160
161    def version(self) :
162        """Returns the TCRS' version string."""
163        return self.sendCommand("version")
164
165    def serial(self) :
166        """Returns the TCRS' serial number.'"""
167        return self.sendCommand("serial")
168
169    def read(self) :
170        """Reads the card's content to the TCRS. Returns the type of card or an error value."""
171        return int(self.sendCommand("read"))
172
173    def write(self) :
174        """Writes the TCRS values to the card. Returns 0 or error value."""
175        return int(self.sendCommand("write"))
176
177    def sensor(self) :
178        """Returns 0 if there's no card in TCRS, else 1, 2 or 3."""
179        return int(self.sendCommand("sensor"))
180
181    def eject(self) :
182        """Ejects the card from the TCRS."""
183        return self.sendCommand("eject")
184
185    def trnum(self) :
186        """Returns the number of transactions made with this card."""
187        return int(self.sendCommand("trnum"))
188
189    def value(self, value=None) :
190        """Returns the last value read, or sets the new value of the card, but doesn't write it to the card yet."""
191        if value is None :
192            return int(self.sendCommand("value"))
193        else :
194            return self.sendCommand("value", value)
195
196    def account(self, account) :
197        """Returns the last account number read, or sets the account number, but doesn't write it to the card yet.'"""
198        if account is None :
199            return int(self.sendCommand("account"))
200        else :
201            return self.sendCommand("account", account)
202
203    def department(self, department) :
204        """Returns the last department number read, or sets the department number, but doesn't write it to the card yet.'"""
205        if department is None :
206            return int(self.sendCommand("department"))
207        else :
208            return self.sendCommand("department", department)
209
210    def group(self, group) :
211        """Returns the last group number read, or sets the group number, but doesn't write it to the card yet.'"""
212        if group is None :
213            return int(self.sendCommand("group"))
214        else :
215            return self.sendCommand("group", group)
216
217    def addgrp(self, group=None) :
218        """Adds the group to the list of allowed ones. If no group, the one on the admin card is used."""
219        return int(self.sendCommand("addgrp", group))
220
221    def listgrp(self) :
222        """Returns the list of allowed group numbers."""
223        return self.sendCommand("listgrp")
224
225    def delgrp(self, group) :
226        """Deletes the group from the list of allowed groups."""
227        return int(self.sendCommand("delgrp", group))
228
229    def cardtype(self, cardtype) :
230        """Returns the type of card, or sets it (not clear in the doc if a write call is needed or not)."""
231        return int(self.sendCommand("cardtype", cardtype))
232
233    def display(self, text) :
234        """Displays a string of text on the TCRS' screen."""
235        return self.sendCommand("display", text)
236
237    def echo(self, echo) :
238        """Changes the type of echo for the TCRS' keyboard."""
239        raise NotImplementedError
240
241    def key(self, key) :
242        """Not really clear what it does..."""
243        raise NotImplementedError
244
245    def getstr(self) :
246        """Returns a string from keyboard or -1 if buffer is empty."""
247        raise NotImplementedError
248
249    def getkey(self) :
250        """Returns the value of the key pressed, or -1 if no key was hit."""
251        raise NotImplementedError
252
253    def prompt1(self, prompt1) :
254        """Changes the 'Introduce card' message."""
255        raise NotImplementedError
256
257    def prompt2(self, prompt2) :
258        """Changes the 'Credit:' message."""
259        raise NotImplementedError
260
261    def prompt3(self, prompt3) :
262        """Changes the text displayed after the value of the card (e.g. 'EURO')."""
263        raise NotImplementedError
264
265if __name__ == "__main__" :
266    # Minimal testing
267    tcrs = CartadisTCRS("/dev/ttyS0", debug=True)
268    tcrs.help()
269    tcrs.close()
Note: See TracBrowser for help on using the browser.