summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcliechti <cliechti@f19166aa-fa4f-0410-85c2-fa1106f25c8a>2009-07-25 03:44:33 +0000
committercliechti <cliechti@f19166aa-fa4f-0410-85c2-fa1106f25c8a>2009-07-25 03:44:33 +0000
commitf81362e1e8eecba691ce30ea2ff2ecc8507639dc (patch)
tree2ca92284a842b5b2a9d85ac6f4c5073a3534da08
parentff94563d7c82c3581eebf641ea95997616ce9918 (diff)
downloadpyserial-git-f81362e1e8eecba691ce30ea2ff2ecc8507639dc.tar.gz
- add more methods for file-like compatibility
- provide RawSerial when io library is present (not yet finished) -> changes internal class hierarchy -> renamed internal read/write -> _read/_write (FileLike resp. RawSerialBase provides read/write) -> add test_rawio.py - _write returns number of byte written - set minimal python version to 2.3 due to basestring - add "name" attribute - documentation updates (new io stuff and VERSION, device())
-rw-r--r--documentation/pyserial.rst4
-rw-r--r--documentation/pyserial_api.rst74
-rw-r--r--pyserial/examples/test_rawio.py57
-rw-r--r--pyserial/serial/serialcli.py21
-rw-r--r--pyserial/serial/serialjava.py21
-rw-r--r--pyserial/serial/serialposix.py23
-rw-r--r--pyserial/serial/serialutil.py107
-rw-r--r--pyserial/serial/serialwin32.py19
-rw-r--r--pyserial/setup.py6
9 files changed, 295 insertions, 37 deletions
diff --git a/documentation/pyserial.rst b/documentation/pyserial.rst
index 1622498..2153f91 100644
--- a/documentation/pyserial.rst
+++ b/documentation/pyserial.rst
@@ -28,7 +28,7 @@ The homepage is at http://pyserial.sf.net.
Features
========
* Same class based interface on all supported platforms.
-* Access to the port settings through Python 2.2+ properties.
+* Access to the port settings through Python 2.3+ properties.
* Port numbering starts at zero, no need to know the port name in the user
program.
* Port string (device name) can be specified if access through numbering is
@@ -45,7 +45,7 @@ Features
Requirements
============
-* Python 2.2.3 or newer
+* Python 2.3 or newer
* ctypes extensions on Windows (is in standard library since Python 2.5+)
* "Java Communications" (JavaComm) or compatible extension for Java/Jython
diff --git a/documentation/pyserial_api.rst b/documentation/pyserial_api.rst
index f5d5791..f5fa01a 100644
--- a/documentation/pyserial_api.rst
+++ b/documentation/pyserial_api.rst
@@ -56,7 +56,7 @@ Classes
- ``timeout = None``: wait forever
- ``timeout = 0``: non-blocking mode (return immediately on read)
- - ``timeout = x``: set timeout to x seconds (float allowed)
+ - ``timeout = x``: set timeout to ``x`` seconds (float allowed)
.. method:: open()
@@ -67,9 +67,8 @@ Classes
Close port immediately.
- .. method:: setBaudrate(baudrate)
- Change baud rate on an open port.
+ The following methods may rise :exc:`ValueError` when applied to a closed port.
.. method:: inWaiting()
@@ -159,11 +158,17 @@ Classes
Read-only attributes:
- .. attribute:: portstr
+ .. attribute:: name
Device name. This is always the device name even if the
port was opened by a number. (Read Only).
+ .. versionadded:: 2.5
+
+ .. attribute:: portstr
+
+ :deprecated: use :attr:`name` instead
+
.. attribute:: BAUDRATES
A list of valid baud rates. The list may be incomplete such that higher
@@ -243,6 +248,19 @@ Classes
Set software flow control state.
+.. class:: RawSerial
+
+ This class is only present when run with Python 2.6 and newer that prides
+ the module :mod:`io`. It shares the same interface with :class:`Serial`
+ with the difference that :meth:`read` and :meth:`write` work with
+ :class:`bytes`and :class:`bytearrays`.
+
+ This also means that readline is borrowed from the :mod:`io` module and
+ lacks the ``eol`` parameter.
+
+ .. versionadded:: 2.5
+
+
.. class:: FileLike
An abstract file like class. It is used as base class for :class:`Serial`.
@@ -296,6 +314,33 @@ Classes
Raises NotImplementedError, needs to be overridden by subclass.
+ The following functions are implemented for compatibility with other
+ file-like objects, however serial ports are not seekable.
+
+
+ .. method:: seek(pos, whence=0)
+
+ :exception IOError: always, as method is not supported on serial port
+
+ .. versionadded:: 2.5
+
+ .. method:: tell()
+
+ :exception IOError: always, as method is not supported on serial port
+
+ .. versionadded:: 2.5
+
+ .. method:: truncate(self, n=None):
+
+ :exception IOError: always, as method is not supported on serial port
+
+ .. versionadded:: 2.5
+
+ .. method:: isatty()
+
+ :exception IOError: always, as method is not supported on serial port
+
+ .. versionadded:: 2.5
To be able to use the file like object as iterator for e.g.
``for line in Serial(0): ...`` usage:
@@ -422,3 +467,24 @@ Default control characters for software flow control.
.. data:: XON
.. data:: XOFF
+
+Version
+
+.. data:: VERSION
+
+ A string indicating the pySerial version, such as ``2.5``.
+
+Functions:
+
+.. function:: device(number)
+
+ :param number: Port number.
+ :return: String containing device name.
+ :deprecated: Use device names directly.
+
+ Convert a port number to a platform dependent device name. Unfortunately
+ this does not work well for all platforms; e.g. some may miss USB-Serial
+ converters and enumerate only internal serial ports.
+
+ The conversion may be made off-line, that is, there is no guarantee that
+ the returned device name really exists on the system.
diff --git a/pyserial/examples/test_rawio.py b/pyserial/examples/test_rawio.py
new file mode 100644
index 0000000..674934f
--- /dev/null
+++ b/pyserial/examples/test_rawio.py
@@ -0,0 +1,57 @@
+##! /usr/bin/env python
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see __init__.py
+#
+# (C) 2001-2008 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+"""\
+Some tests for the serial module.
+Part of pyserial (http://pyserial.sf.net) (C)2001-2009 cliechti@gmx.net
+
+Intended to be run on different platforms, to ensure portability of
+the code.
+
+This modules contains test for RawSerial. This only works on Python 2.6+ with
+the io library.
+
+For all these tests a simple hardware is required.
+Loopback HW adapter:
+Shortcut these pin pairs:
+ TX <-> RX
+ RTS <-> CTS
+ DTR <-> DSR
+
+On a 9 pole DSUB these are the pins (2-3) (4-6) (7-8)
+"""
+
+import unittest, threading, time
+import serial
+
+# on which port should the tests be performed:
+PORT=0
+
+class Test_RawSerial(unittest.TestCase):
+
+ def setUp(self):
+ self.s = serial.RawSerial(PORT)
+
+ def tearDown(self):
+ self.s.close()
+
+ def test_hello(self):
+ self.s.write(bytes("hello"))
+ hello = self.s.read(5)
+ #~ print hello
+ self.failUnlessEqual(hello, bytes("hello"))
+
+
+if __name__ == '__main__':
+ import sys
+ sys.stdout.write(__doc__)
+ if len(sys.argv) > 1:
+ PORT = sys.argv[1]
+ sys.stdout.write("Testing port: %r\n" % PORT)
+ sys.argv[1:] = ['-v']
+ # When this module is executed from the command-line, it runs all its tests
+ unittest.main()
diff --git a/pyserial/serial/serialcli.py b/pyserial/serial/serialcli.py
index 6961bf6..5360cb3 100644
--- a/pyserial/serial/serialcli.py
+++ b/pyserial/serial/serialcli.py
@@ -21,7 +21,7 @@ sab = System.Array[System.Byte]
def as_byte_array(string):
return sab([ord(x) for x in string])
-class Serial(SerialBase):
+class IronSerial(SerialBase):
"""Serial port implemenation for .NET/Mono."""
BAUDRATES = (50,75,110,134,150,200,300,600,1200,1800,2400,4800,9600,
@@ -145,7 +145,7 @@ class Serial(SerialBase):
if not self._port_handle: raise portNotOpenError
return self._port_handle.BytesToRead
- def read(self, size=1):
+ def _read(self, size=1):
"""Read size bytes from the serial port. If a timeout is set it may
return less characters as requested. With no timeout it will block
until the requested number of bytes is read."""
@@ -162,7 +162,7 @@ class Serial(SerialBase):
size -= 1
return ''.join(data)
- def write(self, data):
+ def _write(self, data):
"""Output the given string over the serial port."""
if not self._port_handle: raise portNotOpenError
if not isinstance(data, str):
@@ -173,6 +173,7 @@ class Serial(SerialBase):
self._port_handle.Write(as_byte_array(data), 0, len(data))
except System.TimeoutException, e:
raise writeTimeoutError
+ return len(data)
def flushInput(self):
"""Clear input buffer, discarding all that is in the buffer."""
@@ -230,6 +231,20 @@ class Serial(SerialBase):
return self._port_handle.CDHolding
# - - platform specific - - - -
+ # none
+
+
+# assemble Serial class with the platform specifc implementation and the base
+# for file-like behavior
+class Serial(IronSerial, FileLike):
+ pass
+
+# for Python 2.6 and newer, that provide the new I/O library, implement a
+# RawSerial object that plays nice with it.
+if support_io_module:
+ class RawSerial(IronSerial, RawSerialBase):
+ pass
+
#Nur Testfunktion!!
if __name__ == '__main__':
diff --git a/pyserial/serial/serialjava.py b/pyserial/serial/serialjava.py
index 2dbaad6..d7e68c4 100644
--- a/pyserial/serial/serialjava.py
+++ b/pyserial/serial/serialjava.py
@@ -46,7 +46,8 @@ def device(portnumber):
ports.append(el)
return ports[portnumber].getName()
-class Serial(SerialBase):
+
+class JavaSerial(SerialBase):
"""Serial port class, implemented with Java Communications API and
thus usable with jython and the appropriate java extension."""
@@ -144,7 +145,7 @@ class Serial(SerialBase):
if not self.sPort: raise portNotOpenError
return self._instream.available()
- def read(self, size=1):
+ def _read(self, size=1):
"""Read size bytes from the serial port. If a timeout is set it may
return less characters as requested. With no timeout it will block
until the requested number of bytes is read."""
@@ -160,10 +161,11 @@ class Serial(SerialBase):
read = read + chr(x)
return read
- def write(self, data):
+ def _write(self, data):
"""Output the given string over the serial port."""
if not self.sPort: raise portNotOpenError
self._outstream.write(data)
+ return len(data)
def flushInput(self):
"""Clear input buffer, discarding all that is in the buffer."""
@@ -190,7 +192,7 @@ class Serial(SerialBase):
"""Set terminal status line: Request To Send"""
if not self.sPort: raise portNotOpenError
self.sPort.setRTS(level)
-
+
def setDTR(self, level=1):
"""Set terminal status line: Data Terminal Ready"""
if not self.sPort: raise portNotOpenError
@@ -217,6 +219,17 @@ class Serial(SerialBase):
self.sPort.isCD()
+# assemble Serial class with the platform specifc implementation and the base
+# for file-like behavior
+class Serial(JaveSerial, FileLike):
+ pass
+
+# for Python 2.6 and newer, that provide the new I/O library, implement a
+# RawSerial object that plays nice with it.
+if support_io_module:
+ class RawSerial(JavaSerial, RawSerialBase):
+ pass
+
if __name__ == '__main__':
s = Serial(0,
diff --git a/pyserial/serial/serialposix.py b/pyserial/serial/serialposix.py
index 0b87090..2a989fd 100644
--- a/pyserial/serial/serialposix.py
+++ b/pyserial/serial/serialposix.py
@@ -265,7 +265,7 @@ TIOCSBRK = hasattr(TERMIOS, 'TIOCSBRK') and TERMIOS.TIOCSBRK or 0x5427
TIOCCBRK = hasattr(TERMIOS, 'TIOCCBRK') and TERMIOS.TIOCCBRK or 0x5428
-class Serial(SerialBase):
+class PosixSerial(SerialBase):
"""Serial port class POSIX implementation. Serial port configuration is
done with termios and fcntl. Runs on Linux and many other Un*x like
systems."""
@@ -273,9 +273,9 @@ class Serial(SerialBase):
def open(self):
"""Open port with current settings. This may throw a SerialException
if the port cannot be opened."""
+ self.fd = None
if self._port is None:
raise SerialException("Port must be configured before it can be used.")
- self.fd = None
# open
try:
self.fd = os.open(self.portstr, os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK)
@@ -434,7 +434,7 @@ class Serial(SerialBase):
s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str)
return struct.unpack('I',s)[0]
- def read(self, size=1):
+ def _read(self, size=1):
"""Read size bytes from the serial port. If a timeout is set it may
return less characters as requested. With no timeout it will block
until the requested number of bytes is read."""
@@ -453,9 +453,10 @@ class Serial(SerialBase):
break # early abort on timeout
return read
- def write(self, data):
+ def _write(self, data):
"""Output the given string over the serial port."""
if self.fd is None: raise portNotOpenError
+ #~ if not isinstance(port, basestring):
if not isinstance(data, str):
raise TypeError('expected str, got %s' % type(data))
t = len(data)
@@ -476,6 +477,7 @@ class Serial(SerialBase):
except OSError,v:
if v.errno != errno.EAGAIN:
raise
+ return len(data)
def flush(self):
"""Flush of file like objects. In this case, wait until all data
@@ -568,6 +570,19 @@ class Serial(SerialBase):
if self.fd is None: raise portNotOpenError
return self.fd
+
+# assemble Serial class with the platform specifc implementation and the base
+# for file-like behavior
+class Serial(PosixSerial, FileLike):
+ pass
+
+# for Python 2.6 and newer, that provide the new I/O library, implement a
+# RawSerial object that plays nice with it.
+if support_io_module:
+ class RawSerial(PosixSerial, RawSerialBase):
+ pass
+
+
if __name__ == '__main__':
s = Serial(0,
baudrate=19200, # baud rate
diff --git a/pyserial/serial/serialutil.py b/pyserial/serial/serialutil.py
index 71bafbf..da9a936 100644
--- a/pyserial/serial/serialutil.py
+++ b/pyserial/serial/serialutil.py
@@ -24,13 +24,14 @@ XOFF = chr(19)
class SerialException(Exception):
"""Base class for serial port related exceptions."""
-portNotOpenError = SerialException('Port not open')
+portNotOpenError = ValueError('Attempting to use a port that is not open')
class SerialTimeoutException(SerialException):
"""Write timeouts give an exception"""
writeTimeoutError = SerialTimeoutException("Write timeout")
+
class FileLike(object):
"""An abstract file like class.
@@ -45,8 +46,31 @@ class FileLike(object):
refuses to work (it raises an exception in this case)!
"""
- def read(self, size): raise NotImplementedError
- def write(self, s): raise NotImplementedError
+ def __init__(self):
+ self.closed = True
+
+ def close(self):
+ self.closed = True
+
+ # so that ports are closed when objects are discarded
+ def __del__(self):
+ """Destructor. Calls close()."""
+ # The try/except block is in case this is called at program
+ # exit time, when it's possible that globals have already been
+ # deleted, and then the close() call might fail. Since
+ # there's nothing we can do about such failures and they annoy
+ # the end users, we suppress the traceback.
+ try:
+ self.close()
+ except:
+ pass
+
+ # read and write directly use the platform dependent implementation
+ def read(self, size=1):
+ return self._read(size)
+
+ def write(self, data):
+ return self._write(data)
def readline(self, size=None, eol='\n'):
"""read a line which is terminated with end-of-line (eol) character
@@ -101,15 +125,32 @@ class FileLike(object):
def __iter__(self):
return self
+ # other functions of file-likes - not used by pySerial
+
+ #~ readinto(b)
+
+ def seek(self, pos, whence=0):
+ raise IOError("file is not seekable")
-class SerialBase(FileLike):
+ def tell(self):
+ raise IOError("file is not seekable")
+
+ def truncate(self, n=None):
+ raise IOError("file is not seekable")
+
+ def isatty(self):
+ return False
+
+
+class SerialBase(object):
"""Serial port base class. Provides __init__ function and properties to
get/set port settings."""
# default values, may be overridden in subclasses that do not support all values
- BAUDRATES = (50,75,110,134,150,200,300,600,1200,1800,2400,4800,9600,
- 19200,38400,57600,115200,230400,460800,500000,576000,921600,
- 1000000,1152000,1500000,2000000,2500000,3000000,3500000,4000000)
+ BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000,
+ 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000,
+ 3000000, 3500000, 4000000)
BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE)
STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
@@ -194,13 +235,14 @@ class SerialBase(FileLike):
was_open = self._isOpen
if was_open: self.close()
if port is not None:
- if type(port) in [type(''), type(u'')]: # strings are taken directly
+ if isinstance(port, basestring):
self.portstr = port
else:
self.portstr = self.makeDeviceName(port)
else:
self.portstr = None
self._port = port
+ self.name = self.portstr
if was_open: self.open()
def getPort(self):
@@ -384,11 +426,48 @@ class SerialBase(FileLike):
self.dsrdtr,
)
+# for Python 2.6 and newer, that provide the new I/O library, implement a
+# RawSerial object that plays nice with it.
+try:
+ import io
+except ImportError:
+ support_io_module = False
+else:
+ support_io_module = True
+
+ class RawSerialBase(io.RawIOBase):
+ def readable(self): return True
+ def writable(self): return True
+ def readinto(self, b):
+ data = self._read(len(b))
+ n = len(data)
+ try:
+ b[:n] = data
+ except TypeError, err:
+ import array
+ if not isinstance(b, array.array):
+ raise err
+ b[:n] = array.array(b'b', data)
+ return n
+
+ def write(self, b):
+ if self.closed:
+ raise ValueError("write to closed file")
+ if isinstance(b, unicode):
+ raise TypeError("can't write unicode to binary stream")
+ n = len(b)
+ if n == 0:
+ return 0
+ self._write(b)
+
+
+
if __name__ == '__main__':
+ import sys
s = SerialBase()
- sys.stdio.write('port name: %s\n' % s.portstr)
- sys.stdio.write('baud rates: %s\n' % s.getSupportedBaudrates())
- sys.stdio.write('byte sizes: %s\n' % s.getSupportedByteSizes())
- sys.stdio.write('parities: %s\n' % s.getSupportedParities())
- sys.stdio.write('stop bits: %s\n' % s.getSupportedStopbits())
- sys.stdio.write('%s\n' % s)
+ sys.stdout.write('port name: %s\n' % s.portstr)
+ sys.stdout.write('baud rates: %s\n' % s.getSupportedBaudrates())
+ sys.stdout.write('byte sizes: %s\n' % s.getSupportedByteSizes())
+ sys.stdout.write('parities: %s\n' % s.getSupportedParities())
+ sys.stdout.write('stop bits: %s\n' % s.getSupportedStopbits())
+ sys.stdout.write('%s\n' % s)
diff --git a/pyserial/serial/serialwin32.py b/pyserial/serial/serialwin32.py
index 8c881b5..7df6236 100644
--- a/pyserial/serial/serialwin32.py
+++ b/pyserial/serial/serialwin32.py
@@ -17,7 +17,7 @@ def device(portnum):
"""Turn a port number into a device name"""
return 'COM%d' % (portnum+1) #numbers are transformed to a string
-class Serial(SerialBase):
+class Win32Serial(SerialBase):
"""Serial port implementation for Win32 based on ctypes."""
BAUDRATES = (50,75,110,134,150,200,300,600,1200,1800,2400,4800,9600,
@@ -196,7 +196,7 @@ class Serial(SerialBase):
raise SerialException('call to ClearCommError failed')
return comstat.cbInQue
- def read(self, size=1):
+ def _read(self, size=1):
"""Read size bytes from the serial port. If a timeout is set it may
return less characters as requested. With no timeout it will block
until the requested number of bytes is read."""
@@ -231,7 +231,7 @@ class Serial(SerialBase):
read = ''
return read
- def write(self, data):
+ def _write(self, data):
"""Output the given string over the serial port."""
if not self.hComPort: raise portNotOpenError
if not isinstance(data, str):
@@ -247,6 +247,7 @@ class Serial(SerialBase):
err = win32.GetOverlappedResult(self.hComPort, self._overlappedWrite, ctypes.byref(n), True)
if n.value != len(data):
raise writeTimeoutError
+ return n.value
def flushInput(self):
@@ -339,6 +340,18 @@ class Serial(SerialBase):
raise SerialException('call to ClearCommError failed')
return comstat.cbOutQue
+# assemble Serial class with the platform specifc implementation and the base
+# for file-like behavior
+class Serial(Win32Serial, FileLike):
+ pass
+
+# for Python 2.6 and newer, that provide the new I/O library, implement a
+# RawSerial object that plays nice with it.
+if support_io_module:
+ class RawSerial(Win32Serial, RawSerialBase):
+ pass
+
+
# Nur Testfunktion!!
if __name__ == '__main__':
diff --git a/pyserial/setup.py b/pyserial/setup.py
index f9c41ef..4198f32 100644
--- a/pyserial/setup.py
+++ b/pyserial/setup.py
@@ -14,9 +14,9 @@ except ImportError:
raise ImportError("build_py_2to3 not found in distutils - it is required for Python 3.x")
from distutils.command.build_py import build_py
-if sys.version < '2.2.3':
- # distutils that old can't cope with the "classifiers" or
- # "download_url" keywords and True/False constants are missing
+if sys.version < '2.3':
+ # distutils that old can't cope with the "classifiers" or "download_url"
+ # keywords and True/False constants and basestring are missing
raise ValueError("Sorry Python versions older than 2.2.3 are no longer"
"supported - check http://pyserial.sf.net for older "
"releases or upgrade your Python installation.")