Zeiterfassung und pyserial entfernt
zeit.py las eine Stempeluhr ueber die serielle Schnittstelle aus und schrieb in die Tabelle zeiten. Kein Startskript rief es auf, kein Modul importierte es. Damit faellt auch pyserial weg - ausser zwei Testdateien in sunspec2, die nicht ausgefuehrt werden, hat serial keinen Nutzer mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -1,13 +0,0 @@
|
||||
**/__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
documentation/_build
|
||||
build
|
||||
dist
|
||||
*.egg-info
|
||||
|
||||
/MANIFEST
|
||||
|
||||
.idea
|
||||
venv
|
||||
xxx*
|
||||
@@ -1,16 +0,0 @@
|
||||
# Copyright Roger Meier <r.meier@siemens.com>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
language: python
|
||||
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- pypy
|
||||
- pypy3
|
||||
|
||||
script:
|
||||
- python setup.py install
|
||||
- python test/run_all_tests.py loop://
|
||||
@@ -1,825 +0,0 @@
|
||||
========================
|
||||
pySerial Release Notes
|
||||
========================
|
||||
|
||||
Version 1.0 13 Feb 2002
|
||||
---------------------------
|
||||
- First public release.
|
||||
- Split from the pybsl application (see http://mspgcc.sourceforge.net)
|
||||
|
||||
New Features:
|
||||
|
||||
- Added Jython support
|
||||
|
||||
|
||||
Version 1.1 14 Feb 2002
|
||||
---------------------------
|
||||
Bugfixes:
|
||||
|
||||
- Win32, when not specifying a timeout
|
||||
- Typos in the Docs
|
||||
|
||||
New Features:
|
||||
|
||||
- added ``serialutil`` which provides a base class for the ``Serial``
|
||||
objects.
|
||||
|
||||
- ``readline``, ``readlines``, ``writelines`` and ``flush`` are now supported
|
||||
see README.txt for deatils.
|
||||
|
||||
|
||||
Version 1.11 14 Feb 2002
|
||||
---------------------------
|
||||
Same as 1.1 but added missing files.
|
||||
|
||||
|
||||
Version 1.12 18 Feb 2002
|
||||
---------------------------
|
||||
Removed unneeded constants to fix RH7.x problems.
|
||||
|
||||
|
||||
Version 1.13 09 Apr 2002
|
||||
---------------------------
|
||||
Added alternate way for enabling rtscts (CNEW_RTSCTS is tried too)
|
||||
If port opening fails, a ``SerialException`` is raised on all platforms
|
||||
|
||||
|
||||
Version 1.14 29 May 2002
|
||||
---------------------------
|
||||
Added examples to archive
|
||||
Added non-blocking mode for ``timeout=0`` (tnx Mat Martineau)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- win32 does now return the remaining characters on timeout
|
||||
|
||||
|
||||
Version 1.15 04 Jun 2002
|
||||
---------------------------
|
||||
Bugfixes (win32):
|
||||
|
||||
- removed debug messages
|
||||
- compatibility to win9x improved
|
||||
|
||||
|
||||
Version 1.16 02 Jul 2002
|
||||
---------------------------
|
||||
Added implementation of RI and corrected RTS/CTS on Win32
|
||||
|
||||
|
||||
Version 1.17 03 Jul 2002
|
||||
---------------------------
|
||||
Silly mix of two versions in win32 code corrected
|
||||
|
||||
|
||||
Version 1.18 06 Dec 2002
|
||||
---------------------------
|
||||
Bugfixes (general):
|
||||
|
||||
- remove the mapping of flush to the destructive flushOutput as
|
||||
this is not the expected behaviour.
|
||||
- readline: EOL character for lines can be chosen idea by
|
||||
John Florian.
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- cygwin port numbering fixed
|
||||
- test each and every constant for it's existence in termios module,
|
||||
use default if not existent (fix for Bug item #640214)
|
||||
- wrong exception on nonexistent ports with /dev file. bug report
|
||||
by Louis Cordier
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- RTS/CTS handling as suggested in Bug #635072
|
||||
- bugfix of timeouts brought up by Markus Hoffrogge
|
||||
|
||||
|
||||
Version 1.19 19 Mar 2003
|
||||
---------------------------
|
||||
Bugfixes (posix):
|
||||
|
||||
- removed ``dgux`` entry which actually had a wrong comment and is
|
||||
probably not in use anywhere.
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- added ``int()`` conversion, [Bug 702120]
|
||||
- remove code to set control lines in close method of win32
|
||||
version. [Bug 669625]
|
||||
|
||||
|
||||
Version 1.20 28 Aug 2003
|
||||
---------------------------
|
||||
- Added ``serial.device()`` for all platforms
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- don't recreate overlapped structures and events on each
|
||||
read/write.
|
||||
- don't set unneeded event masks.
|
||||
- don't use DOS device names for ports > 9.
|
||||
- remove send timeout (it's not used in the linux impl. anyway).
|
||||
|
||||
|
||||
Version 1.21 30 Sep 2003
|
||||
---------------------------
|
||||
Bugfixes (win32):
|
||||
|
||||
- name for COM10 was not built correctly, found by Norm Davis.
|
||||
|
||||
Bugfixes (examples):
|
||||
|
||||
- small change in ``miniterm.py`` that should mage it run on cygwin,
|
||||
[Bug 809904] submitted by Rolf Campbell.
|
||||
|
||||
|
||||
Version 2.0b1 1 Oct 2003
|
||||
---------------------------
|
||||
Transition to the Python 2.0 series:
|
||||
|
||||
- New implementation only supports Python 2.2+, backwards compatibility
|
||||
should be maintained almost everywhere.
|
||||
The OS handles (like the ``hComPort`` or ``fd`` attribute) were prefixed
|
||||
with an underscore. The different names stay, as anyone that uses one of
|
||||
these has to write platform specific code anyway.
|
||||
- Common base class ``serialutil.SerialBase`` for all implementations.
|
||||
- ``PARITY_NONE``, ``PARITY_EVEN``, ``PARITY_ODD`` constants changed and all
|
||||
these constants moved to ``serialutil.py`` (still available as
|
||||
``serial.PARITY_NONE`` etc. and they should be used that way)
|
||||
- Added ``serial.PARITY_NAMES`` (implemented in ``serialutil.PARITY_NAMES``).
|
||||
This dictionary can be used to convert parity constants to meaningful
|
||||
strings.
|
||||
- Each Serial class and instance has a list of supported values:
|
||||
``BAUDRATES``, ``BYTESIZES``, ``PARITIES``, ``STOPBITS``Ggg
|
||||
(i.e. ``serial.Serial.BAUDRATES or s = serial.Serial; s.BAUDRATES``)
|
||||
these values can be used to fill in value sin GUI dialogs etc.
|
||||
- Creating a ``Serial()`` object without port spec returns an unconfigured,
|
||||
closed port. Useful if a GUI dialog should take a port and configure
|
||||
it.
|
||||
- New methods for ``serial.Serial`` instances: ``open()``, ``isOpen()``
|
||||
- A port can be opened and closed as many times as desired.
|
||||
- Instances of ``serial.Serial`` have ``baudrate``, ``bytesize``, ``timeout``
|
||||
etc. attributes implemented as properties, all can be set while the port is
|
||||
opened. It will then be reconfigured.
|
||||
- Improved ``__doc__``'s.
|
||||
- New ``test_advanced.py`` for the property setting/getting testing.
|
||||
- Small bugfix on posix with get* methods (return value should be true a
|
||||
boolean).
|
||||
- added a ``__repr__`` that returns a meaningful string will all the serial
|
||||
setting, easy for debugging.
|
||||
- The serialposix module does not throw an exception on unsupported
|
||||
platforms, the message is still printed. The idea that it may still
|
||||
work even if the platform itself s not known, it simply tries to do
|
||||
the posix stuff anyway (It's likely that opening ports by number
|
||||
fails, but by name it should work).
|
||||
|
||||
|
||||
Version 2.0b2 4 Oct 2003
|
||||
---------------------------
|
||||
- Added serial port configuration dialog for wxPython to the examples.
|
||||
- Added terminal application for wxPython with wxGlade design file
|
||||
to the examples.
|
||||
- Jython support is currently broken as Jython does not have a Python 2.2
|
||||
compatible release out yet
|
||||
|
||||
|
||||
Version 2.0 6 Nov 2003
|
||||
---------------------------
|
||||
- Fixes ``setup.py`` for older distutils
|
||||
|
||||
|
||||
Version 2.1 28 Jul 2004
|
||||
---------------------------
|
||||
Bugfixes:
|
||||
|
||||
- Fix XON/XOFF values [Bug 975250]
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- ``fd == 0`` fix from Vsevolod Lobko
|
||||
- netbsd fixes from Erik Lindgren
|
||||
- Dynamically lookup baudrates and some cleanups
|
||||
|
||||
Bugfixes (examples):
|
||||
|
||||
- CRLF handling of ``miniterm.py`` should be more consistent on Win32
|
||||
and others. Added LF only command line option
|
||||
- Multithreading fixes to ``wxTerminal.py`` (helps with wxGTK)
|
||||
- Small change for wxPython 2.5 in ``wxSerialConfigDialog.py`` [Bug 994856]
|
||||
|
||||
New Features:
|
||||
|
||||
- Implement write timeouts (``writeTimeout`` parameter)
|
||||
|
||||
|
||||
Version 2.2 31 Jul 2005
|
||||
---------------------------
|
||||
Bugfixes:
|
||||
|
||||
- [Bug 1014227]: property <del> broken
|
||||
- [Bug 1105687]: ``serial_tcp_example.py``: ``--localport`` option
|
||||
- [Bug 1106313]: device (port) strings cannot be unicode
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [Patch 1043436] Fix for [Bug 1043420] (OSError: EAGAIN)
|
||||
- [Patch 1102700] ``fileno()`` added
|
||||
- ensure disabled PARMRK
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Patch 983106]: keep RTS/CTS state on port setting changes
|
||||
|
||||
New Features:
|
||||
|
||||
- ``dsrdtr`` setting to enable/disable DSR/DTR flow control independently
|
||||
from the ``rtscts`` setting. (Currently Win32 only, ignored on other
|
||||
platforms)
|
||||
|
||||
|
||||
Version 2.3 19 Jun 2008
|
||||
---------------------------
|
||||
New Features:
|
||||
|
||||
- iterator interface. ``for line in Serial(...): ...`` is now possible
|
||||
Suggested by Bernhard Bender
|
||||
- ``sendBreak()`` accepts a ``duration`` argument. Default duration increased.
|
||||
- win32 handles \\.\COMx format automatically for com ports of higher number
|
||||
(COM10 is internally translated to \\.\COM10 etc.)
|
||||
- miniterm.py has a new feature to send a file (upload) and configurable
|
||||
special characters for exit and upload. Refactored internals to class based
|
||||
structure (upload and class refactoring by Colin D Bennett)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [Bug 1451535] TCP/serial redirect example "--help"
|
||||
- update VERSION variable
|
||||
- update wxSerialConfigDialog.py and wxTerminal.py compatibility with
|
||||
wxPython 2.8 (Peleg)
|
||||
- Check for string in write function. Using unicode causes errors, this
|
||||
helps catching errors early (Tom Lynn)
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [Bug 1554183] setRTS/setDTR reference to non existing local "on"
|
||||
- [Bug 1513653] file descriptor not closed when exception is thrown
|
||||
- FreeBSD now uses cuadX instead of cuaaX (Patrick Phalen)
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Bug 1520357] Handle leak
|
||||
- [Bug 1679013] Ignore exception raised by SetCommTimeout() in close().
|
||||
- [Bug 1938118] process hang forever under XP
|
||||
|
||||
|
||||
Version 2.4 6 Jul 2008
|
||||
---------------------------
|
||||
New Features:
|
||||
|
||||
- [Patch 1616790] pyserial: Add inter-character timeout feature
|
||||
- [Patch 1924805] add a setBreak function
|
||||
- Add mark/space parity
|
||||
- Add .NET/Mono backend (IronPython)
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [Bug 1783159] Arbitrary baud rates (Linux/Posix)
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Patch 1561423] Add mark/space parity, Win32
|
||||
- [Bug 2000771] serial port CANNOT be specified by number on windows
|
||||
- examples/scanwin32.py does no longer return \\.\ names
|
||||
- fix \\.\ handling for some cases
|
||||
|
||||
Bugfixes (jython):
|
||||
|
||||
- The Jython backend tries javax.comm and gnu.io (Seo Sanghyeon)
|
||||
|
||||
|
||||
Version 2.5-rc1 2009-07-30
|
||||
---------------------------
|
||||
New Features:
|
||||
|
||||
- Python 3.x support (through 2to3)
|
||||
- compatible with Python io library (Python 2.6+)
|
||||
- Support for Win32 is now written on the top of ctypes (bundled with
|
||||
Python 2.5+) instead of pywin32 (patch by Giovanni Bajo).
|
||||
- 1.5 stop bits (STOPBITS_ONE_POINT_FIVE, implemented on all platforms)
|
||||
- miniterm application extended (CTRL+T -> menu)
|
||||
- miniterm.py is now installed as "script"
|
||||
- add scanlinux.py example
|
||||
- add port_publisher example
|
||||
- experimental RFC-2217 server support (examples/rfc2217_server.py)
|
||||
- add ``getSettingsDict`` and ``applySettingsDict`` serial object methods
|
||||
- use a ``poll`` based implementation on Posix, instead of a ``select`` based,
|
||||
provides better error handling [removed again in later releases].
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Improve and fix tcp_serial_redirector example.
|
||||
- [Bug 2603052] 5-bit mode (needs 1.5 stop bits in some cases)
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [Bug 2810169] Propagate exceptions raised in serialposix _reconfigure
|
||||
- [Bug 2562610] setting non standard baud rates on Darwin (Emmanuel Blot)
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Bug 2469098] parity PARITY_MARK, PARITY_SPACE isn't supported on win32
|
||||
- [SF 2446218] outWaiting implemented
|
||||
- [Bug 2392892] scanwin32.py better exception handling
|
||||
- [Bug 2505422] scanwin32.py Vista 64bit compatibility
|
||||
|
||||
|
||||
Version 2.5-rc2 2010-01-02
|
||||
---------------------------
|
||||
New Features:
|
||||
|
||||
- Documentation update, now written with Sphinx/ReST
|
||||
- Updated miniterm.py example
|
||||
- experimental RFC-2217 client support (serial.rfc2217.Serial, see docs)
|
||||
- add ``loop://`` device for testing.
|
||||
- add ``serial.serial_for_url`` factory function (support for native ports and
|
||||
``rfc2217``, ``socket`` and ``loop`` URLs)
|
||||
- add new example: ``rfc2217_server.py``
|
||||
- tests live in their own directory now (no longer in examples)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [Bug 2915810] Fix for suboption parsing in rfc2217
|
||||
- Packaging bug (missed some files)
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- improve write timeout behavior
|
||||
- [Bug 2836297] move Linux specific constants to not break other platforms
|
||||
- ``poll`` based implementation for ``read`` is in a separate class
|
||||
``PosixPollSerial``, as it is not supported well on all platforms (the
|
||||
default ``Serial`` class uses select).
|
||||
- changed error handling in ``read`` so that disconnected devices are
|
||||
detected.
|
||||
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Bug 2886763] hComPort doesn't get initialized for Serial(port=None)
|
||||
|
||||
|
||||
Version 2.5 2010-07-22
|
||||
---------------------------
|
||||
New Features:
|
||||
|
||||
- [Bug 2976262] dsrdtr should default to False
|
||||
``dsrdtr`` parameter default value changed from ``None`` (follow ``rtscts``
|
||||
setting) to ``False``. This means ``rtscts=True`` enables hardware flow
|
||||
control on RTS/CTS but no longer also on DTR/DSR. This change mostly
|
||||
affects Win32 as on other platforms, that setting was ignored anyway.
|
||||
- Improved xreadlines, it is now a generator function that yields lines as they
|
||||
are received (previously it called readlines which would only return all
|
||||
lines read after a read-timeout). However xreadlines is deprecated and not
|
||||
available when the io module is used. Use ``for line in Serial(...):``
|
||||
instead.
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [Bug 2925854] test.py produces exception with python 3.1
|
||||
- [Bug 3029812] 2.5rc2 readline(s) doesn't work
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [BUG 3006606] Nonblocking error - Unix platform
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Bug 2998169] Memory corruption at faster transmission speeds.
|
||||
(bug introduced in 2.5-rc1)
|
||||
|
||||
|
||||
Version 2.6 2011-11-02
|
||||
---------------------------
|
||||
New Features:
|
||||
|
||||
- Moved some of the examples to serial.tools so that they can be used
|
||||
with ``python -m``
|
||||
- serial port enumeration now included as ``serial.tools.list_ports``
|
||||
- URL handlers for ``serial_for_url`` are now imported dynamically. This allows
|
||||
to add protocols w/o editing files. The list
|
||||
``serial.protocol_handler_packages`` can be used to add or remove user
|
||||
packages with protocol handlers (see docs for details).
|
||||
- new URL type: hwgrep://<regexp> uses list_ports module to search for ports
|
||||
by their description
|
||||
- several internal changes to improve Python 3.x compatibility (setup.py,
|
||||
use of absolute imports and more)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [Bug 3093882] calling open() on an already open port now raises an exception
|
||||
- [Bug 3245627] connection-lost let rfc2217 hangs in closed loop
|
||||
- [Patch 3147043] readlines() to support multi-character eol
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [Patch 3316943] Avoid unneeded termios.tcsetattr calls in serialposix.py
|
||||
- [Patch 2912349] Serial Scan as a Module with Mac Support
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Bug 3057499] writeTimeoutError when write Timeout is 0
|
||||
- [Bug 3414327] Character out of range in list_ports_windows
|
||||
- [Patch 3036175] Windows 98 Support fix
|
||||
- [Patch 3054352] RTS automatic toggle, for RS485 functionality.
|
||||
- Fix type definitions for 64 bit Windows compatibility
|
||||
|
||||
|
||||
Version 2.7 2013-10-17
|
||||
---------------------------
|
||||
- Win32: setRTS and setDTR can be called before the port is opened and it will
|
||||
set the initial state on port open.
|
||||
- Posix: add platform specific method: outWaiting (already present for Win32)
|
||||
- Posix: rename flowControl to setXON to match name on Win32, add
|
||||
flowControlOut function
|
||||
- rfc2217: zero polls value (baudrate, data size, stop bits, parity) (Erik
|
||||
Lundh)
|
||||
- Posix: [Patch pyserial:28] Accept any speed on Linux [update]
|
||||
- Posix: [Patch pyserial:29] PosixSerial.read() should "ignore" errno.EINTR
|
||||
- OSX: [Patch pyserial:27] Scan by VendorID/Product ID for USB Serial devices
|
||||
- Ensure working with bytes in write() calls
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [Bug 3540332] SerialException not returned
|
||||
- [Bug pyserial:145] Error in socket_connection.py
|
||||
- [Bug pyserial:135] reading from socket with timeout=None causes TypeError
|
||||
- [Bug pyserial:130] setup.py should not append py3k to package name
|
||||
- [Bug pyserial:117] no error on lost conn w/socket://
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [Patch 3462364] Fix: NameError: global name 'base' is not defined
|
||||
- list_ports and device() for BSD updated (Anders Langworthy)
|
||||
- [Bug 3518380] python3.2 -m serial.tools.list_ports error
|
||||
- [Bug pyserial:137] Patch to add non-standard baudrates to Cygwin
|
||||
- [Bug pyserial:141] open: Pass errno from IOError to SerialException
|
||||
- [Bug pyserial:125] Undefined 'base' on list_ports_posix.py, function usb_lsusb
|
||||
- [Bug pyserial:151] Serial.write() without a timeout uses 100% CPU on POSIX
|
||||
- [Patch pyserial:30] [PATCH 1/1] serial.Serial() should not raise IOError.
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Bug 3444941] ctypes.WinError() unicode error
|
||||
- [Bug 3550043] on Windows in tools global name 'GetLastError' is not defined
|
||||
- [Bug pyserial:146] flush() does nothing in windows (despite docs)
|
||||
- [Bug pyserial:144] com0com ports ignored due to missing "friendly name"
|
||||
- [Bug pyserial:152] Cannot configure port, some setting was wrong. Can leave
|
||||
port handle open but port not accessible
|
||||
|
||||
|
||||
Version 3.0a0 2015-09-22
|
||||
--------------------------
|
||||
- Starting from this release, only Python 2.7 and 3.2 (or newer) are supported.
|
||||
The source code is compatible to the 2.x and 3.x series without any changes.
|
||||
The support for earlier Python versions than 2.7 is removed, please refer to
|
||||
the pyserial-legacy (V2.x) series if older Python versions are a
|
||||
requirement).
|
||||
- Development moved to github, update links in docs.
|
||||
- API changes: properties for ``rts``, ``dtr``, ``cts``, ``dsr``, ``cd``, ``ri``,
|
||||
``in_waiting`` (instead of get/set functions)
|
||||
- remove file ``FileLike`` class, add ``read_until`` and ``iread_until`` to
|
||||
``SerialBase``
|
||||
- RS485 support changed (``rts_toggle`` removed, added ``serial.rs485`` module
|
||||
and ``rs485_mode`` property)
|
||||
- ``socket://`` and ``rfc2217://`` handlers use the IPv6 compatible
|
||||
``socket.create_connection``
|
||||
- New URL handler: ``spy:://``.
|
||||
- URL handlers now require the proper format (``?`` and ``&``) for arguments
|
||||
instead of ``/`` (e.g. ``rfc2217://localhost:7000?ign_set_control&timeout=5.5``)
|
||||
- Remove obsolete examples.
|
||||
- Finish update to BSD license.
|
||||
- Use setuptools if available, fall back to distutils if unavailable.
|
||||
- miniterm: changed command line options
|
||||
- miniterm: support encodings on serial port
|
||||
- miniterm: new transformations, by default escape/convert all control characters
|
||||
- list_ports: improved, added USB location (Linux, Win32)
|
||||
- refactored code
|
||||
- [FTR pyserial:37] Support fileno() function in the socket protocol
|
||||
- Posix: [Patch pyserial:31] Mark/space parity on Linux
|
||||
- Linux: [Patch pyserial:32] Module list_ports for linux should include the
|
||||
product information as description.
|
||||
- Java: fix 2 bugs (stop bits if/else and non-integer timeouts) (Torsten
|
||||
Roemer)
|
||||
- Update wxSerialConfigDialog.py to use serial.tools.list_ports.
|
||||
- [Patch pyserial:34] Improvements to port_publisher.py example
|
||||
- [Feature pyserial:39] Support BlueTooth serial port discovery on Linux
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [Bug pyserial:157] Implement inWaiting in protocol_socket
|
||||
- [Bug pyserial:166] RFC2217 connections always fail
|
||||
- [Bug pyserial:172] applySettingsDict() throws an error if the settings dictionary is not complete
|
||||
- [Bug pyserial:185] SocketSerial.read() never returns data when timeout==0
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [Bug pyserial:156] PosixSerial.open raises OSError rather than
|
||||
SerialException when port open fails
|
||||
- [Bug pyserial:163] serial.tools.list_ports.grep() fails if it encounters None type
|
||||
- fix setXON
|
||||
- [Patch pyserial:36 / 38] Make USB information work in python 3.4 and 2.7
|
||||
- clear OCRNL/ONLCR flags (CR/LF translation settings)
|
||||
- [Feature pyserial:38] RS485 Support
|
||||
- [Bug pyserial:170] list_ports_posix not working properly for Cygwin
|
||||
- [Bug pyserial:187] improve support for FreeBSD (list_ports_posix)
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [Bug pyserial:169] missing "import time" in serialwin32.py
|
||||
|
||||
Bugfixes (cli):
|
||||
|
||||
- [Bug pyserial:159] write() in serialcli.py not working with IronPython 2.7.4
|
||||
|
||||
|
||||
Version 3.0b1 2015-10-19
|
||||
--------------------------
|
||||
- list_ports: add ``vid``, ``pid``, ``serial_number``, ``product``,
|
||||
``manufacturer`` and ``location`` attribute for USB devices.
|
||||
- list_ports: update OSX implementation.
|
||||
- list_ports: Raspberry Pi: internal port is found.
|
||||
- serial_for_url: fix import (multiple packages in list)
|
||||
- threaded: added new module implementing a reader thread
|
||||
- tweak examples/wx*
|
||||
- posix: add experimental implementation ``VTIMESerial``
|
||||
- new URL handler ``alt://`` to select alternative implementations
|
||||
|
||||
|
||||
Version 3.0 2015-12-28
|
||||
------------------------
|
||||
- minor fixes to setup.py (file list), inter_byte_timeout (not stored when
|
||||
passed to __init__), rfc2217 (behavior of close when open failed),
|
||||
list_ports (__str__), loop://, renamed ReaderThread
|
||||
- hwgrep:// added options to pick n'th port, skip busy ports
|
||||
- miniterm: --ask option added
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [#26/#30] always call tcsettattr on open
|
||||
- [#42] fix disregard read timeout if there is more data
|
||||
- [#45] check for write timeout, even if EAGAIN was raised
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [#27] fix race condition in ``read()``, fix minimal timeout issue
|
||||
- race condition in nonblocking case
|
||||
- [#49] change exception type in case SetCommState fails
|
||||
- [#50] fixed issue with 0 timeout on windows 10
|
||||
|
||||
|
||||
Version 3.0.1 2016-01-11
|
||||
--------------------------
|
||||
- special case for FDTIBUS in list_ports on win32 (#61)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- ``Serial`` keyword arguments, more on backward compatibility, fix #55
|
||||
- list_ports: return name if product is None, fix for #54
|
||||
- port_publisher: restore some sorting of ports
|
||||
|
||||
|
||||
Version 3.1.0 2016-05-27
|
||||
--------------------------
|
||||
Improvements:
|
||||
|
||||
- improve error handling in ``alt://`` handler
|
||||
- ``socket://`` internally used select, improves timeout behavior
|
||||
- initial state of RTS/DTR: ignore error when setting on open posix
|
||||
(support connecting to pty's)
|
||||
- code style updates
|
||||
- posix: remove "number_to_device" which is not called anymore
|
||||
- add cancel_read and cancel_write to win32 and posix implementations
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [#68] aio: catch errors and close connection
|
||||
- [#87] hexlify: update codec for Python 2
|
||||
- [#100] setPort not implemented
|
||||
- [#101] bug in serial.threaded.Packetizer with easy fix
|
||||
- [#104] rfc2217 and socket: set timeout in create_connection
|
||||
- [#107] miniterm.py fails to exit on failed serial port
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [#59] fixes for RTS/DTR handling on open
|
||||
- [#77] list_ports_osx: add missing import
|
||||
- [#85] serialposix.py _set_rs485_mode() tries to read non-existing
|
||||
rs485_settings.delay_rts_before_send
|
||||
- [#96] patch: native RS485 is never enabled
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- fix bad super call and duplicate old-style __init__ call
|
||||
- [#80] list_ports: Compatibility issue between Windows/Linux
|
||||
|
||||
|
||||
Version 3.1.1 2016-06-12
|
||||
--------------------------
|
||||
Improvements:
|
||||
|
||||
- deprecate ``nonblocking()`` method on posix, the port is already in this
|
||||
mode.
|
||||
- style: use .format() in various places instead of "%" formatting
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [#122] fix bug in FramedPacket
|
||||
- [#127] The Serial class in the .NET/Mono (IronPython) backend does not
|
||||
implement the _reconfigure_port method
|
||||
- [#123, #128] Avoid Python 3 syntax in aio module
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [#126] PATCH: Check delay_before_tx/rx for None in serialposix.py
|
||||
- posix: retry if interrupted in Serial.read
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- win32: handle errors of GetOverlappedResult in read(), fixes #121
|
||||
|
||||
|
||||
Version 3.2.0 2016-10-14
|
||||
--------------------------
|
||||
See 3.2.1, this one missed a merge request related to removing aio.
|
||||
|
||||
|
||||
Version 3.2.1 2016-10-14
|
||||
--------------------------
|
||||
Improvements:
|
||||
|
||||
- remove ``serial.aio`` in favor of separate package, ``pyserial-asyncio``
|
||||
- add client mode to example ``tcp_serial_redirect.py``
|
||||
- use of monotonic clock for timeouts, when available (Python 3.3 and up)
|
||||
- [#169] arbitrary baud rate support for BSD family
|
||||
- improve tests, improve ``loop://``
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [#137] Exception while cancel in miniterm (python3)
|
||||
- [#143] Class Serial in protocol_loop.py references variable before assigning
|
||||
to it
|
||||
- [#149] Python 3 fix for threaded.FramedPacket
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [#133] _update_dtr_state throws Inappropriate ioctl for virtual serial
|
||||
port created by socat on OS X
|
||||
- [#157] Broken handling of CMSPAR in serialposix.py
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [#144] Use Unicode API for list_ports
|
||||
- [#145] list_ports_windows: support devices with only VID
|
||||
- [#162] Write in non-blocking mode returns incorrect value on windows
|
||||
|
||||
|
||||
Version 3.3 2017-03-08
|
||||
------------------------
|
||||
Improvements:
|
||||
|
||||
- [#206] Exclusive access on POSIX. ``exclusive`` flag added.
|
||||
- [#172] list_ports_windows: list_ports with 'manufacturer' info property
|
||||
- [#174] miniterm: change cancel impl. for console
|
||||
- [#182] serialutil: add overall timeout for read_until
|
||||
- socket: use non-blocking socket and new Timeout class
|
||||
- socket: implement a functional a reset_input_buffer
|
||||
- rfc2217: improve read timeout implementation
|
||||
- win32: include error message from system in ClearCommError exception
|
||||
- and a few minor changes, docs
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [#183] rfc2217: Fix broken calls to to_bytes on Python3.
|
||||
- [#188] rfc2217: fix auto-open use case when port is given as parameter
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [#178] in read, count length of converted data
|
||||
- [#189] fix return value of write
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [#194] spurious write fails with ERROR_SUCCESS
|
||||
|
||||
|
||||
Version 3.4 2017-07-22
|
||||
------------------------
|
||||
Improvements:
|
||||
|
||||
- miniterm: suspend function (temporarily release port, :kbd:`Ctrl-T s`)
|
||||
- [#240] context manager automatically opens port on ``__enter__``
|
||||
- [#141] list_ports: add interface number to location string
|
||||
- [#225] protocol_socket: Retry if ``BlockingIOError`` occurs in
|
||||
``reset_input_buffer``.
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [#153] list_ports: option to include symlinked devices
|
||||
- [#237] list_ports: workaround for special characters in port names
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- allow calling cancel functions w/o error if port is closed
|
||||
- [#220] protocol_socket: sync error handling with posix version
|
||||
- [#227] posix: ignore more blocking errors and EINTR, timeout only
|
||||
applies to blocking I/O
|
||||
- [#228] fix: port_publisher typo
|
||||
|
||||
|
||||
Version 3.5b0 2020-09-21
|
||||
------------------------
|
||||
New Features:
|
||||
|
||||
- [#411] Add a backend for Silicon Labs CP2110/4 HID-to-UART bridge.
|
||||
(depends on `hid` module)
|
||||
|
||||
Improvements:
|
||||
|
||||
- [#315] Use absolute import everywhere
|
||||
- [#351] win32: miniterm Working CMD.exe terminal using Windows 10 ANSI support
|
||||
- [#354] Make ListPortInfo hashable
|
||||
- [#372] threaded: "write" returns byte count
|
||||
- [#400] Add bytesize and stopbits argument parser to tcp_serial_redirect
|
||||
- [#408] loop: add out_waiting
|
||||
- [#495] list_ports_linux: Correct "interface" property on Linux hosts
|
||||
- [#500] Remove Python 3.2 and 3.3 from test
|
||||
- [#261, #285, #296, #320, #333, #342, #356, #358, #389, #397, #510] doc updates
|
||||
- miniterm: add :kbd:`CTRL+T Q` as alternative to exit
|
||||
- miniterm: suspend function key changed to :kbd:`CTRL-T Z`
|
||||
- add command line tool entries ``pyserial-miniterm`` (replaces ``miniterm.py``)
|
||||
and ``pyserial-ports`` (runs ``serial.tools.list_ports``).
|
||||
- ``python -m serial`` opens miniterm (use w/o args and it will print port
|
||||
list too) [experimental]
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- [#371] Don't open port if self.port is not set while entering context manager
|
||||
- [#437, #502] refactor: raise new instances for PortNotOpenError and SerialTimeoutException
|
||||
- [#261, #263] list_ports: set default `name` attribute
|
||||
- [#286] fix: compare only of the same type in list_ports_common.ListPortInfo
|
||||
- rfc2217/close(): fix race-condition
|
||||
- [#305] return b'' when connection closes on rfc2217 connection
|
||||
- [#386] rfc2217/close(): fix race condition
|
||||
- Fixed flush_input_buffer() for situations where the remote end has closed the socket.
|
||||
- [#441] reset_input_buffer() can hang on sockets
|
||||
- examples: port_publisher python 3 fixes
|
||||
- [#324] miniterm: Fix miniterm constructor exit_character and menu_character
|
||||
- [#326] miniterm: use exclusive access for native serial ports by default
|
||||
- [#497] miniterm: fix double use of CTRL-T + s use z for suspend instead
|
||||
- [#443, #444] examples: refactor wx example, use Bind to avoid deprecated
|
||||
warnings, IsChecked, unichr
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [#265] posix: fix PosixPollSerial with timeout=None and add cancel support
|
||||
- [#290] option for low latency mode on linux
|
||||
- [#335] Add support to xr-usb-serial ports
|
||||
- [#494] posix: Don't catch the SerialException we just raised
|
||||
- [#519] posix: Fix custom baud rate to not temporarily set 38400 baud rates on linux
|
||||
- [#509 #518] list_ports: use hardcoded path to library on osx
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [#481] win32: extend RS485 error messages
|
||||
- [#303] win32: do not check for links in serial.tools.list_ports
|
||||
- [#430] Add WaitCommEvent function to win32
|
||||
- [#314, #433] tools/list_ports_windows: Scan both 'Ports' and 'Modem' device classes
|
||||
- [#414] Serial number support for composite USB devices
|
||||
- Added recursive search for device USB serial number to support composite devices
|
||||
|
||||
Bugfixes (MacOS):
|
||||
|
||||
- [#364] MacOS: rework list_ports to support unicode product descriptors.
|
||||
- [#367] Mac and bsd fix _update_break_state
|
||||
|
||||
|
||||
Version 3.5 2020-11-23
|
||||
----------------------
|
||||
See above (3.5b0) for what's all new in this release
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- spy: ensure bytes in write()
|
||||
|
||||
Bugfixes (posix):
|
||||
|
||||
- [#540] serialposix: Fix inconsistent state after exception in open()
|
||||
|
||||
Bugfixes (win32):
|
||||
|
||||
- [#530] win32: Fix exception for composite serial number search on Windows
|
||||
|
||||
Bugfixes (MacOS):
|
||||
|
||||
- [#542] list_ports_osx: kIOMasterPortDefault no longer exported on Big Sur
|
||||
- [#545, #545] list_ports_osx: getting USB info on BigSur/AppleSilicon
|
||||
@@ -1,39 +0,0 @@
|
||||
Copyright (c) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
All Rights Reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
Note:
|
||||
Individual files contain the following tag instead of the full license text.
|
||||
|
||||
SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
This enables machine processing of license information based on the SPDX
|
||||
License Identifiers that are here available: http://spdx.org/licenses/
|
||||
@@ -1,39 +0,0 @@
|
||||
include README.rst
|
||||
include LICENSE.txt
|
||||
include CHANGES.rst
|
||||
include MANIFEST.in
|
||||
include setup.py
|
||||
include setup.cfg
|
||||
include pylintrc
|
||||
|
||||
include examples/at_protocol.py
|
||||
include examples/port_publisher.py
|
||||
include examples/port_publisher.sh
|
||||
include examples/rfc2217_server.py
|
||||
include examples/setup-miniterm-py2exe.py
|
||||
include examples/setup-rfc2217_server-py2exe.py
|
||||
include examples/setup-wxTerminal-py2exe.py
|
||||
include examples/tcp_serial_redirect.py
|
||||
include examples/wxSerialConfigDialog.py
|
||||
include examples/wxSerialConfigDialog.wxg
|
||||
include examples/wxTerminal.py
|
||||
include examples/wxTerminal.wxg
|
||||
|
||||
include test/handlers/__init__.py
|
||||
include test/handlers/protocol_test.py
|
||||
include test/run_all_tests.py
|
||||
include test/test_advanced.py
|
||||
include test/test_high_load.py
|
||||
include test/test_iolib.py
|
||||
include test/test.py
|
||||
include test/test_readline.py
|
||||
include test/test_rfc2217.py
|
||||
include test/test_rs485.py
|
||||
include test/test_settings_dict.py
|
||||
include test/test_url.py
|
||||
|
||||
include documentation/*.rst
|
||||
include documentation/pyserial.png
|
||||
include documentation/conf.py
|
||||
include documentation/Makefile
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
=================================
|
||||
pySerial |build-status| |docs|
|
||||
=================================
|
||||
|
||||
Overview
|
||||
========
|
||||
This module encapsulates the access for the serial port. It provides backends
|
||||
for Python_ running on Windows, OSX, Linux, BSD (possibly any POSIX compliant
|
||||
system) and IronPython. The module named "serial" automatically selects the
|
||||
appropriate backend.
|
||||
|
||||
- Project Homepage: https://github.com/pyserial/pyserial
|
||||
- Download Page: https://pypi.python.org/pypi/pyserial
|
||||
|
||||
BSD license, (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
|
||||
|
||||
Documentation
|
||||
=============
|
||||
For API documentation, usage and examples see files in the "documentation"
|
||||
directory. The ".rst" files can be read in any text editor or being converted to
|
||||
HTML or PDF using Sphinx_. An HTML version is online at
|
||||
https://pythonhosted.org/pyserial/
|
||||
|
||||
Examples
|
||||
========
|
||||
Examples and unit tests are in the directory examples_.
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
``pip install pyserial`` should work for most users.
|
||||
|
||||
Detailed information can be found in `documentation/pyserial.rst`_.
|
||||
|
||||
The usual setup.py for Python_ libraries is used for the source distribution.
|
||||
Windows installers are also available (see download link above).
|
||||
|
||||
or
|
||||
|
||||
To install this package with conda run:
|
||||
|
||||
``conda install -c conda-forge pyserial``
|
||||
|
||||
conda builds are available for linux, mac and windows.
|
||||
|
||||
.. _`documentation/pyserial.rst`: https://github.com/pyserial/pyserial/blob/master/documentation/pyserial.rst#installation
|
||||
.. _examples: https://github.com/pyserial/pyserial/blob/master/examples
|
||||
.. _Python: http://python.org/
|
||||
.. _Sphinx: http://sphinx-doc.org/
|
||||
.. |build-status| image:: https://travis-ci.org/pyserial/pyserial.svg?branch=master
|
||||
:target: https://travis-ci.org/pyserial/pyserial
|
||||
:alt: Build status
|
||||
.. |docs| image:: https://readthedocs.org/projects/pyserial/badge/?version=latest
|
||||
:target: http://pyserial.readthedocs.io/
|
||||
:alt: Documentation
|
||||
@@ -1,88 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf _build/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in _build/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in _build/dirhtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in _build/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in _build/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator _build/qthelp/pySerial.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile _build/qthelp/pySerial.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in _build/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
|
||||
@echo
|
||||
@echo "The overview file is in _build/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in _build/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in _build/doctest/output.txt."
|
||||
@@ -1,148 +0,0 @@
|
||||
==========
|
||||
Appendix
|
||||
==========
|
||||
|
||||
How To
|
||||
======
|
||||
|
||||
Enable :rfc:`2217` (and other URL handlers) in programs using pySerial.
|
||||
Patch the code where the :class:`serial.Serial` is instantiated.
|
||||
E.g. replace::
|
||||
|
||||
s = serial.Serial(...)
|
||||
|
||||
it with::
|
||||
|
||||
s = serial.serial_for_url(...)
|
||||
|
||||
or for backwards compatibility to old pySerial installations::
|
||||
|
||||
try:
|
||||
s = serial.serial_for_url(...)
|
||||
except AttributeError:
|
||||
s = serial.Serial(...)
|
||||
|
||||
Assuming the application already stores port names as strings that's all
|
||||
that is required. The user just needs a way to change the port setting of
|
||||
your application to an ``rfc2217://`` :ref:`URL <URLs>` (e.g. by editing a
|
||||
configuration file, GUI dialog etc.).
|
||||
|
||||
Please note that this enables all :ref:`URL <URLs>` types supported by
|
||||
pySerial and that those involving the network are unencrypted and not
|
||||
protected against eavesdropping.
|
||||
|
||||
Test your setup.
|
||||
Is the device not working as expected? Maybe it's time to check the
|
||||
connection before proceeding. :ref:`miniterm` from the :ref:`examples`
|
||||
can be used to open the serial port and do some basic tests.
|
||||
|
||||
To test cables, connecting RX to TX (loop back) and typing some characters
|
||||
in :ref:`miniterm` is a simple test. When the characters are displayed
|
||||
on the screen, then at least RX and TX work (they still could be swapped
|
||||
though).
|
||||
|
||||
There is also a ``spy:://`` URL handler. It prints all calls (read/write,
|
||||
control lines) to the serial port to a file or stderr. See :ref:`spy`
|
||||
for details.
|
||||
|
||||
|
||||
FAQ
|
||||
===
|
||||
Example works in :ref:`miniterm` but not in script.
|
||||
The RTS and DTR lines are switched when the port is opened. This may cause
|
||||
some processing or reset on the connected device. In such a cases an
|
||||
immediately following call to :meth:`write` may not be received by the
|
||||
device.
|
||||
|
||||
A delay after opening the port, before the first :meth:`write`, is
|
||||
recommended in this situation. E.g. a ``time.sleep(1)``
|
||||
|
||||
|
||||
Application works when .py file is run, but fails when packaged (py2exe etc.)
|
||||
py2exe and similar packaging programs scan the sources for import
|
||||
statements and create a list of modules that they package. pySerial may
|
||||
create two issues with that:
|
||||
|
||||
- implementations for other modules are found. On Windows, it's safe to
|
||||
exclude 'serialposix', 'serialjava' and 'serialcli' as these are not
|
||||
used.
|
||||
|
||||
- :func:`serial.serial_for_url` does a dynamic lookup of protocol handlers
|
||||
at runtime. If this function is used, the desired handlers have to be
|
||||
included manually (e.g. 'serial.urlhandler.protocol_socket',
|
||||
'serial.urlhandler.protocol_rfc2217', etc.). This can be done either with
|
||||
the "includes" option in ``setup.py`` or by a dummy import in one of the
|
||||
packaged modules.
|
||||
|
||||
User supplied URL handlers
|
||||
:func:`serial.serial_for_url` can be used to access "virtual" serial ports
|
||||
identified by an :ref:`URL <URLs>` scheme. E.g. for the :rfc:`2217`:
|
||||
``rfc2217://``.
|
||||
|
||||
Custom :ref:`URL <URLs>` handlers can be added by extending the module
|
||||
search path in :data:`serial.protocol_handler_packages`. This is possible
|
||||
starting from pySerial V2.6.
|
||||
|
||||
``Permission denied`` errors
|
||||
On POSIX based systems, the user usually needs to be in a special group to
|
||||
have access to serial ports.
|
||||
|
||||
On Debian based systems, serial ports are usually in the group ``dialout``,
|
||||
so running ``sudo adduser $USER dialout`` (and logging-out and -in) enables
|
||||
the user to access the port.
|
||||
|
||||
Parity on Raspberry Pi
|
||||
The Raspi has one full UART and a restricted one. On devices with built
|
||||
in wireless (WIFI/BT) use the restricted one on the GPIO header pins.
|
||||
If enhanced features are required, it is possible to swap UARTs, see
|
||||
https://www.raspberrypi.org/documentation/configuration/uart.md
|
||||
|
||||
Support for Python 2.6 or earlier
|
||||
Support for older Python releases than 2.7 will not return to pySerial 3.x.
|
||||
Python 2.7 is now many years old (released 2010). If you insist on using
|
||||
Python 2.6 or earlier, it is recommend to use pySerial `2.7`_
|
||||
(or any 2.x version).
|
||||
|
||||
.. _`2.7`: https://pypi.python.org/pypi/pyserial/2.7
|
||||
|
||||
|
||||
Related software
|
||||
================
|
||||
|
||||
com0com - http://com0com.sourceforge.net/
|
||||
Provides virtual serial ports for Windows.
|
||||
|
||||
|
||||
License
|
||||
=======
|
||||
Copyright (c) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
All Rights Reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# pySerial documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Jul 21 00:27:45 2009.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.append(os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.intersphinx']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'pySerial'
|
||||
copyright = u'2001-2020, Chris Liechti'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '3.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '3.4'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
#html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
html_logo = 'pyserial.png'
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
#htmlhelp_basename = 'pySerialdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'pySerial.tex', u'pySerial Documentation',
|
||||
u'Chris Liechti', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
latex_logo = 'pyserial.png'
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
|
||||
# for external links to standard library
|
||||
intersphinx_mapping = {
|
||||
#~ 'python': ('http://docs.python.org', None),
|
||||
'py': ('http://docs.python.org', None),
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
.. _examples:
|
||||
|
||||
==========
|
||||
Examples
|
||||
==========
|
||||
|
||||
|
||||
Miniterm
|
||||
========
|
||||
Miniterm is now available as module instead of example.
|
||||
see :ref:`miniterm` for details.
|
||||
|
||||
miniterm.py_
|
||||
The miniterm program.
|
||||
|
||||
setup-miniterm-py2exe.py_
|
||||
This is a py2exe setup script for Windows. It can be used to create a
|
||||
standalone ``miniterm.exe``.
|
||||
|
||||
.. _miniterm.py: https://github.com/pyserial/pyserial/blob/master/serial/tools/miniterm.py
|
||||
.. _setup-miniterm-py2exe.py: https://github.com/pyserial/pyserial/blob/master/examples/setup-miniterm-py2exe.py
|
||||
|
||||
|
||||
TCP/IP - serial bridge
|
||||
======================
|
||||
This program opens a TCP/IP port. When a connection is made to that port (e.g.
|
||||
with telnet) it forwards all data to the serial port and vice versa.
|
||||
|
||||
This example only exports a raw socket connection. The next example
|
||||
below gives the client much more control over the remote serial port.
|
||||
|
||||
- The serial port settings are set on the command line when starting the
|
||||
program.
|
||||
- There is no possibility to change settings from remote.
|
||||
- All data is passed through as-is.
|
||||
|
||||
::
|
||||
|
||||
usage: tcp_serial_redirect.py [-h] [-q] [--parity {N,E,O,S,M}] [--rtscts]
|
||||
[--xonxoff] [--rts RTS] [--dtr DTR]
|
||||
[-P LOCALPORT]
|
||||
SERIALPORT [BAUDRATE]
|
||||
|
||||
Simple Serial to Network (TCP/IP) redirector.
|
||||
|
||||
positional arguments:
|
||||
SERIALPORT serial port name
|
||||
BAUDRATE set baud rate, default: 9600
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-q, --quiet suppress non error messages
|
||||
|
||||
serial port:
|
||||
--parity {N,E,O,S,M} set parity, one of {N E O S M}, default: N
|
||||
--rtscts enable RTS/CTS flow control (default off)
|
||||
--xonxoff enable software flow control (default off)
|
||||
--rts RTS set initial RTS line state (possible values: 0, 1)
|
||||
--dtr DTR set initial DTR line state (possible values: 0, 1)
|
||||
|
||||
network settings:
|
||||
-P LOCALPORT, --localport LOCALPORT
|
||||
local TCP port
|
||||
|
||||
NOTE: no security measures are implemented. Anyone can remotely connect to
|
||||
this service over the network. Only one connection at once is supported. When
|
||||
the connection is terminated it waits for the next connect.
|
||||
|
||||
|
||||
tcp_serial_redirect.py_
|
||||
Main program.
|
||||
|
||||
.. _tcp_serial_redirect.py: https://github.com/pyserial/pyserial/blob/master/examples/tcp_serial_redirect.py
|
||||
|
||||
|
||||
Single-port TCP/IP - serial bridge (RFC 2217)
|
||||
=============================================
|
||||
Simple cross platform :rfc:`2217` serial port server. It uses threads and is
|
||||
portable (runs on POSIX, Windows, etc).
|
||||
|
||||
- The port settings and control lines (RTS/DTR) can be changed at any time
|
||||
using :rfc:`2217` requests. The status lines (DSR/CTS/RI/CD) are polled every
|
||||
second and notifications are sent to the client.
|
||||
- Telnet character IAC (0xff) needs to be doubled in data stream. IAC followed
|
||||
by another value is interpreted as Telnet command sequence.
|
||||
- Telnet negotiation commands are sent when connecting to the server.
|
||||
- RTS/DTR are activated on client connect and deactivated on disconnect.
|
||||
- Default port settings are set again when client disconnects.
|
||||
|
||||
::
|
||||
|
||||
usage: rfc2217_server.py [-h] [-p TCPPORT] [-v] SERIALPORT
|
||||
|
||||
RFC 2217 Serial to Network (TCP/IP) redirector.
|
||||
|
||||
positional arguments:
|
||||
SERIALPORT
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-p TCPPORT, --localport TCPPORT
|
||||
local TCP port, default: 2217
|
||||
-v, --verbose print more diagnostic messages (option can be given
|
||||
multiple times)
|
||||
|
||||
NOTE: no security measures are implemented. Anyone can remotely connect to
|
||||
this service over the network. Only one connection at once is supported. When
|
||||
the connection is terminated it waits for the next connect.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
|
||||
rfc2217_server.py_
|
||||
Main program.
|
||||
|
||||
setup-rfc2217_server-py2exe.py_
|
||||
This is a py2exe setup script for Windows. It can be used to create a
|
||||
standalone ``rfc2217_server.exe``.
|
||||
|
||||
.. _rfc2217_server.py: https://github.com/pyserial/pyserial/blob/master/examples/rfc2217_server.py
|
||||
.. _setup-rfc2217_server-py2exe.py: https://github.com/pyserial/pyserial/blob/master/examples/setup-rfc2217_server-py2exe.py
|
||||
|
||||
|
||||
Multi-port TCP/IP - serial bridge (RFC 2217)
|
||||
============================================
|
||||
This example implements a TCP/IP to serial port service that works with
|
||||
multiple ports at once. It uses select, no threads, for the serial ports and
|
||||
the network sockets and therefore runs on POSIX systems only.
|
||||
|
||||
- Full control over the serial port with :rfc:`2217`.
|
||||
- Check existence of ``/tty/USB0...8``. This is done every 5 seconds using
|
||||
``os.path.exists``.
|
||||
- Send zeroconf announcements when port appears or disappears (uses
|
||||
python-avahi and dbus). Service name: ``_serial_port._tcp``.
|
||||
- Each serial port becomes available as one TCP/IP server. e.g.
|
||||
``/dev/ttyUSB0`` is reachable at ``<host>:7000``.
|
||||
- Single process for all ports and sockets (not per port).
|
||||
- The script can be started as daemon.
|
||||
- Logging to stdout or when run as daemon to syslog.
|
||||
- Default port settings are set again when client disconnects.
|
||||
- modem status lines (CTS/DSR/RI/CD) are not polled periodically and the server
|
||||
therefore does not send NOTIFY_MODEMSTATE on its own. However it responds to
|
||||
request from the client (i.e. use the ``poll_modem`` option in the URL when
|
||||
using a pySerial client.)
|
||||
|
||||
::
|
||||
|
||||
usage: port_publisher.py [options]
|
||||
|
||||
Announce the existence of devices using zeroconf and provide
|
||||
a TCP/IP <-> serial port gateway (implements RFC 2217).
|
||||
|
||||
If running as daemon, write to syslog. Otherwise write to stdout.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
serial port settings:
|
||||
--ports-regex REGEX specify a regex to search against the serial devices
|
||||
and their descriptions (default: /dev/ttyUSB[0-9]+)
|
||||
|
||||
network settings:
|
||||
--tcp-port PORT specify lowest TCP port number (default: 7000)
|
||||
|
||||
daemon:
|
||||
-d, --daemon start as daemon
|
||||
--pidfile FILE specify a name for the PID file
|
||||
|
||||
diagnostics:
|
||||
-o FILE, --logfile FILE
|
||||
write messages file instead of stdout
|
||||
-q, --quiet suppress most diagnostic messages
|
||||
-v, --verbose increase diagnostic messages
|
||||
|
||||
NOTE: no security measures are implemented. Anyone can remotely connect to
|
||||
this service over the network. Only one connection at once, per port, is
|
||||
supported. When the connection is terminated, it waits for the next connect.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Python (>= 2.4)
|
||||
- python-avahi
|
||||
- python-dbus
|
||||
- python-serial (>= 2.5)
|
||||
|
||||
Installation as daemon:
|
||||
|
||||
- Copy the script ``port_publisher.py`` to ``/usr/local/bin``.
|
||||
- Copy the script ``port_publisher.sh`` to ``/etc/init.d``.
|
||||
- Add links to the runlevels using ``update-rc.d port_publisher.sh defaults 99``
|
||||
- That's it :-) the service will be started on next reboot. Alternatively run
|
||||
``invoke-rc.d port_publisher.sh start`` as root.
|
||||
|
||||
.. versionadded:: 2.5 new example
|
||||
|
||||
port_publisher.py_
|
||||
Multi-port TCP/IP-serial converter (RFC 2217) for POSIX environments.
|
||||
|
||||
port_publisher.sh_
|
||||
Example init.d script.
|
||||
|
||||
.. _port_publisher.py: https://github.com/pyserial/pyserial/blob/master/examples/port_publisher.py
|
||||
.. _port_publisher.sh: https://github.com/pyserial/pyserial/blob/master/examples/port_publisher.sh
|
||||
|
||||
|
||||
wxPython examples
|
||||
=================
|
||||
A simple terminal application for wxPython and a flexible serial port
|
||||
configuration dialog are shown here.
|
||||
|
||||
wxTerminal.py_
|
||||
A simple terminal application. Note that the length of the buffer is
|
||||
limited by wx and it may suddenly stop displaying new input.
|
||||
|
||||
wxTerminal.wxg_
|
||||
A wxGlade design file for the terminal application.
|
||||
|
||||
wxSerialConfigDialog.py_
|
||||
A flexible serial port configuration dialog.
|
||||
|
||||
wxSerialConfigDialog.wxg_
|
||||
The wxGlade design file for the configuration dialog.
|
||||
|
||||
setup-wxTerminal-py2exe.py_
|
||||
A py2exe setup script to package the terminal application.
|
||||
|
||||
.. _wxTerminal.py: https://github.com/pyserial/pyserial/blob/master/examples/wxTerminal.py
|
||||
.. _wxTerminal.wxg: https://github.com/pyserial/pyserial/blob/master/examples/wxTerminal.wxg
|
||||
.. _wxSerialConfigDialog.py: https://github.com/pyserial/pyserial/blob/master/examples/wxSerialConfigDialog.py
|
||||
.. _wxSerialConfigDialog.wxg: https://github.com/pyserial/pyserial/blob/master/examples/wxSerialConfigDialog.wxg
|
||||
.. _setup-wxTerminal-py2exe.py: https://github.com/pyserial/pyserial/blob/master/examples/setup-wxTerminal-py2exe.py
|
||||
|
||||
|
||||
|
||||
Unit tests
|
||||
==========
|
||||
The project uses a number of unit test to verify the functionality. They all
|
||||
need a loop back connector. The scripts itself contain more information. All
|
||||
test scripts are contained in the directory ``test``.
|
||||
|
||||
The unit tests are performed on port ``loop://`` unless a different device
|
||||
name or URL is given on the command line (``sys.argv[1]``). e.g. to run the
|
||||
test on an attached USB-serial converter ``hwgrep://USB`` could be used or
|
||||
the actual name such as ``/dev/ttyUSB0`` or ``COM1`` (depending on platform).
|
||||
|
||||
run_all_tests.py_
|
||||
Collect all tests from all ``test*`` files and run them. By default, the
|
||||
``loop://`` device is used.
|
||||
|
||||
test.py_
|
||||
Basic tests (binary capabilities, timeout, control lines).
|
||||
|
||||
test_advanced.py_
|
||||
Test more advanced features (properties).
|
||||
|
||||
test_high_load.py_
|
||||
Tests involving sending a lot of data.
|
||||
|
||||
test_readline.py_
|
||||
Tests involving ``readline``.
|
||||
|
||||
test_iolib.py_
|
||||
Tests involving the :mod:`io` library. Only available for Python 2.6 and
|
||||
newer.
|
||||
|
||||
test_url.py_
|
||||
Tests involving the :ref:`URL <URLs>` feature.
|
||||
|
||||
.. _run_all_tests.py: https://github.com/pyserial/pyserial/blob/master/test/run_all_tests.py
|
||||
.. _test.py: https://github.com/pyserial/pyserial/blob/master/test/test.py
|
||||
.. _test_advanced.py: https://github.com/pyserial/pyserial/blob/master/test/test_advanced.py
|
||||
.. _test_high_load.py: https://github.com/pyserial/pyserial/blob/master/test/test_high_load.py
|
||||
.. _test_readline.py: https://github.com/pyserial/pyserial/blob/master/test/test_readline.py
|
||||
.. _test_iolib.py: https://github.com/pyserial/pyserial/blob/master/test/test_iolib.py
|
||||
.. _test_url.py: https://github.com/pyserial/pyserial/blob/master/test/test_url.py
|
||||
@@ -1,44 +0,0 @@
|
||||
.. pySerial documentation master file
|
||||
.. _welcome:
|
||||
|
||||
Welcome to pySerial's documentation
|
||||
===================================
|
||||
|
||||
|
||||
This module encapsulates the access for the serial port. It provides backends
|
||||
for Python_ running on Windows, OSX, Linux, BSD (possibly any POSIX compliant
|
||||
system) and IronPython. The module named "serial" automatically selects the
|
||||
appropriate backend.
|
||||
|
||||
Other pages (online)
|
||||
|
||||
- `project page on GitHub`_
|
||||
- `Download Page`_ with releases
|
||||
- This page, when viewed online is at https://pyserial.readthedocs.io/en/latest/ or
|
||||
http://pythonhosted.org/pyserial/ .
|
||||
|
||||
.. _Python: http://python.org/
|
||||
.. _`project page on GitHub`: https://github.com/pyserial/
|
||||
.. _`Download Page`: http://pypi.python.org/pypi/pyserial
|
||||
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
pyserial
|
||||
shortintro
|
||||
pyserial_api
|
||||
tools
|
||||
url_handlers
|
||||
examples
|
||||
appendix
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.9 KiB |
@@ -1,145 +0,0 @@
|
||||
==========
|
||||
pySerial
|
||||
==========
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
This module encapsulates the access for the serial port. It provides backends
|
||||
for Python_ running on Windows, OSX, Linux, BSD (possibly any POSIX compliant
|
||||
system) and IronPython. The module named "serial" automatically selects the
|
||||
appropriate backend.
|
||||
|
||||
It is released under a free software license, see LICENSE_ for more
|
||||
details.
|
||||
|
||||
Copyright (C) 2001-2020 Chris Liechti <cliechti(at)gmx.net>
|
||||
|
||||
Other pages (online)
|
||||
|
||||
- `project page on GitHub`_
|
||||
- `Download Page`_ with releases (PyPi)
|
||||
- This page, when viewed online is at https://pyserial.readthedocs.io/en/latest/ or
|
||||
http://pythonhosted.org/pyserial/ .
|
||||
|
||||
.. _Python: http://python.org/
|
||||
.. _LICENSE: appendix.html#license
|
||||
.. _`project page on GitHub`: https://github.com/pyserial/pyserial/
|
||||
.. _`Download Page`: http://pypi.python.org/pypi/pyserial
|
||||
|
||||
|
||||
Features
|
||||
========
|
||||
- Same class based interface on all supported platforms.
|
||||
- Access to the port settings through Python properties.
|
||||
- Support for different byte sizes, stop bits, parity and flow control with
|
||||
RTS/CTS and/or Xon/Xoff.
|
||||
- Working with or without receive timeout.
|
||||
- File like API with "read" and "write" ("readline" etc. also supported).
|
||||
- The files in this package are 100% pure Python.
|
||||
- The port is set up for binary transmission. No NULL byte stripping, CR-LF
|
||||
translation etc. (which are many times enabled for POSIX.) This makes this
|
||||
module universally useful.
|
||||
- Compatible with :mod:`io` library
|
||||
- RFC 2217 client (experimental), server provided in the examples.
|
||||
|
||||
|
||||
Requirements
|
||||
============
|
||||
- Python 2.7 or Python 3.4 and newer
|
||||
|
||||
- If running on Windows: Windows 7 or newer
|
||||
|
||||
- If running on Jython: "Java Communications" (JavaComm) or compatible
|
||||
extension for Java
|
||||
|
||||
For older installations (older Python versions or older operating systems), see
|
||||
`older versions`_ below.
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
This installs a package that can be used from Python (``import serial``).
|
||||
|
||||
To install for all users on the system, administrator rights (root)
|
||||
may be required.
|
||||
|
||||
From PyPI
|
||||
---------
|
||||
pySerial can be installed from PyPI::
|
||||
|
||||
python -m pip install pyserial
|
||||
|
||||
Using the `python`/`python3` executable of the desired version (2.7/3.x).
|
||||
|
||||
Developers also may be interested to get the source archive, because it
|
||||
contains examples, tests and the this documentation.
|
||||
|
||||
From Conda
|
||||
----------
|
||||
pySerial can be installed from Conda::
|
||||
|
||||
conda install pyserial
|
||||
|
||||
or
|
||||
|
||||
conda install -c conda-forge pyserial
|
||||
|
||||
Currently the default conda channel will provide version 3.4 whereas the
|
||||
conda-forge channel provides the current 3.x version.
|
||||
|
||||
Conda: https://www.continuum.io/downloads
|
||||
|
||||
From source (zip/tar.gz or checkout)
|
||||
------------------------------------
|
||||
Download the archive from http://pypi.python.org/pypi/pyserial or
|
||||
https://github.com/pyserial/pyserial/releases.
|
||||
Unpack the archive, enter the ``pyserial-x.y`` directory and run::
|
||||
|
||||
python setup.py install
|
||||
|
||||
Using the `python`/`python3` executable of the desired version (2.7/3.x).
|
||||
|
||||
Packages
|
||||
--------
|
||||
There are also packaged versions for some Linux distributions:
|
||||
|
||||
- Debian/Ubuntu: "python-serial", "python3-serial"
|
||||
- Fedora / RHEL / CentOS / EPEL: "pyserial"
|
||||
- Arch Linux: "python-pyserial"
|
||||
- Gentoo: "dev-python/pyserial"
|
||||
|
||||
Note that some distributions may package an older version of pySerial.
|
||||
These packages are created and maintained by developers working on
|
||||
these distributions.
|
||||
|
||||
.. _PyPi: http://pypi.python.org/pypi/pyserial
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
* Python: http://www.python.org/
|
||||
* Jython: http://www.jython.org/
|
||||
* IronPython: http://www.codeplex.com/IronPython
|
||||
|
||||
|
||||
Older Versions
|
||||
==============
|
||||
Older versions are still available on the current download_ page or the `old
|
||||
download`_ page. The last version of pySerial's 2.x series was `2.7`_,
|
||||
compatible with Python 2.3 and newer and partially with early Python 3.x
|
||||
versions.
|
||||
|
||||
pySerial `1.21`_ is compatible with Python 2.0 on Windows, Linux and several
|
||||
un*x like systems, MacOSX and Jython.
|
||||
|
||||
On Windows, releases older than 2.5 will depend on pywin32_ (previously known as
|
||||
win32all). WinXP is supported up to 3.0.1.
|
||||
|
||||
|
||||
.. _`old download`: https://sourceforge.net/projects/pyserial/files/pyserial/
|
||||
.. _download: https://pypi.python.org/simple/pyserial/
|
||||
.. _pywin32: http://pypi.python.org/pypi/pywin32
|
||||
.. _`2.7`: https://pypi.python.org/pypi/pyserial/2.7
|
||||
.. _`1.21`: https://sourceforge.net/projects/pyserial/files/pyserial/1.21/pyserial-1.21.zip/download
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,123 +0,0 @@
|
||||
====================
|
||||
Short introduction
|
||||
====================
|
||||
|
||||
Opening serial ports
|
||||
====================
|
||||
|
||||
Open port at "9600,8,N,1", no timeout::
|
||||
|
||||
>>> import serial
|
||||
>>> ser = serial.Serial('/dev/ttyUSB0') # open serial port
|
||||
>>> print(ser.name) # check which port was really used
|
||||
>>> ser.write(b'hello') # write a string
|
||||
>>> ser.close() # close port
|
||||
|
||||
Open named port at "19200,8,N,1", 1s timeout::
|
||||
|
||||
>>> with serial.Serial('/dev/ttyS1', 19200, timeout=1) as ser:
|
||||
... x = ser.read() # read one byte
|
||||
... s = ser.read(10) # read up to ten bytes (timeout)
|
||||
... line = ser.readline() # read a '\n' terminated line
|
||||
|
||||
Open port at "38400,8,E,1", non blocking HW handshaking::
|
||||
|
||||
>>> ser = serial.Serial('COM3', 38400, timeout=0,
|
||||
... parity=serial.PARITY_EVEN, rtscts=1)
|
||||
>>> s = ser.read(100) # read up to one hundred bytes
|
||||
... # or as much is in the buffer
|
||||
|
||||
Configuring ports later
|
||||
=======================
|
||||
|
||||
Get a Serial instance and configure/open it later::
|
||||
|
||||
>>> ser = serial.Serial()
|
||||
>>> ser.baudrate = 19200
|
||||
>>> ser.port = 'COM1'
|
||||
>>> ser
|
||||
Serial<id=0xa81c10, open=False>(port='COM1', baudrate=19200, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=0, rtscts=0)
|
||||
>>> ser.open()
|
||||
>>> ser.is_open
|
||||
True
|
||||
>>> ser.close()
|
||||
>>> ser.is_open
|
||||
False
|
||||
|
||||
Also supported with :ref:`context manager <context-manager>`::
|
||||
|
||||
with serial.Serial() as ser:
|
||||
ser.baudrate = 19200
|
||||
ser.port = 'COM1'
|
||||
ser.open()
|
||||
ser.write(b'hello')
|
||||
|
||||
|
||||
.. _shortintro_readline:
|
||||
|
||||
Readline
|
||||
========
|
||||
:meth:`readline` reads up to one line, including the ``\n`` at the end.
|
||||
Be careful when using :meth:`readline`. Do specify a timeout when opening the
|
||||
serial port otherwise it could block forever if no newline character is
|
||||
received. If the ``\n`` is missing in the return value, it returned on timeout.
|
||||
|
||||
:meth:`readlines` tries to read "all" lines which is not well defined for a
|
||||
serial port that is still open. Therefore :meth:`readlines` depends on having
|
||||
a timeout on the port and interprets that as EOF (end of file). It raises an
|
||||
exception if the port is not opened correctly. The returned list of lines do
|
||||
not include the ``\n``.
|
||||
|
||||
Both functions call :meth:`read` to get their data and the serial port timeout
|
||||
is acting on this function. Therefore the effective timeout, especially for
|
||||
:meth:`readlines`, can be much larger.
|
||||
|
||||
Do also have a look at the example files in the examples directory in the
|
||||
source distribution or online.
|
||||
|
||||
.. note::
|
||||
|
||||
The ``eol`` parameter for :meth:`readline` is no longer supported when
|
||||
pySerial is run with newer Python versions (V2.6+) where the module
|
||||
:mod:`io` is available.
|
||||
|
||||
EOL
|
||||
---
|
||||
To specify the EOL character for :meth:`readline` or to use universal newline
|
||||
mode, it is advised to use io.TextIOWrapper_::
|
||||
|
||||
import serial
|
||||
import io
|
||||
ser = serial.serial_for_url('loop://', timeout=1)
|
||||
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))
|
||||
|
||||
sio.write(unicode("hello\n"))
|
||||
sio.flush() # it is buffering. required to get the data out *now*
|
||||
hello = sio.readline()
|
||||
print(hello == unicode("hello\n"))
|
||||
|
||||
|
||||
.. _io.TextIOWrapper: http://docs.python.org/library/io.html#io.TextIOWrapper
|
||||
|
||||
|
||||
Testing ports
|
||||
=============
|
||||
Listing ports
|
||||
-------------
|
||||
``python -m serial.tools.list_ports`` will print a list of available ports. It
|
||||
is also possible to add a regexp as first argument and the list will only
|
||||
include entries that matched.
|
||||
|
||||
.. note::
|
||||
|
||||
The enumeration may not work on all operating systems. It may be
|
||||
incomplete, list unavailable ports or may lack detailed descriptions of the
|
||||
ports.
|
||||
|
||||
.. versionadded: 2.6
|
||||
|
||||
Accessing ports
|
||||
---------------
|
||||
pySerial includes a small console based terminal program called
|
||||
:ref:`miniterm`. It can be started with ``python -m serial.tools.miniterm <port_name>``
|
||||
(use option ``-h`` to get a listing of all options).
|
||||
@@ -1,291 +0,0 @@
|
||||
=======
|
||||
Tools
|
||||
=======
|
||||
|
||||
.. module:: serial
|
||||
|
||||
serial.tools.list_ports
|
||||
=======================
|
||||
.. module:: serial.tools.list_ports
|
||||
|
||||
This module can be executed to get a list of ports (``python -m
|
||||
serial.tools.list_ports``). It also contains the following functions.
|
||||
|
||||
|
||||
.. function:: comports(include_links=False)
|
||||
|
||||
:param bool include_links: include symlinks under ``/dev`` when they point
|
||||
to a serial port
|
||||
:return: a list containing :class:`ListPortInfo` objects.
|
||||
|
||||
The function returns a list of :obj:`ListPortInfo` objects.
|
||||
|
||||
Items are returned in no particular order. It may make sense to sort the
|
||||
items. Also note that the reported strings are different across platforms
|
||||
and operating systems, even for the same device.
|
||||
|
||||
.. note:: Support is limited to a number of operating systems. On some
|
||||
systems description and hardware ID will not be available
|
||||
(``None``).
|
||||
|
||||
Under Linux, OSX and Windows, extended information will be available for
|
||||
USB devices (e.g. the :attr:`ListPortInfo.hwid` string contains `VID:PID`,
|
||||
`SER` (serial number), `LOCATION` (hierarchy), which makes them searchable
|
||||
via :func:`grep`. The USB info is also available as attributes of
|
||||
:attr:`ListPortInfo`.
|
||||
|
||||
If *include_links* is true, all devices under ``/dev`` are inspected and
|
||||
tested if they are a link to a known serial port device. These entries
|
||||
will include ``LINK`` in their ``hwid`` string. This implies that the same
|
||||
device listed twice, once under its original name and once under linked
|
||||
name.
|
||||
|
||||
:platform: Posix (/dev files)
|
||||
:platform: Linux (/dev files, sysfs)
|
||||
:platform: OSX (iokit)
|
||||
:platform: Windows (setupapi, registry)
|
||||
|
||||
|
||||
.. function:: grep(regexp, include_links=False)
|
||||
|
||||
:param regexp: regular expression (see stdlib :mod:`re`)
|
||||
:param bool include_links: include symlinks under ``/dev`` when they point
|
||||
to a serial port
|
||||
:return: an iterable that yields :class:`ListPortInfo` objects, see also
|
||||
:func:`comports`.
|
||||
|
||||
Search for ports using a regular expression. Port ``name``,
|
||||
``description`` and ``hwid`` are searched (case insensitive). The function
|
||||
returns an iterable that contains the same data that :func:`comports`
|
||||
generates, but includes only those entries that match the regexp.
|
||||
|
||||
|
||||
.. class:: ListPortInfo
|
||||
|
||||
This object holds information about a serial port. It supports indexed
|
||||
access for backwards compatibility, as in ``port, desc, hwid = info``.
|
||||
|
||||
.. attribute:: device
|
||||
|
||||
Full device name/path, e.g. ``/dev/ttyUSB0``. This is also the
|
||||
information returned as first element when accessed by index.
|
||||
|
||||
.. attribute:: name
|
||||
|
||||
Short device name, e.g. ``ttyUSB0``.
|
||||
|
||||
.. attribute:: description
|
||||
|
||||
Human readable description or ``n/a``. This is also the information
|
||||
returned as second element when accessed by index.
|
||||
|
||||
.. attribute:: hwid
|
||||
|
||||
Technical description or ``n/a``. This is also the information
|
||||
returned as third element when accessed by index.
|
||||
|
||||
USB specific data, these are all ``None`` if it is not an USB device (or the
|
||||
platform does not support extended info).
|
||||
|
||||
.. attribute:: vid
|
||||
|
||||
USB Vendor ID (integer, 0...65535).
|
||||
|
||||
.. attribute:: pid
|
||||
|
||||
USB product ID (integer, 0...65535).
|
||||
|
||||
.. attribute:: serial_number
|
||||
|
||||
USB serial number as a string.
|
||||
|
||||
.. attribute:: location
|
||||
|
||||
USB device location string ("<bus>-<port>[-<port>]...")
|
||||
|
||||
.. attribute:: manufacturer
|
||||
|
||||
USB manufacturer string, as reported by device.
|
||||
|
||||
.. attribute:: product
|
||||
|
||||
USB product string, as reported by device.
|
||||
|
||||
.. attribute:: interface
|
||||
|
||||
Interface specific description, e.g. used in compound USB devices.
|
||||
|
||||
Comparison operators are implemented such that the :obj:`ListPortInfo` objects
|
||||
can be sorted by ``device``. Strings are split into groups of numbers and
|
||||
text so that the order is "natural" (i.e. ``com1`` < ``com2`` <
|
||||
``com10``).
|
||||
|
||||
|
||||
**Command line usage**
|
||||
|
||||
Help for ``python -m serial.tools.list_ports``::
|
||||
|
||||
usage: list_ports.py [-h] [-v] [-q] [-n N] [-s] [regexp]
|
||||
|
||||
Serial port enumeration
|
||||
|
||||
positional arguments:
|
||||
regexp only show ports that match this regex
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-v, --verbose show more messages
|
||||
-q, --quiet suppress all messages
|
||||
-n N only output the N-th entry
|
||||
-s, --include-links include entries that are symlinks to real devices
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
- List all ports with details::
|
||||
|
||||
$ python -m serial.tools.list_ports -v
|
||||
/dev/ttyS0
|
||||
desc: ttyS0
|
||||
hwid: PNP0501
|
||||
/dev/ttyUSB0
|
||||
desc: CP2102 USB to UART Bridge Controller
|
||||
hwid: USB VID:PID=10C4:EA60 SER=0001 LOCATION=2-1.6
|
||||
2 ports found
|
||||
|
||||
|
||||
- List the 2nd port matching a USB VID:PID pattern::
|
||||
|
||||
$ python -m serial.tools.list_ports 1234:5678 -q -n 2
|
||||
/dev/ttyUSB1
|
||||
|
||||
.. versionadded:: 2.6
|
||||
.. versionchanged:: 3.0 returning ``ListPortInfo`` objects instead of a tuple
|
||||
|
||||
|
||||
.. _miniterm:
|
||||
|
||||
serial.tools.miniterm
|
||||
=====================
|
||||
.. module:: serial.tools.miniterm
|
||||
|
||||
This is a console application that provides a small terminal application.
|
||||
|
||||
Miniterm itself does not implement any terminal features such as VT102
|
||||
compatibility. However it may inherit these features from the terminal it is run.
|
||||
For example on GNU/Linux running from an xterm it will support the escape
|
||||
sequences of the xterm. On Windows the typical console window is dumb and does
|
||||
not support any escapes. When ANSI.sys is loaded it supports some escapes.
|
||||
|
||||
The default is to filter terminal control characters, see ``--filter`` for
|
||||
different options.
|
||||
|
||||
Miniterm::
|
||||
|
||||
--- Miniterm on /dev/ttyS0: 9600,8,N,1 ---
|
||||
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
|
||||
|
||||
Command line options can be given so that binary data including escapes for
|
||||
terminals are escaped or output as hex.
|
||||
|
||||
Miniterm supports :rfc:`2217` remote serial ports and raw sockets using :ref:`URLs`
|
||||
such as ``rfc2217://<host>:<port>`` respectively ``socket://<host>:<port>`` as
|
||||
*port* argument when invoking.
|
||||
|
||||
Command line options ``python -m serial.tools.miniterm -h``::
|
||||
|
||||
usage: miniterm.py [-h] [--parity {N,E,O,S,M}] [--rtscts] [--xonxoff]
|
||||
[--rts RTS] [--dtr DTR] [-e] [--encoding CODEC] [-f NAME]
|
||||
[--eol {CR,LF,CRLF}] [--raw] [--exit-char NUM]
|
||||
[--menu-char NUM] [-q] [--develop]
|
||||
[port] [baudrate]
|
||||
|
||||
Miniterm - A simple terminal program for the serial port.
|
||||
|
||||
positional arguments:
|
||||
port serial port name
|
||||
baudrate set baud rate, default: 9600
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
port settings:
|
||||
--parity {N,E,O,S,M} set parity, one of {N E O S M}, default: N
|
||||
--rtscts enable RTS/CTS flow control (default off)
|
||||
--xonxoff enable software flow control (default off)
|
||||
--rts RTS set initial RTS line state (possible values: 0, 1)
|
||||
--dtr DTR set initial DTR line state (possible values: 0, 1)
|
||||
--ask ask again for port when open fails
|
||||
|
||||
data handling:
|
||||
-e, --echo enable local echo (default off)
|
||||
--encoding CODEC set the encoding for the serial port (e.g. hexlify,
|
||||
Latin1, UTF-8), default: UTF-8
|
||||
-f NAME, --filter NAME
|
||||
add text transformation
|
||||
--eol {CR,LF,CRLF} end of line mode
|
||||
--raw Do no apply any encodings/transformations
|
||||
|
||||
hotkeys:
|
||||
--exit-char NUM Unicode of special character that is used to exit the
|
||||
application, default: 29
|
||||
--menu-char NUM Unicode code of special character that is used to
|
||||
control miniterm (menu), default: 20
|
||||
|
||||
diagnostics:
|
||||
-q, --quiet suppress non-error messages
|
||||
--develop show Python traceback on error
|
||||
|
||||
|
||||
Available filters (``--filter`` option):
|
||||
|
||||
- ``colorize``: Apply different colors for received and echo
|
||||
- ``debug``: Print what is sent and received
|
||||
- ``default``: remove typical terminal control codes from input
|
||||
- ``direct``: do-nothing: forward all data unchanged
|
||||
- ``nocontrol``: Remove all control codes, incl. ``CR+LF``
|
||||
- ``printable``: Show decimal code for all non-ASCII characters and replace most control codes
|
||||
|
||||
|
||||
Miniterm supports some control functions while being connected.
|
||||
Typing :kbd:`Ctrl+T Ctrl+H` when it is running shows the help text::
|
||||
|
||||
--- pySerial (3.0a) - miniterm - help
|
||||
---
|
||||
--- Ctrl+] Exit program
|
||||
--- Ctrl+T Menu escape key, followed by:
|
||||
--- Menu keys:
|
||||
--- Ctrl+T Send the menu character itself to remote
|
||||
--- Ctrl+] Send the exit character itself to remote
|
||||
--- Ctrl+I Show info
|
||||
--- Ctrl+U Upload file (prompt will be shown)
|
||||
--- Ctrl+A encoding
|
||||
--- Ctrl+F edit filters
|
||||
--- Toggles:
|
||||
--- Ctrl+R RTS Ctrl+D DTR Ctrl+B BREAK
|
||||
--- Ctrl+E echo Ctrl+L EOL
|
||||
---
|
||||
--- Port settings (Ctrl+T followed by the following):
|
||||
--- p change port
|
||||
--- 7 8 set data bits
|
||||
--- N E O S M change parity (None, Even, Odd, Space, Mark)
|
||||
--- 1 2 3 set stop bits (1, 2, 1.5)
|
||||
--- b change baud rate
|
||||
--- x X disable/enable software flow control
|
||||
--- r R disable/enable hardware flow control
|
||||
|
||||
:kbd:`Ctrl+T z` suspends the connection (port is opened) and reconnects when a
|
||||
key is pressed. This can be used to temporarily access the serial port with an
|
||||
other application, without exiting miniterm. If reconnecting fails it is
|
||||
also possible to exit (:kbd:`Ctrl+]`) or change the port (:kbd:`p`).
|
||||
|
||||
.. versionchanged:: 2.5
|
||||
Added :kbd:`Ctrl+T` menu and added support for opening URLs.
|
||||
.. versionchanged:: 2.6
|
||||
File moved from the examples to :mod:`serial.tools.miniterm`.
|
||||
.. versionchanged:: 3.0
|
||||
Apply encoding on serial port, convert to Unicode for console.
|
||||
Added new filters, default to stripping terminal control sequences.
|
||||
Added ``--ask`` option.
|
||||
.. versionchanged:: 3.5
|
||||
Enable escape code handling on Windows 10 console.
|
||||
@@ -1,274 +0,0 @@
|
||||
.. _URLs:
|
||||
|
||||
==============
|
||||
URL Handlers
|
||||
==============
|
||||
|
||||
.. module:: serial
|
||||
|
||||
Overview
|
||||
========
|
||||
The function :func:`serial_for_url` accepts the following types of URLs:
|
||||
|
||||
- ``rfc2217://<host>:<port>[?<option>[&<option>...]]``
|
||||
- ``socket://<host>:<port>[?logging={debug|info|warning|error}]``
|
||||
- ``loop://[?logging={debug|info|warning|error}]``
|
||||
- ``hwgrep://<regexp>[&skip_busy][&n=N]``
|
||||
- ``spy://port[?option[=value][&option[=value]]]``
|
||||
- ``alt://port?class=<classname>``
|
||||
- ``cp2110://<bus>:<dev>:<if>``
|
||||
|
||||
.. versionchanged:: 3.0 Options are specified with ``?`` and ``&`` instead of ``/``
|
||||
|
||||
Device names are also supported, e.g.:
|
||||
|
||||
- ``/dev/ttyUSB0`` (Linux)
|
||||
- ``COM3`` (Windows)
|
||||
|
||||
Future releases of pySerial might add more types. Since pySerial 2.6 it is also
|
||||
possible for the user to add protocol handlers using
|
||||
:attr:`protocol_handler_packages`.
|
||||
|
||||
|
||||
``rfc2217://``
|
||||
==============
|
||||
Used to connect to :rfc:`2217` compatible servers. All serial port
|
||||
functions are supported. Implemented by :class:`rfc2217.Serial`.
|
||||
|
||||
Supported options in the URL are:
|
||||
|
||||
- ``ign_set_control`` does not wait for acknowledges to SET_CONTROL. This
|
||||
option can be used for non compliant servers (i.e. when getting an
|
||||
``remote rejected value for option 'control'`` error when connecting).
|
||||
|
||||
- ``poll_modem``: The client issues NOTIFY_MODEMSTATE requests when status
|
||||
lines are read (CTS/DTR/RI/CD). Without this option it relies on the server
|
||||
sending the notifications automatically (that's what the RFC suggests and
|
||||
most servers do). Enable this option when :attr:`cts` does not work as
|
||||
expected, i.e. for servers that do not send notifications.
|
||||
|
||||
- ``timeout=<value>``: Change network timeout (default 3 seconds). This is
|
||||
useful when the server takes a little more time to send its answers. The
|
||||
timeout applies to the initial Telnet / :rfc:`2217` negotiation as well
|
||||
as changing port settings or control line change commands.
|
||||
|
||||
- ``logging={debug|info|warning|error}``: Prints diagnostic messages (not
|
||||
useful for end users). It uses the logging module and a logger called
|
||||
``pySerial.rfc2217`` so that the application can setup up logging
|
||||
handlers etc. It will call :meth:`logging.basicConfig` which initializes
|
||||
for output on ``sys.stderr`` (if no logging was set up already).
|
||||
|
||||
.. warning:: The connection is not encrypted and no authentication is
|
||||
supported! Only use it in trusted environments.
|
||||
|
||||
|
||||
``socket://``
|
||||
=============
|
||||
The purpose of this connection type is that applications using pySerial can
|
||||
connect to TCP/IP to serial port converters that do not support :rfc:`2217`.
|
||||
|
||||
Uses a TCP/IP socket. All serial port settings, control and status lines
|
||||
are ignored. Only data is transmitted and received.
|
||||
|
||||
Supported options in the URL are:
|
||||
|
||||
- ``logging={debug|info|warning|error}``: Prints diagnostic messages (not
|
||||
useful for end users). It uses the logging module and a logger called
|
||||
``pySerial.socket`` so that the application can setup up logging handlers
|
||||
etc. It will call :meth:`logging.basicConfig` which initializes for
|
||||
output on ``sys.stderr`` (if no logging was set up already).
|
||||
|
||||
.. warning:: The connection is not encrypted and no authentication is
|
||||
supported! Only use it in trusted environments.
|
||||
|
||||
|
||||
``loop://``
|
||||
===========
|
||||
The least useful type. It simulates a loop back connection
|
||||
(``RX<->TX`` ``RTS<->CTS`` ``DTR<->DSR``). It could be used to test
|
||||
applications or run the unit tests.
|
||||
|
||||
Supported options in the URL are:
|
||||
|
||||
- ``logging={debug|info|warning|error}``: Prints diagnostic messages (not
|
||||
useful for end users). It uses the logging module and a logger called
|
||||
``pySerial.loop`` so that the application can setup up logging handlers
|
||||
etc. It will call :meth:`logging.basicConfig` which initializes for
|
||||
output on ``sys.stderr`` (if no logging was set up already).
|
||||
|
||||
|
||||
``hwgrep://``
|
||||
=============
|
||||
This type uses :mod:`serial.tools.list_ports` to obtain a list of ports and
|
||||
searches the list for matches by a regexp that follows the slashes (see Pythons
|
||||
:py:mod:`re` module for detailed syntax information).
|
||||
|
||||
Note that options are separated using the character ``&``, this also applies to
|
||||
the first, where URLs usually use ``?``. This exception is made as the question
|
||||
mark is used in regexp itself.
|
||||
|
||||
Depending on the capabilities of the ``list_ports`` module on the system, it is
|
||||
possible to search for the description or hardware ID of a device, e.g. USB
|
||||
VID:PID or texts.
|
||||
|
||||
Unfortunately, on some systems ``list_ports`` only lists a subset of the port
|
||||
names with no additional information. Currently, on Windows and Linux and
|
||||
OSX it should find additional information.
|
||||
|
||||
Supported options in the URL are:
|
||||
|
||||
- ``n=N``: pick the N'th entry instead of the first
|
||||
- ``skip_busy``: skip ports that can not be opened, e.g. because they are
|
||||
already in use. This may not work as expected on platforms where the file is
|
||||
not locked automatically (e.g. Posix).
|
||||
|
||||
|
||||
.. _spy:
|
||||
|
||||
``spy://``
|
||||
==========
|
||||
Wrapping the native serial port, this protocol makes it possible to
|
||||
intercept the data received and transmitted as well as the access to the
|
||||
control lines, break and flush commands. It is mainly used to debug
|
||||
applications.
|
||||
|
||||
Supported options in the URL are:
|
||||
|
||||
- ``file=FILENAME`` output to given file or device instead of stderr
|
||||
- ``color`` enable ANSI escape sequences to colorize output
|
||||
- ``raw`` output the read and written data directly (default is to create a
|
||||
hex dump). In this mode, no control line and other commands are logged.
|
||||
- ``all`` also show ``in_waiting`` and empty ``read()`` calls (hidden by
|
||||
default because of high traffic).
|
||||
- ``log`` or ``log=LOGGERNAME`` output to stdlib ``logging`` module. Default
|
||||
channel name is ``serial``. This variant outputs hex dump.
|
||||
- ``rawlog`` or ``rawlog=LOGGERNAME`` output to stdlib ``logging`` module. Default
|
||||
channel name is ``serial``. This variant outputs text (``repr``).
|
||||
|
||||
The ``log`` and ``rawlog`` options require that the logging is set up, in order
|
||||
to see the log output.
|
||||
|
||||
Example::
|
||||
|
||||
import serial
|
||||
|
||||
with serial.serial_for_url('spy:///dev/ttyUSB0?file=test.txt', timeout=1) as s:
|
||||
s.dtr = False
|
||||
s.write('hello world')
|
||||
s.read(20)
|
||||
s.dtr = True
|
||||
s.write(serial.to_bytes(range(256)))
|
||||
s.read(400)
|
||||
s.send_break()
|
||||
|
||||
with open('test.txt') as f:
|
||||
print(f.read())
|
||||
|
||||
Outputs::
|
||||
|
||||
000000.002 Q-RX reset_input_buffer
|
||||
000000.002 DTR inactive
|
||||
000000.002 TX 0000 68 65 6C 6C 6F 20 77 6F 72 6C 64 hello world
|
||||
000001.015 RX 0000 68 65 6C 6C 6F 20 77 6F 72 6C 64 hello world
|
||||
000001.015 DTR active
|
||||
000001.015 TX 0000 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ................
|
||||
000001.015 TX 0010 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ................
|
||||
000001.015 TX 0020 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F !"#$%&'()*+,-./
|
||||
000001.015 TX 0030 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 0123456789:;<=>?
|
||||
000001.015 TX 0040 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F @ABCDEFGHIJKLMNO
|
||||
000001.016 TX 0050 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F PQRSTUVWXYZ[\]^_
|
||||
000001.016 TX 0060 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F `abcdefghijklmno
|
||||
000001.016 TX 0070 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F pqrstuvwxyz{|}~.
|
||||
000001.016 TX 0080 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F ................
|
||||
000001.016 TX 0090 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F ................
|
||||
000001.016 TX 00A0 A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF ................
|
||||
000001.016 TX 00B0 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF ................
|
||||
000001.016 TX 00C0 C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF ................
|
||||
000001.016 TX 00D0 D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF ................
|
||||
000001.016 TX 00E0 E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF ................
|
||||
000001.016 TX 00F0 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
|
||||
000002.284 RX 0000 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ................
|
||||
000002.284 RX 0010 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ................
|
||||
000002.284 RX 0020 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F !"#$%&'()*+,-./
|
||||
000002.284 RX 0030 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 0123456789:;<=>?
|
||||
000002.284 RX 0040 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F @ABCDEFGHIJKLMNO
|
||||
000002.284 RX 0050 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F PQRSTUVWXYZ[\]^_
|
||||
000002.284 RX 0060 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F `abcdefghijklmno
|
||||
000002.284 RX 0070 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F pqrstuvwxyz{|}~.
|
||||
000002.284 RX 0080 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F ................
|
||||
000002.284 RX 0090 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F ................
|
||||
000002.284 RX 00A0 A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF ................
|
||||
000002.284 RX 00B0 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF ................
|
||||
000002.284 RX 00C0 C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF ................
|
||||
000002.284 RX 00D0 D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF ................
|
||||
000002.284 RX 00E0 E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF ................
|
||||
000002.284 RX 00F0 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................
|
||||
000002.284 BRK send_break 0.25
|
||||
|
||||
Another example, on POSIX, open a second terminal window and find out it's
|
||||
device (e.g. with the ``ps`` command in the TTY column), assumed to be
|
||||
``/dev/pts/2`` here, double quotes are used so that the ampersand in the URL is
|
||||
not interpreted by the shell::
|
||||
|
||||
python -m serial.tools.miniterm "spy:///dev/ttyUSB0?file=/dev/pts/2&color" 115200
|
||||
|
||||
The spy output will be live in the second terminal window.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
.. versionchanged:: 3.6 Added ``log`` and ``rawlog`` options
|
||||
|
||||
|
||||
``alt://``
|
||||
==========
|
||||
This handler allows to select alternate implementations of the native serial
|
||||
port.
|
||||
|
||||
Currently only the POSIX platform provides alternative implementations.
|
||||
|
||||
``PosixPollSerial``
|
||||
Poll based read implementation. Not all systems support poll properly.
|
||||
However this one has better handling of errors, such as a device
|
||||
disconnecting while it's in use (e.g. USB-serial unplugged).
|
||||
|
||||
``VTIMESerial``
|
||||
Implement timeout using ``VTIME``/``VMIN`` of TTY device instead of using
|
||||
``select``. This means that inter character timeout and overall timeout
|
||||
can not be used at the same time. Overall timeout is disabled when
|
||||
inter-character timeout is used. The error handling is degraded.
|
||||
|
||||
|
||||
Examples::
|
||||
|
||||
alt:///dev/ttyUSB0?class=PosixPollSerial
|
||||
alt:///dev/ttyUSB0?class=VTIMESerial
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
|
||||
``cp2110://``
|
||||
=============
|
||||
This backend implements support for HID-to-UART devices manufactured by Silicon
|
||||
Labs and marketed as CP2110 and CP2114. The implementation is (mostly)
|
||||
OS-independent and in userland. It relies on `cython-hidapi`_.
|
||||
|
||||
.. _cython-hidapi: https://github.com/trezor/cython-hidapi
|
||||
|
||||
Examples::
|
||||
|
||||
cp2110://0001:004a:00
|
||||
cp2110://0002:0077:00
|
||||
|
||||
.. versionadded:: 3.5
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
- ``rfc2217://localhost:7000``
|
||||
- ``rfc2217://localhost:7000?poll_modem``
|
||||
- ``rfc2217://localhost:7000?ign_set_control&timeout=5.5``
|
||||
- ``socket://localhost:7777``
|
||||
- ``loop://?logging=debug``
|
||||
- ``hwgrep://0451:f432`` (USB VID:PID)
|
||||
- ``spy://COM54?file=log.txt``
|
||||
- ``alt:///dev/ttyUSB0?class=PosixPollSerial``
|
||||
- ``cp2110://0001:004a:00``
|
||||
@@ -1,154 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Example of a AT command protocol.
|
||||
|
||||
https://en.wikipedia.org/wiki/Hayes_command_set
|
||||
http://www.itu.int/rec/T-REC-V.250-200307-I/en
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, '..')
|
||||
|
||||
import logging
|
||||
import serial
|
||||
import serial.threaded
|
||||
import threading
|
||||
|
||||
try:
|
||||
import queue
|
||||
except ImportError:
|
||||
import Queue as queue
|
||||
|
||||
|
||||
class ATException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ATProtocol(serial.threaded.LineReader):
|
||||
|
||||
TERMINATOR = b'\r\n'
|
||||
|
||||
def __init__(self):
|
||||
super(ATProtocol, self).__init__()
|
||||
self.alive = True
|
||||
self.responses = queue.Queue()
|
||||
self.events = queue.Queue()
|
||||
self._event_thread = threading.Thread(target=self._run_event)
|
||||
self._event_thread.daemon = True
|
||||
self._event_thread.name = 'at-event'
|
||||
self._event_thread.start()
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the event processing thread, abort pending commands, if any.
|
||||
"""
|
||||
self.alive = False
|
||||
self.events.put(None)
|
||||
self.responses.put('<exit>')
|
||||
|
||||
def _run_event(self):
|
||||
"""
|
||||
Process events in a separate thread so that input thread is not
|
||||
blocked.
|
||||
"""
|
||||
while self.alive:
|
||||
try:
|
||||
self.handle_event(self.events.get())
|
||||
except:
|
||||
logging.exception('_run_event')
|
||||
|
||||
def handle_line(self, line):
|
||||
"""
|
||||
Handle input from serial port, check for events.
|
||||
"""
|
||||
if line.startswith('+'):
|
||||
self.events.put(line)
|
||||
else:
|
||||
self.responses.put(line)
|
||||
|
||||
def handle_event(self, event):
|
||||
"""
|
||||
Spontaneous message received.
|
||||
"""
|
||||
print('event received:', event)
|
||||
|
||||
def command(self, command, response='OK', timeout=5):
|
||||
"""
|
||||
Set an AT command and wait for the response.
|
||||
"""
|
||||
with self.lock: # ensure that just one thread is sending commands at once
|
||||
self.write_line(command)
|
||||
lines = []
|
||||
while True:
|
||||
try:
|
||||
line = self.responses.get(timeout=timeout)
|
||||
#~ print("%s -> %r" % (command, line))
|
||||
if line == response:
|
||||
return lines
|
||||
else:
|
||||
lines.append(line)
|
||||
except queue.Empty:
|
||||
raise ATException('AT command timeout ({!r})'.format(command))
|
||||
|
||||
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
import time
|
||||
|
||||
class PAN1322(ATProtocol):
|
||||
"""
|
||||
Example communication with PAN1322 BT module.
|
||||
|
||||
Some commands do not respond with OK but with a '+...' line. This is
|
||||
implemented via command_with_event_response and handle_event, because
|
||||
'+...' lines are also used for real events.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(PAN1322, self).__init__()
|
||||
self.event_responses = queue.Queue()
|
||||
self._awaiting_response_for = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
super(PAN1322, self).connection_made(transport)
|
||||
# our adapter enables the module with RTS=low
|
||||
self.transport.serial.rts = False
|
||||
time.sleep(0.3)
|
||||
self.transport.serial.reset_input_buffer()
|
||||
|
||||
def handle_event(self, event):
|
||||
"""Handle events and command responses starting with '+...'"""
|
||||
if event.startswith('+RRBDRES') and self._awaiting_response_for.startswith('AT+JRBD'):
|
||||
rev = event[9:9 + 12]
|
||||
mac = ':'.join('{:02X}'.format(ord(x)) for x in rev.decode('hex')[::-1])
|
||||
self.event_responses.put(mac)
|
||||
else:
|
||||
logging.warning('unhandled event: {!r}'.format(event))
|
||||
|
||||
def command_with_event_response(self, command):
|
||||
"""Send a command that responds with '+...' line"""
|
||||
with self.lock: # ensure that just one thread is sending commands at once
|
||||
self._awaiting_response_for = command
|
||||
self.transport.write(b'{}\r\n'.format(command.encode(self.ENCODING, self.UNICODE_HANDLING)))
|
||||
response = self.event_responses.get()
|
||||
self._awaiting_response_for = None
|
||||
return response
|
||||
|
||||
# - - - example commands
|
||||
|
||||
def reset(self):
|
||||
self.command("AT+JRES", response='ROK') # SW-Reset BT module
|
||||
|
||||
def get_mac_address(self):
|
||||
# requests hardware / calibration info as event
|
||||
return self.command_with_event_response("AT+JRBD")
|
||||
|
||||
ser = serial.serial_for_url('spy://COM1', baudrate=115200, timeout=1)
|
||||
#~ ser = serial.Serial('COM1', baudrate=115200, timeout=1)
|
||||
with serial.threaded.ReaderThread(ser, PAN1322) as bt_module:
|
||||
bt_module.reset()
|
||||
print("reset OK")
|
||||
print("MAC address is", bt_module.get_mac_address())
|
||||
@@ -1,578 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Multi-port serial<->TCP/IP forwarder.
|
||||
- RFC 2217
|
||||
- check existence of serial port periodically
|
||||
- start/stop forwarders
|
||||
- each forwarder creates a server socket and opens the serial port
|
||||
- serial ports are opened only once. network connect/disconnect
|
||||
does not influence serial port
|
||||
- only one client per connection
|
||||
"""
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import serial
|
||||
import serial.rfc2217
|
||||
import serial.tools.list_ports
|
||||
|
||||
import dbus
|
||||
|
||||
# Try to import the avahi service definitions properly. If the avahi module is
|
||||
# not available, fall back to a hard-coded solution that hopefully still works.
|
||||
try:
|
||||
import avahi
|
||||
except ImportError:
|
||||
class avahi:
|
||||
DBUS_NAME = "org.freedesktop.Avahi"
|
||||
DBUS_PATH_SERVER = "/"
|
||||
DBUS_INTERFACE_SERVER = "org.freedesktop.Avahi.Server"
|
||||
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
|
||||
IF_UNSPEC = -1
|
||||
PROTO_UNSPEC, PROTO_INET, PROTO_INET6 = -1, 0, 1
|
||||
|
||||
|
||||
class ZeroconfService:
|
||||
"""\
|
||||
A simple class to publish a network service with zeroconf using avahi.
|
||||
"""
|
||||
|
||||
def __init__(self, name, port, stype="_http._tcp",
|
||||
domain="", host="", text=""):
|
||||
self.name = name
|
||||
self.stype = stype
|
||||
self.domain = domain
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.text = text
|
||||
self.group = None
|
||||
|
||||
def publish(self):
|
||||
bus = dbus.SystemBus()
|
||||
server = dbus.Interface(
|
||||
bus.get_object(
|
||||
avahi.DBUS_NAME,
|
||||
avahi.DBUS_PATH_SERVER
|
||||
),
|
||||
avahi.DBUS_INTERFACE_SERVER
|
||||
)
|
||||
|
||||
g = dbus.Interface(
|
||||
bus.get_object(
|
||||
avahi.DBUS_NAME,
|
||||
server.EntryGroupNew()
|
||||
),
|
||||
avahi.DBUS_INTERFACE_ENTRY_GROUP
|
||||
)
|
||||
|
||||
g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),
|
||||
self.name, self.stype, self.domain, self.host,
|
||||
dbus.UInt16(self.port), self.text)
|
||||
|
||||
g.Commit()
|
||||
self.group = g
|
||||
|
||||
def unpublish(self):
|
||||
if self.group is not None:
|
||||
self.group.Reset()
|
||||
self.group = None
|
||||
|
||||
def __str__(self):
|
||||
return "{!r} @ {}:{} ({})".format(self.name, self.host, self.port, self.stype)
|
||||
|
||||
|
||||
class Forwarder(ZeroconfService):
|
||||
"""\
|
||||
Single port serial<->TCP/IP forarder that depends on an external select
|
||||
loop.
|
||||
- Buffers for serial -> network and network -> serial
|
||||
- RFC 2217 state
|
||||
- Zeroconf publish/unpublish on open/close.
|
||||
"""
|
||||
|
||||
def __init__(self, device, name, network_port, on_close=None, log=None):
|
||||
ZeroconfService.__init__(self, name, network_port, stype='_serial_port._tcp')
|
||||
self.alive = False
|
||||
self.network_port = network_port
|
||||
self.on_close = on_close
|
||||
self.log = log
|
||||
self.device = device
|
||||
self.serial = serial.Serial()
|
||||
self.serial.port = device
|
||||
self.serial.baudrate = 115200
|
||||
self.serial.timeout = 0
|
||||
self.socket = None
|
||||
self.server_socket = None
|
||||
self.rfc2217 = None # instantiate later, when connecting
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
if self.alive:
|
||||
self.close()
|
||||
except:
|
||||
pass # XXX errors on shutdown
|
||||
|
||||
def open(self):
|
||||
"""open serial port, start network server and publish service"""
|
||||
self.buffer_net2ser = bytearray()
|
||||
self.buffer_ser2net = bytearray()
|
||||
|
||||
# open serial port
|
||||
try:
|
||||
self.serial.rts = False
|
||||
self.serial.open()
|
||||
except Exception as msg:
|
||||
self.handle_serial_error(msg)
|
||||
|
||||
self.serial_settings_backup = self.serial.get_settings()
|
||||
|
||||
# start the socket server
|
||||
# XXX add IPv6 support: use getaddrinfo for socket options, bind to multiple sockets?
|
||||
# info_list = socket.getaddrinfo(None, port, 0, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server_socket.setsockopt(
|
||||
socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR,
|
||||
self.server_socket.getsockopt(
|
||||
socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR
|
||||
) | 1
|
||||
)
|
||||
self.server_socket.setblocking(0)
|
||||
try:
|
||||
self.server_socket.bind(('', self.network_port))
|
||||
self.server_socket.listen(1)
|
||||
except socket.error as msg:
|
||||
self.handle_server_error()
|
||||
#~ raise
|
||||
if self.log is not None:
|
||||
self.log.info("{}: Waiting for connection on {}...".format(self.device, self.network_port))
|
||||
|
||||
# zeroconfig
|
||||
self.publish()
|
||||
|
||||
# now we are ready
|
||||
self.alive = True
|
||||
|
||||
def close(self):
|
||||
"""Close all resources and unpublish service"""
|
||||
if self.log is not None:
|
||||
self.log.info("{}: closing...".format(self.device))
|
||||
self.alive = False
|
||||
self.unpublish()
|
||||
if self.server_socket:
|
||||
self.server_socket.close()
|
||||
if self.socket:
|
||||
self.handle_disconnect()
|
||||
self.serial.close()
|
||||
if self.on_close is not None:
|
||||
# ensure it is only called once
|
||||
callback = self.on_close
|
||||
self.on_close = None
|
||||
callback(self)
|
||||
|
||||
def write(self, data):
|
||||
"""the write method is used by serial.rfc2217.PortManager. it has to
|
||||
write to the network."""
|
||||
self.buffer_ser2net += data
|
||||
|
||||
def update_select_maps(self, read_map, write_map, error_map):
|
||||
"""Update dictionaries for select call. insert fd->callback mapping"""
|
||||
if self.alive:
|
||||
# always handle serial port reads
|
||||
read_map[self.serial] = self.handle_serial_read
|
||||
error_map[self.serial] = self.handle_serial_error
|
||||
# handle serial port writes if buffer is not empty
|
||||
if self.buffer_net2ser:
|
||||
write_map[self.serial] = self.handle_serial_write
|
||||
# handle network
|
||||
if self.socket is not None:
|
||||
# handle socket if connected
|
||||
# only read from network if the internal buffer is not
|
||||
# already filled. the TCP flow control will hold back data
|
||||
if len(self.buffer_net2ser) < 2048:
|
||||
read_map[self.socket] = self.handle_socket_read
|
||||
# only check for write readiness when there is data
|
||||
if self.buffer_ser2net:
|
||||
write_map[self.socket] = self.handle_socket_write
|
||||
error_map[self.socket] = self.handle_socket_error
|
||||
else:
|
||||
# no connection, ensure clear buffer
|
||||
self.buffer_ser2net = bytearray()
|
||||
# check the server socket
|
||||
read_map[self.server_socket] = self.handle_connect
|
||||
error_map[self.server_socket] = self.handle_server_error
|
||||
|
||||
def handle_serial_read(self):
|
||||
"""Reading from serial port"""
|
||||
try:
|
||||
data = os.read(self.serial.fileno(), 1024)
|
||||
if data:
|
||||
# store data in buffer if there is a client connected
|
||||
if self.socket is not None:
|
||||
# escape outgoing data when needed (Telnet IAC (0xff) character)
|
||||
if self.rfc2217:
|
||||
data = serial.to_bytes(self.rfc2217.escape(data))
|
||||
self.buffer_ser2net.extend(data)
|
||||
else:
|
||||
self.handle_serial_error()
|
||||
except Exception as msg:
|
||||
self.handle_serial_error(msg)
|
||||
|
||||
def handle_serial_write(self):
|
||||
"""Writing to serial port"""
|
||||
try:
|
||||
# write a chunk
|
||||
n = os.write(self.serial.fileno(), bytes(self.buffer_net2ser))
|
||||
# and see how large that chunk was, remove that from buffer
|
||||
self.buffer_net2ser = self.buffer_net2ser[n:]
|
||||
except Exception as msg:
|
||||
self.handle_serial_error(msg)
|
||||
|
||||
def handle_serial_error(self, error=None):
|
||||
"""Serial port error"""
|
||||
# terminate connection
|
||||
self.close()
|
||||
|
||||
def handle_socket_read(self):
|
||||
"""Read from socket"""
|
||||
try:
|
||||
# read a chunk from the serial port
|
||||
data = self.socket.recv(1024)
|
||||
if data:
|
||||
# Process RFC 2217 stuff when enabled
|
||||
if self.rfc2217:
|
||||
data = b''.join(self.rfc2217.filter(data))
|
||||
# add data to buffer
|
||||
self.buffer_net2ser.extend(data)
|
||||
else:
|
||||
# empty read indicates disconnection
|
||||
self.handle_disconnect()
|
||||
except socket.error:
|
||||
if self.log is not None:
|
||||
self.log.exception("{}: error reading...".format(self.device))
|
||||
self.handle_socket_error()
|
||||
|
||||
def handle_socket_write(self):
|
||||
"""Write to socket"""
|
||||
try:
|
||||
# write a chunk
|
||||
count = self.socket.send(bytes(self.buffer_ser2net))
|
||||
# and remove the sent data from the buffer
|
||||
self.buffer_ser2net = self.buffer_ser2net[count:]
|
||||
except socket.error:
|
||||
if self.log is not None:
|
||||
self.log.exception("{}: error writing...".format(self.device))
|
||||
self.handle_socket_error()
|
||||
|
||||
def handle_socket_error(self):
|
||||
"""Socket connection fails"""
|
||||
self.handle_disconnect()
|
||||
|
||||
def handle_connect(self):
|
||||
"""Server socket gets a connection"""
|
||||
# accept a connection in any case, close connection
|
||||
# below if already busy
|
||||
connection, addr = self.server_socket.accept()
|
||||
if self.socket is None:
|
||||
self.socket = connection
|
||||
# More quickly detect bad clients who quit without closing the
|
||||
# connection: After 1 second of idle, start sending TCP keep-alive
|
||||
# packets every 1 second. If 3 consecutive keep-alive packets
|
||||
# fail, assume the client is gone and close the connection.
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
|
||||
self.socket.setblocking(0)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
if self.log is not None:
|
||||
self.log.warning('{}: Connected by {}:{}'.format(self.device, addr[0], addr[1]))
|
||||
self.serial.rts = True
|
||||
self.serial.dtr = True
|
||||
if self.log is not None:
|
||||
self.rfc2217 = serial.rfc2217.PortManager(self.serial, self, logger=log.getChild(self.device))
|
||||
else:
|
||||
self.rfc2217 = serial.rfc2217.PortManager(self.serial, self)
|
||||
else:
|
||||
# reject connection if there is already one
|
||||
connection.close()
|
||||
if self.log is not None:
|
||||
self.log.warning('{}: Rejecting connect from {}:{}'.format(self.device, addr[0], addr[1]))
|
||||
|
||||
def handle_server_error(self):
|
||||
"""Socket server fails"""
|
||||
self.close()
|
||||
|
||||
def handle_disconnect(self):
|
||||
"""Socket gets disconnected"""
|
||||
# signal disconnected terminal with control lines
|
||||
try:
|
||||
self.serial.rts = False
|
||||
self.serial.dtr = False
|
||||
finally:
|
||||
# restore original port configuration in case it was changed
|
||||
self.serial.apply_settings(self.serial_settings_backup)
|
||||
# stop RFC 2217 state machine
|
||||
self.rfc2217 = None
|
||||
# clear send buffer
|
||||
self.buffer_ser2net = bytearray()
|
||||
# close network connection
|
||||
if self.socket is not None:
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
if self.log is not None:
|
||||
self.log.warning('{}: Disconnected'.format(self.device))
|
||||
|
||||
|
||||
def test():
|
||||
service = ZeroconfService(name="TestService", port=3000)
|
||||
service.publish()
|
||||
input("Press the ENTER key to unpublish the service ")
|
||||
service.unpublish()
|
||||
|
||||
|
||||
if __name__ == '__main__': # noqa
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
VERBOSTIY = [
|
||||
logging.ERROR, # 0
|
||||
logging.WARNING, # 1 (default)
|
||||
logging.INFO, # 2
|
||||
logging.DEBUG, # 3
|
||||
]
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
usage="""\
|
||||
%(prog)s [options]
|
||||
|
||||
Announce the existence of devices using zeroconf and provide
|
||||
a TCP/IP <-> serial port gateway (implements RFC 2217).
|
||||
|
||||
If running as daemon, write to syslog. Otherwise write to stdout.
|
||||
""",
|
||||
epilog="""\
|
||||
NOTE: no security measures are implemented. Anyone can remotely connect
|
||||
to this service over the network.
|
||||
|
||||
Only one connection at once, per port, is supported. When the connection is
|
||||
terminated, it waits for the next connect.
|
||||
""")
|
||||
|
||||
group = parser.add_argument_group("serial port settings")
|
||||
|
||||
group.add_argument(
|
||||
"--ports-regex",
|
||||
help="specify a regex to search against the serial devices and their descriptions (default: %(default)s)",
|
||||
default='/dev/ttyUSB[0-9]+',
|
||||
metavar="REGEX")
|
||||
|
||||
group = parser.add_argument_group("network settings")
|
||||
|
||||
group.add_argument(
|
||||
"--tcp-port",
|
||||
dest="base_port",
|
||||
help="specify lowest TCP port number (default: %(default)s)",
|
||||
default=7000,
|
||||
type=int,
|
||||
metavar="PORT")
|
||||
|
||||
group = parser.add_argument_group("daemon")
|
||||
|
||||
group.add_argument(
|
||||
"-d", "--daemon",
|
||||
dest="daemonize",
|
||||
action="store_true",
|
||||
help="start as daemon",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--pidfile",
|
||||
help="specify a name for the PID file",
|
||||
default=None,
|
||||
metavar="FILE")
|
||||
|
||||
group = parser.add_argument_group("diagnostics")
|
||||
|
||||
group.add_argument(
|
||||
"-o", "--logfile",
|
||||
help="write messages file instead of stdout",
|
||||
default=None,
|
||||
metavar="FILE")
|
||||
|
||||
group.add_argument(
|
||||
"-q", "--quiet",
|
||||
dest="verbosity",
|
||||
action="store_const",
|
||||
const=0,
|
||||
help="suppress most diagnostic messages",
|
||||
default=1)
|
||||
|
||||
group.add_argument(
|
||||
"-v", "--verbose",
|
||||
dest="verbosity",
|
||||
action="count",
|
||||
help="increase diagnostic messages")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# set up logging
|
||||
logging.basicConfig(level=VERBOSTIY[min(args.verbosity, len(VERBOSTIY) - 1)])
|
||||
log = logging.getLogger('port_publisher')
|
||||
|
||||
# redirect output if specified
|
||||
if args.logfile is not None:
|
||||
class WriteFlushed:
|
||||
def __init__(self, fileobj):
|
||||
self.fileobj = fileobj
|
||||
|
||||
def write(self, s):
|
||||
self.fileobj.write(s)
|
||||
self.fileobj.flush()
|
||||
|
||||
def close(self):
|
||||
self.fileobj.close()
|
||||
sys.stdout = sys.stderr = WriteFlushed(open(args.logfile, 'a'))
|
||||
# atexit.register(lambda: sys.stdout.close())
|
||||
|
||||
if args.daemonize:
|
||||
# if running as daemon is requested, do the fork magic
|
||||
# args.quiet = True
|
||||
# do the UNIX double-fork magic, see Stevens' "Advanced
|
||||
# Programming in the UNIX Environment" for details (ISBN 0201563177)
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit first parent
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
log.critical("fork #1 failed: {} ({})\n".format(e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# decouple from parent environment
|
||||
os.chdir("/") # don't prevent unmounting....
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
# do second fork
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit from second parent, save eventual PID before
|
||||
if args.pidfile is not None:
|
||||
open(args.pidfile, 'w').write("{}".format(pid))
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
log.critical("fork #2 failed: {} ({})\n".format(e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
if args.logfile is None:
|
||||
import syslog
|
||||
syslog.openlog("serial port publisher")
|
||||
|
||||
# redirect output to syslog
|
||||
class WriteToSysLog:
|
||||
def __init__(self):
|
||||
self.buffer = ''
|
||||
|
||||
def write(self, s):
|
||||
self.buffer += s
|
||||
if '\n' in self.buffer:
|
||||
output, self.buffer = self.buffer.split('\n', 1)
|
||||
syslog.syslog(output)
|
||||
|
||||
def flush(self):
|
||||
syslog.syslog(self.buffer)
|
||||
self.buffer = ''
|
||||
|
||||
def close(self):
|
||||
self.flush()
|
||||
sys.stdout = sys.stderr = WriteToSysLog()
|
||||
|
||||
# ensure the that the daemon runs a normal user, if run as root
|
||||
# if os.getuid() == 0:
|
||||
# name, passwd, uid, gid, desc, home, shell = pwd.getpwnam('someuser')
|
||||
# os.setgid(gid) # set group first
|
||||
# os.setuid(uid) # set user
|
||||
|
||||
# keep the published stuff in a dictionary
|
||||
published = {}
|
||||
# get a nice hostname
|
||||
hostname = socket.gethostname()
|
||||
|
||||
def unpublish(forwarder):
|
||||
"""when forwarders die, we need to unregister them"""
|
||||
try:
|
||||
del published[forwarder.device]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
log.info("unpublish: {}".format(forwarder))
|
||||
|
||||
alive = True
|
||||
next_check = 0
|
||||
# main loop
|
||||
while alive:
|
||||
try:
|
||||
# if it is time, check for serial port devices
|
||||
now = time.time()
|
||||
if now > next_check:
|
||||
next_check = now + 5
|
||||
connected = [d for d, p, i in serial.tools.list_ports.grep(args.ports_regex)]
|
||||
# Handle devices that are published, but no longer connected
|
||||
for device in set(published).difference(connected):
|
||||
log.info("unpublish: {}".format(published[device]))
|
||||
unpublish(published[device])
|
||||
# Handle devices that are connected but not yet published
|
||||
for device in sorted(set(connected).difference(published)):
|
||||
# Find the first available port, starting from specified number
|
||||
port = args.base_port
|
||||
ports_in_use = [f.network_port for f in published.values()]
|
||||
while port in ports_in_use:
|
||||
port += 1
|
||||
published[device] = Forwarder(
|
||||
device,
|
||||
"{} on {}".format(device, hostname),
|
||||
port,
|
||||
on_close=unpublish,
|
||||
log=log)
|
||||
log.warning("publish: {}".format(published[device]))
|
||||
published[device].open()
|
||||
|
||||
# select_start = time.time()
|
||||
read_map = {}
|
||||
write_map = {}
|
||||
error_map = {}
|
||||
for publisher in published.values():
|
||||
publisher.update_select_maps(read_map, write_map, error_map)
|
||||
readers, writers, errors = select.select(
|
||||
read_map.keys(),
|
||||
write_map.keys(),
|
||||
error_map.keys(),
|
||||
5)
|
||||
# select_end = time.time()
|
||||
# print "select used %.3f s" % (select_end - select_start)
|
||||
for reader in readers:
|
||||
read_map[reader]()
|
||||
for writer in writers:
|
||||
write_map[writer]()
|
||||
for error in errors:
|
||||
error_map[error]()
|
||||
# print "operation used %.3f s" % (time.time() - select_end)
|
||||
except KeyboardInterrupt:
|
||||
alive = False
|
||||
sys.stdout.write('\n')
|
||||
except SystemExit:
|
||||
raise
|
||||
except:
|
||||
#~ raise
|
||||
traceback.print_exc()
|
||||
@@ -1,44 +0,0 @@
|
||||
#! /bin/sh
|
||||
# daemon starter script
|
||||
# based on skeleton from Debian GNU/Linux
|
||||
# cliechti at gmx.net
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||
DAEMON=/usr/local/bin/port_publisher.py
|
||||
NAME=port_publisher
|
||||
DESC="serial port avahi device publisher"
|
||||
|
||||
test -f $DAEMON || exit 0
|
||||
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo -n "Starting $DESC: "
|
||||
$DAEMON --daemon --pidfile /var/run/$NAME.pid
|
||||
echo "$NAME."
|
||||
;;
|
||||
stop)
|
||||
echo -n "Stopping $DESC: "
|
||||
start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid
|
||||
# \ --exec $DAEMON
|
||||
echo "$NAME."
|
||||
;;
|
||||
restart|force-reload)
|
||||
echo -n "Restarting $DESC: "
|
||||
start-stop-daemon --stop --quiet --pidfile \
|
||||
/var/run/$NAME.pid
|
||||
# --exec $DAEMON
|
||||
sleep 1
|
||||
$DAEMON --daemon --pidfile /var/run/$NAME.pid
|
||||
echo "$NAME."
|
||||
;;
|
||||
*)
|
||||
N=/etc/init.d/$NAME
|
||||
echo "Usage: $N {start|stop|restart|force-reload}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# redirect data from a TCP/IP connection to a serial port and vice versa
|
||||
# using RFC 2217
|
||||
#
|
||||
# (C) 2009-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import logging
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import serial
|
||||
import serial.rfc2217
|
||||
|
||||
|
||||
class Redirector(object):
|
||||
def __init__(self, serial_instance, socket, debug=False):
|
||||
self.serial = serial_instance
|
||||
self.socket = socket
|
||||
self._write_lock = threading.Lock()
|
||||
self.rfc2217 = serial.rfc2217.PortManager(
|
||||
self.serial,
|
||||
self,
|
||||
logger=logging.getLogger('rfc2217.server') if debug else None)
|
||||
self.log = logging.getLogger('redirector')
|
||||
|
||||
def statusline_poller(self):
|
||||
self.log.debug('status line poll thread started')
|
||||
while self.alive:
|
||||
time.sleep(1)
|
||||
self.rfc2217.check_modem_lines()
|
||||
self.log.debug('status line poll thread terminated')
|
||||
|
||||
def shortcircuit(self):
|
||||
"""connect the serial port to the TCP port by copying everything
|
||||
from one side to the other"""
|
||||
self.alive = True
|
||||
self.thread_read = threading.Thread(target=self.reader)
|
||||
self.thread_read.daemon = True
|
||||
self.thread_read.name = 'serial->socket'
|
||||
self.thread_read.start()
|
||||
self.thread_poll = threading.Thread(target=self.statusline_poller)
|
||||
self.thread_poll.daemon = True
|
||||
self.thread_poll.name = 'status line poll'
|
||||
self.thread_poll.start()
|
||||
self.writer()
|
||||
|
||||
def reader(self):
|
||||
"""loop forever and copy serial->socket"""
|
||||
self.log.debug('reader thread started')
|
||||
while self.alive:
|
||||
try:
|
||||
data = self.serial.read(self.serial.in_waiting or 1)
|
||||
if data:
|
||||
# escape outgoing data when needed (Telnet IAC (0xff) character)
|
||||
self.write(b''.join(self.rfc2217.escape(data)))
|
||||
except socket.error as msg:
|
||||
self.log.error('{}'.format(msg))
|
||||
# probably got disconnected
|
||||
break
|
||||
self.alive = False
|
||||
self.log.debug('reader thread terminated')
|
||||
|
||||
def write(self, data):
|
||||
"""thread safe socket write with no data escaping. used to send telnet stuff"""
|
||||
with self._write_lock:
|
||||
self.socket.sendall(data)
|
||||
|
||||
def writer(self):
|
||||
"""loop forever and copy socket->serial"""
|
||||
while self.alive:
|
||||
try:
|
||||
data = self.socket.recv(1024)
|
||||
if not data:
|
||||
break
|
||||
self.serial.write(b''.join(self.rfc2217.filter(data)))
|
||||
except socket.error as msg:
|
||||
self.log.error('{}'.format(msg))
|
||||
# probably got disconnected
|
||||
break
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
"""Stop copying"""
|
||||
self.log.debug('stopping')
|
||||
if self.alive:
|
||||
self.alive = False
|
||||
self.thread_read.join()
|
||||
self.thread_poll.join()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="RFC 2217 Serial to Network (TCP/IP) redirector.",
|
||||
epilog="""\
|
||||
NOTE: no security measures are implemented. Anyone can remotely connect
|
||||
to this service over the network.
|
||||
|
||||
Only one connection at once is supported. When the connection is terminated
|
||||
it waits for the next connect.
|
||||
""")
|
||||
|
||||
parser.add_argument('SERIALPORT')
|
||||
|
||||
parser.add_argument(
|
||||
'-p', '--localport',
|
||||
type=int,
|
||||
help='local TCP port, default: %(default)s',
|
||||
metavar='TCPPORT',
|
||||
default=2217)
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
dest='verbosity',
|
||||
action='count',
|
||||
help='print more diagnostic messages (option can be given multiple times)',
|
||||
default=0)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbosity > 3:
|
||||
args.verbosity = 3
|
||||
level = (logging.WARNING,
|
||||
logging.INFO,
|
||||
logging.DEBUG,
|
||||
logging.NOTSET)[args.verbosity]
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
#~ logging.getLogger('root').setLevel(logging.INFO)
|
||||
logging.getLogger('rfc2217').setLevel(level)
|
||||
|
||||
# connect to serial port
|
||||
ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True)
|
||||
ser.timeout = 3 # required so that the reader thread can exit
|
||||
# reset control line as no _remote_ "terminal" has been connected yet
|
||||
ser.dtr = False
|
||||
ser.rts = False
|
||||
|
||||
logging.info("RFC 2217 TCP/IP to Serial redirector - type Ctrl-C / BREAK to quit")
|
||||
|
||||
try:
|
||||
ser.open()
|
||||
except serial.SerialException as e:
|
||||
logging.error("Could not open serial port {}: {}".format(ser.name, e))
|
||||
sys.exit(1)
|
||||
|
||||
logging.info("Serving serial port: {}".format(ser.name))
|
||||
settings = ser.get_settings()
|
||||
|
||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(('', args.localport))
|
||||
srv.listen(1)
|
||||
logging.info("TCP/IP port: {}".format(args.localport))
|
||||
while True:
|
||||
try:
|
||||
client_socket, addr = srv.accept()
|
||||
logging.info('Connected by {}:{}'.format(addr[0], addr[1]))
|
||||
client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
ser.rts = True
|
||||
ser.dtr = True
|
||||
# enter network <-> serial loop
|
||||
r = Redirector(
|
||||
ser,
|
||||
client_socket,
|
||||
args.verbosity > 0)
|
||||
try:
|
||||
r.shortcircuit()
|
||||
finally:
|
||||
logging.info('Disconnected')
|
||||
r.stop()
|
||||
client_socket.close()
|
||||
ser.dtr = False
|
||||
ser.rts = False
|
||||
# Restore port settings (may have been changed by RFC 2217
|
||||
# capable client)
|
||||
ser.apply_settings(settings)
|
||||
except KeyboardInterrupt:
|
||||
sys.stdout.write('\n')
|
||||
break
|
||||
except socket.error as msg:
|
||||
logging.error(str(msg))
|
||||
|
||||
logging.info('--- exit ---')
|
||||
@@ -1,33 +0,0 @@
|
||||
# setup script for py2exe to create the miniterm.exe
|
||||
#
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from distutils.core import setup
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '..')
|
||||
|
||||
import serial.tools.miniterm
|
||||
|
||||
|
||||
sys.argv.extend("py2exe --bundle 1".split())
|
||||
|
||||
setup(
|
||||
name='miniterm',
|
||||
zipfile=None,
|
||||
options={"py2exe": {
|
||||
'dll_excludes': [],
|
||||
'includes': [
|
||||
'serial.urlhandler.protocol_hwgrep', 'serial.urlhandler.protocol_rfc2217',
|
||||
'serial.urlhandler.protocol_socket', 'serial.urlhandler.protocol_loop'],
|
||||
'dist_dir': 'bin',
|
||||
'excludes': ['serialjava', 'serialposix', 'serialcli'],
|
||||
'compressed': 1,
|
||||
}
|
||||
},
|
||||
console=[
|
||||
serial.tools.miniterm.__file__
|
||||
],
|
||||
)
|
||||
@@ -1,31 +0,0 @@
|
||||
# setup script for py2exe to create the rfc2217_server.exe
|
||||
#
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from distutils.core import setup
|
||||
import sys
|
||||
import py2exe
|
||||
|
||||
sys.path.insert(0, '..')
|
||||
|
||||
sys.argv.extend("py2exe --bundle 1".split())
|
||||
|
||||
setup(
|
||||
name='rfc2217_server',
|
||||
zipfile=None,
|
||||
options={"py2exe": {
|
||||
'dll_excludes': [],
|
||||
'includes': [
|
||||
'serial.urlhandler.protocol_hwgrep', 'serial.urlhandler.protocol_rfc2217',
|
||||
'serial.urlhandler.protocol_socket', 'serial.urlhandler.protocol_loop'],
|
||||
'dist_dir': 'bin',
|
||||
'excludes': ['serialjava', 'serialposix', 'serialcli'],
|
||||
'compressed': 1,
|
||||
},
|
||||
},
|
||||
console=[
|
||||
"rfc2217_server.py",
|
||||
],
|
||||
)
|
||||
@@ -1,41 +0,0 @@
|
||||
# This is a setup.py example script for the use with py2exe
|
||||
#
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from distutils.core import setup
|
||||
import os
|
||||
import sys
|
||||
|
||||
# this script is only useful for py2exe so just run that distutils command.
|
||||
# that allows to run it with a simple double click.
|
||||
sys.argv.append('py2exe')
|
||||
|
||||
# get an icon from somewhere.. the python installation should have one:
|
||||
icon = os.path.join(os.path.dirname(sys.executable), 'py.ico')
|
||||
|
||||
setup(
|
||||
options={
|
||||
'py2exe': {
|
||||
'excludes': ['javax.comm'],
|
||||
'optimize': 2,
|
||||
'dist_dir': 'dist',
|
||||
}
|
||||
},
|
||||
|
||||
name="wxTerminal",
|
||||
windows=[
|
||||
{
|
||||
'script': "wxTerminal.py",
|
||||
'icon_resources': [(0x0004, icon)]
|
||||
},
|
||||
],
|
||||
zipfile="stuff.lib",
|
||||
|
||||
description="Simple serial terminal application",
|
||||
version="0.1",
|
||||
author="Chris Liechti",
|
||||
author_email="cliechti@gmx.net",
|
||||
url="https://github.com/pyserial/pyserial/",
|
||||
)
|
||||
@@ -1,230 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Redirect data from a TCP/IP connection to a serial port and vice versa.
|
||||
#
|
||||
# (C) 2002-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import sys
|
||||
import socket
|
||||
import serial
|
||||
import serial.threaded
|
||||
import time
|
||||
|
||||
|
||||
class SerialToNet(serial.threaded.Protocol):
|
||||
"""serial->socket"""
|
||||
|
||||
def __init__(self):
|
||||
self.socket = None
|
||||
|
||||
def __call__(self):
|
||||
return self
|
||||
|
||||
def data_received(self, data):
|
||||
if self.socket is not None:
|
||||
self.socket.sendall(data)
|
||||
|
||||
|
||||
if __name__ == '__main__': # noqa
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Simple Serial to Network (TCP/IP) redirector.',
|
||||
epilog="""\
|
||||
NOTE: no security measures are implemented. Anyone can remotely connect
|
||||
to this service over the network.
|
||||
|
||||
Only one connection at once is supported. When the connection is terminated
|
||||
it waits for the next connect.
|
||||
""")
|
||||
|
||||
parser.add_argument(
|
||||
'SERIALPORT',
|
||||
help="serial port name")
|
||||
|
||||
parser.add_argument(
|
||||
'BAUDRATE',
|
||||
type=int,
|
||||
nargs='?',
|
||||
help='set baud rate, default: %(default)s',
|
||||
default=9600)
|
||||
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help='suppress non error messages',
|
||||
default=False)
|
||||
|
||||
parser.add_argument(
|
||||
'--develop',
|
||||
action='store_true',
|
||||
help='Development mode, prints Python internals on errors',
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group('serial port')
|
||||
|
||||
group.add_argument(
|
||||
"--bytesize",
|
||||
choices=[5, 6, 7, 8],
|
||||
type=int,
|
||||
help="set bytesize, one of {5 6 7 8}, default: 8",
|
||||
default=8)
|
||||
|
||||
group.add_argument(
|
||||
"--parity",
|
||||
choices=['N', 'E', 'O', 'S', 'M'],
|
||||
type=lambda c: c.upper(),
|
||||
help="set parity, one of {N E O S M}, default: N",
|
||||
default='N')
|
||||
|
||||
group.add_argument(
|
||||
"--stopbits",
|
||||
choices=[1, 1.5, 2],
|
||||
type=float,
|
||||
help="set stopbits, one of {1 1.5 2}, default: 1",
|
||||
default=1)
|
||||
|
||||
group.add_argument(
|
||||
'--rtscts',
|
||||
action='store_true',
|
||||
help='enable RTS/CTS flow control (default off)',
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
'--xonxoff',
|
||||
action='store_true',
|
||||
help='enable software flow control (default off)',
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
'--rts',
|
||||
type=int,
|
||||
help='set initial RTS line state (possible values: 0, 1)',
|
||||
default=None)
|
||||
|
||||
group.add_argument(
|
||||
'--dtr',
|
||||
type=int,
|
||||
help='set initial DTR line state (possible values: 0, 1)',
|
||||
default=None)
|
||||
|
||||
group = parser.add_argument_group('network settings')
|
||||
|
||||
exclusive_group = group.add_mutually_exclusive_group()
|
||||
|
||||
exclusive_group.add_argument(
|
||||
'-P', '--localport',
|
||||
type=int,
|
||||
help='local TCP port',
|
||||
default=7777)
|
||||
|
||||
exclusive_group.add_argument(
|
||||
'-c', '--client',
|
||||
metavar='HOST:PORT',
|
||||
help='make the connection as a client, instead of running a server',
|
||||
default=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# connect to serial port
|
||||
ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True)
|
||||
ser.baudrate = args.BAUDRATE
|
||||
ser.bytesize = args.bytesize
|
||||
ser.parity = args.parity
|
||||
ser.stopbits = args.stopbits
|
||||
ser.rtscts = args.rtscts
|
||||
ser.xonxoff = args.xonxoff
|
||||
|
||||
if args.rts is not None:
|
||||
ser.rts = args.rts
|
||||
|
||||
if args.dtr is not None:
|
||||
ser.dtr = args.dtr
|
||||
|
||||
if not args.quiet:
|
||||
sys.stderr.write(
|
||||
'--- TCP/IP to Serial redirect on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'
|
||||
'--- type Ctrl-C / BREAK to quit\n'.format(p=ser))
|
||||
|
||||
try:
|
||||
ser.open()
|
||||
except serial.SerialException as e:
|
||||
sys.stderr.write('Could not open serial port {}: {}\n'.format(ser.name, e))
|
||||
sys.exit(1)
|
||||
|
||||
ser_to_net = SerialToNet()
|
||||
serial_worker = serial.threaded.ReaderThread(ser, ser_to_net)
|
||||
serial_worker.start()
|
||||
|
||||
if not args.client:
|
||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(('', args.localport))
|
||||
srv.listen(1)
|
||||
try:
|
||||
intentional_exit = False
|
||||
while True:
|
||||
if args.client:
|
||||
host, port = args.client.split(':')
|
||||
sys.stderr.write("Opening connection to {}:{}...\n".format(host, port))
|
||||
client_socket = socket.socket()
|
||||
try:
|
||||
client_socket.connect((host, int(port)))
|
||||
except socket.error as msg:
|
||||
sys.stderr.write('WARNING: {}\n'.format(msg))
|
||||
time.sleep(5) # intentional delay on reconnection as client
|
||||
continue
|
||||
sys.stderr.write('Connected\n')
|
||||
client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
#~ client_socket.settimeout(5)
|
||||
else:
|
||||
sys.stderr.write('Waiting for connection on {}...\n'.format(args.localport))
|
||||
client_socket, addr = srv.accept()
|
||||
sys.stderr.write('Connected by {}\n'.format(addr))
|
||||
# More quickly detect bad clients who quit without closing the
|
||||
# connection: After 1 second of idle, start sending TCP keep-alive
|
||||
# packets every 1 second. If 3 consecutive keep-alive packets
|
||||
# fail, assume the client is gone and close the connection.
|
||||
try:
|
||||
client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
|
||||
client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)
|
||||
client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
|
||||
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
except AttributeError:
|
||||
pass # XXX not available on windows
|
||||
client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
try:
|
||||
ser_to_net.socket = client_socket
|
||||
# enter network <-> serial loop
|
||||
while True:
|
||||
try:
|
||||
data = client_socket.recv(1024)
|
||||
if not data:
|
||||
break
|
||||
ser.write(data) # get a bunch of bytes and send them
|
||||
except socket.error as msg:
|
||||
if args.develop:
|
||||
raise
|
||||
sys.stderr.write('ERROR: {}\n'.format(msg))
|
||||
# probably got disconnected
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
intentional_exit = True
|
||||
raise
|
||||
except socket.error as msg:
|
||||
if args.develop:
|
||||
raise
|
||||
sys.stderr.write('ERROR: {}\n'.format(msg))
|
||||
finally:
|
||||
ser_to_net.socket = None
|
||||
sys.stderr.write('Disconnected\n')
|
||||
client_socket.close()
|
||||
if args.client and not intentional_exit:
|
||||
time.sleep(5) # intentional delay on reconnection as client
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
sys.stderr.write('\n--- exit ---\n')
|
||||
serial_worker.stop()
|
||||
@@ -1,293 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A serial port configuration dialog for wxPython. A number of flags can
|
||||
# be used to configure the fields that are displayed.
|
||||
#
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import wx
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
|
||||
SHOW_BAUDRATE = 1 << 0
|
||||
SHOW_FORMAT = 1 << 1
|
||||
SHOW_FLOW = 1 << 2
|
||||
SHOW_TIMEOUT = 1 << 3
|
||||
SHOW_ALL = SHOW_BAUDRATE | SHOW_FORMAT | SHOW_FLOW | SHOW_TIMEOUT
|
||||
|
||||
|
||||
class SerialConfigDialog(wx.Dialog):
|
||||
"""\
|
||||
Serial Port configuration dialog, to be used with pySerial 2.0+
|
||||
When instantiating a class of this dialog, then the "serial" keyword
|
||||
argument is mandatory. It is a reference to a serial.Serial instance.
|
||||
the optional "show" keyword argument can be used to show/hide different
|
||||
settings. The default is SHOW_ALL which corresponds to
|
||||
SHOW_BAUDRATE|SHOW_FORMAT|SHOW_FLOW|SHOW_TIMEOUT. All constants can be
|
||||
found in this module (not the class).
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
# grab the serial keyword and remove it from the dict
|
||||
self.serial = kwds['serial']
|
||||
del kwds['serial']
|
||||
self.show = SHOW_ALL
|
||||
if 'show' in kwds:
|
||||
self.show = kwds.pop('show')
|
||||
# begin wxGlade: SerialConfigDialog.__init__
|
||||
kwds["style"] = wx.DEFAULT_DIALOG_STYLE
|
||||
wx.Dialog.__init__(self, *args, **kwds)
|
||||
self.label_2 = wx.StaticText(self, -1, "Port")
|
||||
self.choice_port = wx.Choice(self, -1, choices=[])
|
||||
self.label_1 = wx.StaticText(self, -1, "Baudrate")
|
||||
self.combo_box_baudrate = wx.ComboBox(self, -1, choices=[], style=wx.CB_DROPDOWN)
|
||||
self.sizer_1_staticbox = wx.StaticBox(self, -1, "Basics")
|
||||
self.panel_format = wx.Panel(self, -1)
|
||||
self.label_3 = wx.StaticText(self.panel_format, -1, "Data Bits")
|
||||
self.choice_databits = wx.Choice(self.panel_format, -1, choices=["choice 1"])
|
||||
self.label_4 = wx.StaticText(self.panel_format, -1, "Stop Bits")
|
||||
self.choice_stopbits = wx.Choice(self.panel_format, -1, choices=["choice 1"])
|
||||
self.label_5 = wx.StaticText(self.panel_format, -1, "Parity")
|
||||
self.choice_parity = wx.Choice(self.panel_format, -1, choices=["choice 1"])
|
||||
self.sizer_format_staticbox = wx.StaticBox(self.panel_format, -1, "Data Format")
|
||||
self.panel_timeout = wx.Panel(self, -1)
|
||||
self.checkbox_timeout = wx.CheckBox(self.panel_timeout, -1, "Use Timeout")
|
||||
self.text_ctrl_timeout = wx.TextCtrl(self.panel_timeout, -1, "")
|
||||
self.label_6 = wx.StaticText(self.panel_timeout, -1, "seconds")
|
||||
self.sizer_timeout_staticbox = wx.StaticBox(self.panel_timeout, -1, "Timeout")
|
||||
self.panel_flow = wx.Panel(self, -1)
|
||||
self.checkbox_rtscts = wx.CheckBox(self.panel_flow, -1, "RTS/CTS")
|
||||
self.checkbox_xonxoff = wx.CheckBox(self.panel_flow, -1, "Xon/Xoff")
|
||||
self.sizer_flow_staticbox = wx.StaticBox(self.panel_flow, -1, "Flow Control")
|
||||
self.button_ok = wx.Button(self, wx.ID_OK, "")
|
||||
self.button_cancel = wx.Button(self, wx.ID_CANCEL, "")
|
||||
|
||||
self.__set_properties()
|
||||
self.__do_layout()
|
||||
# end wxGlade
|
||||
# attach the event handlers
|
||||
self.__attach_events()
|
||||
|
||||
def __set_properties(self):
|
||||
# begin wxGlade: SerialConfigDialog.__set_properties
|
||||
self.SetTitle("Serial Port Configuration")
|
||||
self.choice_databits.SetSelection(0)
|
||||
self.choice_stopbits.SetSelection(0)
|
||||
self.choice_parity.SetSelection(0)
|
||||
self.text_ctrl_timeout.Enable(False)
|
||||
self.button_ok.SetDefault()
|
||||
# end wxGlade
|
||||
self.SetTitle("Serial Port Configuration")
|
||||
if self.show & SHOW_TIMEOUT:
|
||||
self.text_ctrl_timeout.Enable(0)
|
||||
self.button_ok.SetDefault()
|
||||
|
||||
if not self.show & SHOW_BAUDRATE:
|
||||
self.label_1.Hide()
|
||||
self.combo_box_baudrate.Hide()
|
||||
if not self.show & SHOW_FORMAT:
|
||||
self.panel_format.Hide()
|
||||
if not self.show & SHOW_TIMEOUT:
|
||||
self.panel_timeout.Hide()
|
||||
if not self.show & SHOW_FLOW:
|
||||
self.panel_flow.Hide()
|
||||
|
||||
# fill in ports and select current setting
|
||||
preferred_index = 0
|
||||
self.choice_port.Clear()
|
||||
self.ports = []
|
||||
for n, (portname, desc, hwid) in enumerate(sorted(serial.tools.list_ports.comports())):
|
||||
self.choice_port.Append(u'{} - {}'.format(portname, desc))
|
||||
self.ports.append(portname)
|
||||
if self.serial.name == portname:
|
||||
preferred_index = n
|
||||
self.choice_port.SetSelection(preferred_index)
|
||||
if self.show & SHOW_BAUDRATE:
|
||||
preferred_index = None
|
||||
# fill in baud rates and select current setting
|
||||
self.combo_box_baudrate.Clear()
|
||||
for n, baudrate in enumerate(self.serial.BAUDRATES):
|
||||
self.combo_box_baudrate.Append(str(baudrate))
|
||||
if self.serial.baudrate == baudrate:
|
||||
preferred_index = n
|
||||
if preferred_index is not None:
|
||||
self.combo_box_baudrate.SetSelection(preferred_index)
|
||||
else:
|
||||
self.combo_box_baudrate.SetValue(u'{}'.format(self.serial.baudrate))
|
||||
if self.show & SHOW_FORMAT:
|
||||
# fill in data bits and select current setting
|
||||
self.choice_databits.Clear()
|
||||
for n, bytesize in enumerate(self.serial.BYTESIZES):
|
||||
self.choice_databits.Append(str(bytesize))
|
||||
if self.serial.bytesize == bytesize:
|
||||
index = n
|
||||
self.choice_databits.SetSelection(index)
|
||||
# fill in stop bits and select current setting
|
||||
self.choice_stopbits.Clear()
|
||||
for n, stopbits in enumerate(self.serial.STOPBITS):
|
||||
self.choice_stopbits.Append(str(stopbits))
|
||||
if self.serial.stopbits == stopbits:
|
||||
index = n
|
||||
self.choice_stopbits.SetSelection(index)
|
||||
# fill in parities and select current setting
|
||||
self.choice_parity.Clear()
|
||||
for n, parity in enumerate(self.serial.PARITIES):
|
||||
self.choice_parity.Append(str(serial.PARITY_NAMES[parity]))
|
||||
if self.serial.parity == parity:
|
||||
index = n
|
||||
self.choice_parity.SetSelection(index)
|
||||
if self.show & SHOW_TIMEOUT:
|
||||
# set the timeout mode and value
|
||||
if self.serial.timeout is None:
|
||||
self.checkbox_timeout.SetValue(False)
|
||||
self.text_ctrl_timeout.Enable(False)
|
||||
else:
|
||||
self.checkbox_timeout.SetValue(True)
|
||||
self.text_ctrl_timeout.Enable(True)
|
||||
self.text_ctrl_timeout.SetValue(str(self.serial.timeout))
|
||||
if self.show & SHOW_FLOW:
|
||||
# set the rtscts mode
|
||||
self.checkbox_rtscts.SetValue(self.serial.rtscts)
|
||||
# set the rtscts mode
|
||||
self.checkbox_xonxoff.SetValue(self.serial.xonxoff)
|
||||
|
||||
def __do_layout(self):
|
||||
# begin wxGlade: SerialConfigDialog.__do_layout
|
||||
sizer_2 = wx.BoxSizer(wx.VERTICAL)
|
||||
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.sizer_flow_staticbox.Lower()
|
||||
sizer_flow = wx.StaticBoxSizer(self.sizer_flow_staticbox, wx.HORIZONTAL)
|
||||
self.sizer_timeout_staticbox.Lower()
|
||||
sizer_timeout = wx.StaticBoxSizer(self.sizer_timeout_staticbox, wx.HORIZONTAL)
|
||||
self.sizer_format_staticbox.Lower()
|
||||
sizer_format = wx.StaticBoxSizer(self.sizer_format_staticbox, wx.VERTICAL)
|
||||
grid_sizer_1 = wx.FlexGridSizer(3, 2, 0, 0)
|
||||
self.sizer_1_staticbox.Lower()
|
||||
sizer_1 = wx.StaticBoxSizer(self.sizer_1_staticbox, wx.VERTICAL)
|
||||
sizer_basics = wx.FlexGridSizer(3, 2, 0, 0)
|
||||
sizer_basics.Add(self.label_2, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
sizer_basics.Add(self.choice_port, 0, wx.EXPAND, 0)
|
||||
sizer_basics.Add(self.label_1, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
sizer_basics.Add(self.combo_box_baudrate, 0, wx.EXPAND, 0)
|
||||
sizer_basics.AddGrowableCol(1)
|
||||
sizer_1.Add(sizer_basics, 0, wx.EXPAND, 0)
|
||||
sizer_2.Add(sizer_1, 0, wx.EXPAND, 0)
|
||||
grid_sizer_1.Add(self.label_3, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
grid_sizer_1.Add(self.choice_databits, 1, wx.EXPAND | wx.ALIGN_RIGHT, 0)
|
||||
grid_sizer_1.Add(self.label_4, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
grid_sizer_1.Add(self.choice_stopbits, 1, wx.EXPAND | wx.ALIGN_RIGHT, 0)
|
||||
grid_sizer_1.Add(self.label_5, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
grid_sizer_1.Add(self.choice_parity, 1, wx.EXPAND | wx.ALIGN_RIGHT, 0)
|
||||
sizer_format.Add(grid_sizer_1, 1, wx.EXPAND, 0)
|
||||
self.panel_format.SetSizer(sizer_format)
|
||||
sizer_2.Add(self.panel_format, 0, wx.EXPAND, 0)
|
||||
sizer_timeout.Add(self.checkbox_timeout, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
sizer_timeout.Add(self.text_ctrl_timeout, 0, 0, 0)
|
||||
sizer_timeout.Add(self.label_6, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
self.panel_timeout.SetSizer(sizer_timeout)
|
||||
sizer_2.Add(self.panel_timeout, 0, wx.EXPAND, 0)
|
||||
sizer_flow.Add(self.checkbox_rtscts, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
sizer_flow.Add(self.checkbox_xonxoff, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
||||
sizer_flow.Add((10, 10), 1, wx.EXPAND, 0)
|
||||
self.panel_flow.SetSizer(sizer_flow)
|
||||
sizer_2.Add(self.panel_flow, 0, wx.EXPAND, 0)
|
||||
sizer_3.Add(self.button_ok, 0, 0, 0)
|
||||
sizer_3.Add(self.button_cancel, 0, 0, 0)
|
||||
sizer_2.Add(sizer_3, 0, wx.ALL | wx.ALIGN_RIGHT, 4)
|
||||
self.SetSizer(sizer_2)
|
||||
sizer_2.Fit(self)
|
||||
self.Layout()
|
||||
# end wxGlade
|
||||
|
||||
def __attach_events(self):
|
||||
self.button_ok.Bind(wx.EVT_BUTTON, self.OnOK)
|
||||
self.button_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
|
||||
if self.show & SHOW_TIMEOUT:
|
||||
self.checkbox_timeout.Bind(wx.EVT_CHECKBOX, self.OnTimeout)
|
||||
|
||||
def OnOK(self, events):
|
||||
success = True
|
||||
self.serial.port = self.ports[self.choice_port.GetSelection()]
|
||||
if self.show & SHOW_BAUDRATE:
|
||||
try:
|
||||
b = int(self.combo_box_baudrate.GetValue())
|
||||
except ValueError:
|
||||
with wx.MessageDialog(
|
||||
self,
|
||||
'Baudrate must be a numeric value',
|
||||
'Value Error',
|
||||
wx.OK | wx.ICON_ERROR) as dlg:
|
||||
dlg.ShowModal()
|
||||
success = False
|
||||
else:
|
||||
self.serial.baudrate = b
|
||||
if self.show & SHOW_FORMAT:
|
||||
self.serial.bytesize = self.serial.BYTESIZES[self.choice_databits.GetSelection()]
|
||||
self.serial.stopbits = self.serial.STOPBITS[self.choice_stopbits.GetSelection()]
|
||||
self.serial.parity = self.serial.PARITIES[self.choice_parity.GetSelection()]
|
||||
if self.show & SHOW_FLOW:
|
||||
self.serial.rtscts = self.checkbox_rtscts.GetValue()
|
||||
self.serial.xonxoff = self.checkbox_xonxoff.GetValue()
|
||||
if self.show & SHOW_TIMEOUT:
|
||||
if self.checkbox_timeout.GetValue():
|
||||
try:
|
||||
self.serial.timeout = float(self.text_ctrl_timeout.GetValue())
|
||||
except ValueError:
|
||||
with wx.MessageDialog(
|
||||
self,
|
||||
'Timeout must be a numeric value',
|
||||
'Value Error',
|
||||
wx.OK | wx.ICON_ERROR) as dlg:
|
||||
dlg.ShowModal()
|
||||
success = False
|
||||
else:
|
||||
self.serial.timeout = None
|
||||
if success:
|
||||
self.EndModal(wx.ID_OK)
|
||||
|
||||
def OnCancel(self, events):
|
||||
self.EndModal(wx.ID_CANCEL)
|
||||
|
||||
def OnTimeout(self, events):
|
||||
if self.checkbox_timeout.GetValue():
|
||||
self.text_ctrl_timeout.Enable(True)
|
||||
else:
|
||||
self.text_ctrl_timeout.Enable(False)
|
||||
|
||||
# end of class SerialConfigDialog
|
||||
|
||||
|
||||
class MyApp(wx.App):
|
||||
"""Test code"""
|
||||
def OnInit(self):
|
||||
wx.InitAllImageHandlers()
|
||||
|
||||
ser = serial.Serial()
|
||||
print(ser)
|
||||
# loop until cancel is pressed, old values are used as start for the next run
|
||||
# show the different views, one after the other
|
||||
# value are kept.
|
||||
for flags in (SHOW_BAUDRATE, SHOW_FLOW, SHOW_FORMAT, SHOW_TIMEOUT, SHOW_ALL):
|
||||
dialog_serial_cfg = SerialConfigDialog(None, -1, "", serial=ser, show=flags)
|
||||
self.SetTopWindow(dialog_serial_cfg)
|
||||
result = dialog_serial_cfg.ShowModal()
|
||||
print(ser)
|
||||
if result != wx.ID_OK:
|
||||
break
|
||||
# the user can play around with the values, CANCEL aborts the loop
|
||||
while True:
|
||||
dialog_serial_cfg = SerialConfigDialog(None, -1, "", serial=ser)
|
||||
self.SetTopWindow(dialog_serial_cfg)
|
||||
result = dialog_serial_cfg.ShowModal()
|
||||
print(ser)
|
||||
if result != wx.ID_OK:
|
||||
break
|
||||
return 0
|
||||
|
||||
# end of class MyApp
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = MyApp(0)
|
||||
app.MainLoop()
|
||||
@@ -1,252 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- generated by wxGlade 0.6.5 on Wed Oct 14 18:12:11 2015 -->
|
||||
|
||||
<application path="wxSerialConfigDialog.py" name="app" class="MyApp" option="0" language="python" top_window="dialog_serial_cfg" encoding="ISO-8859-1" use_gettext="0" overwrite="0" use_new_namespace="1" for_version="2.8" is_template="0" indent_amount="4" indent_symbol="space" source_extension=".cpp" header_extension=".h">
|
||||
<object class="SerialConfigDialog" name="dialog_serial_cfg" base="EditDialog">
|
||||
<style>wxDEFAULT_DIALOG_STYLE</style>
|
||||
<title>Serial Port Configuration</title>
|
||||
<object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxStaticBoxSizer" name="sizer_1" base="EditStaticBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<label>Basics</label>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxFlexGridSizer" name="sizer_basics" base="EditFlexGridSizer">
|
||||
<hgap>0</hgap>
|
||||
<rows>3</rows>
|
||||
<growable_cols>1</growable_cols>
|
||||
<cols>2</cols>
|
||||
<vgap>0</vgap>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxStaticText" name="label_2" base="EditStaticText">
|
||||
<attribute>1</attribute>
|
||||
<label>Port</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxChoice" name="choice_port" base="EditChoice">
|
||||
<selection>0</selection>
|
||||
<choices>
|
||||
</choices>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxStaticText" name="label_1" base="EditStaticText">
|
||||
<attribute>1</attribute>
|
||||
<label>Baudrate</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxComboBox" name="combo_box_baudrate" base="EditComboBox">
|
||||
<selection>-1</selection>
|
||||
<choices>
|
||||
</choices>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxPanel" name="panel_format" base="EditPanel">
|
||||
<style>wxTAB_TRAVERSAL</style>
|
||||
<object class="wxStaticBoxSizer" name="sizer_format" base="EditStaticBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<label>Data Format</label>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>1</option>
|
||||
<object class="wxFlexGridSizer" name="grid_sizer_1" base="EditFlexGridSizer">
|
||||
<hgap>0</hgap>
|
||||
<rows>3</rows>
|
||||
<cols>2</cols>
|
||||
<vgap>0</vgap>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>1</option>
|
||||
<object class="wxStaticText" name="label_3" base="EditStaticText">
|
||||
<attribute>1</attribute>
|
||||
<label>Data Bits</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND|wxALIGN_RIGHT</flag>
|
||||
<border>0</border>
|
||||
<option>1</option>
|
||||
<object class="wxChoice" name="choice_databits" base="EditChoice">
|
||||
<selection>0</selection>
|
||||
<choices>
|
||||
<choice>choice 1</choice>
|
||||
</choices>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>1</option>
|
||||
<object class="wxStaticText" name="label_4" base="EditStaticText">
|
||||
<attribute>1</attribute>
|
||||
<label>Stop Bits</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND|wxALIGN_RIGHT</flag>
|
||||
<border>0</border>
|
||||
<option>1</option>
|
||||
<object class="wxChoice" name="choice_stopbits" base="EditChoice">
|
||||
<selection>0</selection>
|
||||
<choices>
|
||||
<choice>choice 1</choice>
|
||||
</choices>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>1</option>
|
||||
<object class="wxStaticText" name="label_5" base="EditStaticText">
|
||||
<attribute>1</attribute>
|
||||
<label>Parity</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND|wxALIGN_RIGHT</flag>
|
||||
<border>0</border>
|
||||
<option>1</option>
|
||||
<object class="wxChoice" name="choice_parity" base="EditChoice">
|
||||
<selection>0</selection>
|
||||
<choices>
|
||||
<choice>choice 1</choice>
|
||||
</choices>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxPanel" name="panel_timeout" base="EditPanel">
|
||||
<style>wxTAB_TRAVERSAL</style>
|
||||
<object class="wxStaticBoxSizer" name="sizer_timeout" base="EditStaticBoxSizer">
|
||||
<orient>wxHORIZONTAL</orient>
|
||||
<label>Timeout</label>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxCheckBox" name="checkbox_timeout" base="EditCheckBox">
|
||||
<label>Use Timeout</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxTextCtrl" name="text_ctrl_timeout" base="EditTextCtrl">
|
||||
<disabled>1</disabled>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxStaticText" name="label_6" base="EditStaticText">
|
||||
<attribute>1</attribute>
|
||||
<label>seconds</label>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxPanel" name="panel_flow" base="EditPanel">
|
||||
<style>wxTAB_TRAVERSAL</style>
|
||||
<object class="wxStaticBoxSizer" name="sizer_flow" base="EditStaticBoxSizer">
|
||||
<orient>wxHORIZONTAL</orient>
|
||||
<label>Flow Control</label>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxCheckBox" name="checkbox_rtscts" base="EditCheckBox">
|
||||
<label>RTS/CTS</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxCheckBox" name="checkbox_xonxoff" base="EditCheckBox">
|
||||
<label>Xon/Xoff</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>1</option>
|
||||
<object class="spacer" name="spacer" base="EditSpacer">
|
||||
<height>10</height>
|
||||
<width>10</width>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_RIGHT</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxBoxSizer" name="sizer_3" base="EditBoxSizer">
|
||||
<orient>wxHORIZONTAL</orient>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxButton" name="button_ok" base="EditButton">
|
||||
<stockitem>OK</stockitem>
|
||||
<default>1</default>
|
||||
<label>&OK</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxButton" name="button_cancel" base="EditButton">
|
||||
<stockitem>CANCEL</stockitem>
|
||||
<label>&Cancel</label>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</application>
|
||||
@@ -1,367 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A simple terminal application with wxPython.
|
||||
#
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import codecs
|
||||
from serial.tools.miniterm import unichr
|
||||
import serial
|
||||
import threading
|
||||
import wx
|
||||
import wx.lib.newevent
|
||||
import wxSerialConfigDialog
|
||||
|
||||
try:
|
||||
unichr
|
||||
except NameError:
|
||||
unichr = chr
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Create an own event type, so that GUI updates can be delegated
|
||||
# this is required as on some platforms only the main thread can
|
||||
# access the GUI without crashing. wxMutexGuiEnter/wxMutexGuiLeave
|
||||
# could be used too, but an event is more elegant.
|
||||
|
||||
SerialRxEvent, EVT_SERIALRX = wx.lib.newevent.NewEvent()
|
||||
SERIALRX = wx.NewEventType()
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
ID_CLEAR = wx.NewId()
|
||||
ID_SAVEAS = wx.NewId()
|
||||
ID_SETTINGS = wx.NewId()
|
||||
ID_TERM = wx.NewId()
|
||||
ID_EXIT = wx.NewId()
|
||||
ID_RTS = wx.NewId()
|
||||
ID_DTR = wx.NewId()
|
||||
|
||||
NEWLINE_CR = 0
|
||||
NEWLINE_LF = 1
|
||||
NEWLINE_CRLF = 2
|
||||
|
||||
|
||||
class TerminalSetup:
|
||||
"""
|
||||
Placeholder for various terminal settings. Used to pass the
|
||||
options to the TerminalSettingsDialog.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.echo = False
|
||||
self.unprintable = False
|
||||
self.newline = NEWLINE_CRLF
|
||||
|
||||
|
||||
class TerminalSettingsDialog(wx.Dialog):
|
||||
"""Simple dialog with common terminal settings like echo, newline mode."""
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.settings = kwds['settings']
|
||||
del kwds['settings']
|
||||
# begin wxGlade: TerminalSettingsDialog.__init__
|
||||
kwds["style"] = wx.DEFAULT_DIALOG_STYLE
|
||||
wx.Dialog.__init__(self, *args, **kwds)
|
||||
self.checkbox_echo = wx.CheckBox(self, -1, "Local Echo")
|
||||
self.checkbox_unprintable = wx.CheckBox(self, -1, "Show unprintable characters")
|
||||
self.radio_box_newline = wx.RadioBox(self, -1, "Newline Handling", choices=["CR only", "LF only", "CR+LF"], majorDimension=0, style=wx.RA_SPECIFY_ROWS)
|
||||
self.sizer_4_staticbox = wx.StaticBox(self, -1, "Input/Output")
|
||||
self.button_ok = wx.Button(self, wx.ID_OK, "")
|
||||
self.button_cancel = wx.Button(self, wx.ID_CANCEL, "")
|
||||
|
||||
self.__set_properties()
|
||||
self.__do_layout()
|
||||
# end wxGlade
|
||||
self.__attach_events()
|
||||
self.checkbox_echo.SetValue(self.settings.echo)
|
||||
self.checkbox_unprintable.SetValue(self.settings.unprintable)
|
||||
self.radio_box_newline.SetSelection(self.settings.newline)
|
||||
|
||||
def __set_properties(self):
|
||||
# begin wxGlade: TerminalSettingsDialog.__set_properties
|
||||
self.SetTitle("Terminal Settings")
|
||||
self.radio_box_newline.SetSelection(0)
|
||||
self.button_ok.SetDefault()
|
||||
# end wxGlade
|
||||
|
||||
def __do_layout(self):
|
||||
# begin wxGlade: TerminalSettingsDialog.__do_layout
|
||||
sizer_2 = wx.BoxSizer(wx.VERTICAL)
|
||||
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.sizer_4_staticbox.Lower()
|
||||
sizer_4 = wx.StaticBoxSizer(self.sizer_4_staticbox, wx.VERTICAL)
|
||||
sizer_4.Add(self.checkbox_echo, 0, wx.ALL, 4)
|
||||
sizer_4.Add(self.checkbox_unprintable, 0, wx.ALL, 4)
|
||||
sizer_4.Add(self.radio_box_newline, 0, 0, 0)
|
||||
sizer_2.Add(sizer_4, 0, wx.EXPAND, 0)
|
||||
sizer_3.Add(self.button_ok, 0, 0, 0)
|
||||
sizer_3.Add(self.button_cancel, 0, 0, 0)
|
||||
sizer_2.Add(sizer_3, 0, wx.ALL | wx.ALIGN_RIGHT, 4)
|
||||
self.SetSizer(sizer_2)
|
||||
sizer_2.Fit(self)
|
||||
self.Layout()
|
||||
# end wxGlade
|
||||
|
||||
def __attach_events(self):
|
||||
self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.button_ok.GetId())
|
||||
self.Bind(wx.EVT_BUTTON, self.OnCancel, id=self.button_cancel.GetId())
|
||||
|
||||
def OnOK(self, events):
|
||||
"""Update data with new values and close dialog."""
|
||||
self.settings.echo = self.checkbox_echo.GetValue()
|
||||
self.settings.unprintable = self.checkbox_unprintable.GetValue()
|
||||
self.settings.newline = self.radio_box_newline.GetSelection()
|
||||
self.EndModal(wx.ID_OK)
|
||||
|
||||
def OnCancel(self, events):
|
||||
"""Do not update data but close dialog."""
|
||||
self.EndModal(wx.ID_CANCEL)
|
||||
|
||||
# end of class TerminalSettingsDialog
|
||||
|
||||
|
||||
class TerminalFrame(wx.Frame):
|
||||
"""Simple terminal program for wxPython"""
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.serial = serial.Serial()
|
||||
self.serial.timeout = 0.5 # make sure that the alive event can be checked from time to time
|
||||
self.settings = TerminalSetup() # placeholder for the settings
|
||||
self.thread = None
|
||||
self.alive = threading.Event()
|
||||
# begin wxGlade: TerminalFrame.__init__
|
||||
kwds["style"] = wx.DEFAULT_FRAME_STYLE
|
||||
wx.Frame.__init__(self, *args, **kwds)
|
||||
|
||||
# Menu Bar
|
||||
self.frame_terminal_menubar = wx.MenuBar()
|
||||
wxglade_tmp_menu = wx.Menu()
|
||||
wxglade_tmp_menu.Append(ID_CLEAR, "&Clear", "", wx.ITEM_NORMAL)
|
||||
wxglade_tmp_menu.Append(ID_SAVEAS, "&Save Text As...", "", wx.ITEM_NORMAL)
|
||||
wxglade_tmp_menu.AppendSeparator()
|
||||
wxglade_tmp_menu.Append(ID_TERM, "&Terminal Settings...", "", wx.ITEM_NORMAL)
|
||||
wxglade_tmp_menu.AppendSeparator()
|
||||
wxglade_tmp_menu.Append(ID_EXIT, "&Exit", "", wx.ITEM_NORMAL)
|
||||
self.frame_terminal_menubar.Append(wxglade_tmp_menu, "&File")
|
||||
wxglade_tmp_menu = wx.Menu()
|
||||
wxglade_tmp_menu.Append(ID_RTS, "RTS", "", wx.ITEM_CHECK)
|
||||
wxglade_tmp_menu.Append(ID_DTR, "&DTR", "", wx.ITEM_CHECK)
|
||||
wxglade_tmp_menu.Append(ID_SETTINGS, "&Port Settings...", "", wx.ITEM_NORMAL)
|
||||
self.frame_terminal_menubar.Append(wxglade_tmp_menu, "Serial Port")
|
||||
self.SetMenuBar(self.frame_terminal_menubar)
|
||||
# Menu Bar end
|
||||
self.text_ctrl_output = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE | wx.TE_READONLY)
|
||||
|
||||
self.__set_properties()
|
||||
self.__do_layout()
|
||||
|
||||
self.Bind(wx.EVT_MENU, self.OnClear, id=ID_CLEAR)
|
||||
self.Bind(wx.EVT_MENU, self.OnSaveAs, id=ID_SAVEAS)
|
||||
self.Bind(wx.EVT_MENU, self.OnTermSettings, id=ID_TERM)
|
||||
self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT)
|
||||
self.Bind(wx.EVT_MENU, self.OnRTS, id=ID_RTS)
|
||||
self.Bind(wx.EVT_MENU, self.OnDTR, id=ID_DTR)
|
||||
self.Bind(wx.EVT_MENU, self.OnPortSettings, id=ID_SETTINGS)
|
||||
# end wxGlade
|
||||
self.__attach_events() # register events
|
||||
self.OnPortSettings(None) # call setup dialog on startup, opens port
|
||||
if not self.alive.is_set():
|
||||
self.Close()
|
||||
|
||||
def StartThread(self):
|
||||
"""Start the receiver thread"""
|
||||
self.thread = threading.Thread(target=self.ComPortThread)
|
||||
self.thread.daemon = True
|
||||
self.alive.set()
|
||||
self.thread.start()
|
||||
self.serial.rts = True
|
||||
self.serial.dtr = True
|
||||
self.frame_terminal_menubar.Check(ID_RTS, self.serial.rts)
|
||||
self.frame_terminal_menubar.Check(ID_DTR, self.serial.dtr)
|
||||
|
||||
def StopThread(self):
|
||||
"""Stop the receiver thread, wait until it's finished."""
|
||||
if self.thread is not None:
|
||||
self.alive.clear() # clear alive event for thread
|
||||
self.thread.join() # wait until thread has finished
|
||||
self.thread = None
|
||||
|
||||
def __set_properties(self):
|
||||
# begin wxGlade: TerminalFrame.__set_properties
|
||||
self.SetTitle("Serial Terminal")
|
||||
self.SetSize((546, 383))
|
||||
self.text_ctrl_output.SetFont(wx.Font(9, wx.MODERN, wx.NORMAL, wx.NORMAL, 0, ""))
|
||||
# end wxGlade
|
||||
|
||||
def __do_layout(self):
|
||||
# begin wxGlade: TerminalFrame.__do_layout
|
||||
sizer_1 = wx.BoxSizer(wx.VERTICAL)
|
||||
sizer_1.Add(self.text_ctrl_output, 1, wx.EXPAND, 0)
|
||||
self.SetSizer(sizer_1)
|
||||
self.Layout()
|
||||
# end wxGlade
|
||||
|
||||
def __attach_events(self):
|
||||
# register events at the controls
|
||||
self.Bind(wx.EVT_MENU, self.OnClear, id=ID_CLEAR)
|
||||
self.Bind(wx.EVT_MENU, self.OnSaveAs, id=ID_SAVEAS)
|
||||
self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT)
|
||||
self.Bind(wx.EVT_MENU, self.OnPortSettings, id=ID_SETTINGS)
|
||||
self.Bind(wx.EVT_MENU, self.OnTermSettings, id=ID_TERM)
|
||||
self.text_ctrl_output.Bind(wx.EVT_CHAR, self.OnKey)
|
||||
self.Bind(wx.EVT_CHAR_HOOK, self.OnKey)
|
||||
self.Bind(EVT_SERIALRX, self.OnSerialRead)
|
||||
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
||||
|
||||
def OnExit(self, event): # wxGlade: TerminalFrame.<event_handler>
|
||||
"""Menu point Exit"""
|
||||
self.Close()
|
||||
|
||||
def OnClose(self, event):
|
||||
"""Called on application shutdown."""
|
||||
self.StopThread() # stop reader thread
|
||||
self.serial.close() # cleanup
|
||||
self.Destroy() # close windows, exit app
|
||||
|
||||
def OnSaveAs(self, event): # wxGlade: TerminalFrame.<event_handler>
|
||||
"""Save contents of output window."""
|
||||
with wx.FileDialog(
|
||||
None,
|
||||
"Save Text As...",
|
||||
".",
|
||||
"",
|
||||
"Text File|*.txt|All Files|*",
|
||||
wx.SAVE) as dlg:
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
filename = dlg.GetPath()
|
||||
with codecs.open(filename, 'w', encoding='utf-8') as f:
|
||||
text = self.text_ctrl_output.GetValue().encode("utf-8")
|
||||
f.write(text)
|
||||
|
||||
def OnClear(self, event): # wxGlade: TerminalFrame.<event_handler>
|
||||
"""Clear contents of output window."""
|
||||
self.text_ctrl_output.Clear()
|
||||
|
||||
def OnPortSettings(self, event): # wxGlade: TerminalFrame.<event_handler>
|
||||
"""
|
||||
Show the port settings dialog. The reader thread is stopped for the
|
||||
settings change.
|
||||
"""
|
||||
if event is not None: # will be none when called on startup
|
||||
self.StopThread()
|
||||
self.serial.close()
|
||||
ok = False
|
||||
while not ok:
|
||||
with wxSerialConfigDialog.SerialConfigDialog(
|
||||
self,
|
||||
-1,
|
||||
"",
|
||||
show=wxSerialConfigDialog.SHOW_BAUDRATE | wxSerialConfigDialog.SHOW_FORMAT | wxSerialConfigDialog.SHOW_FLOW,
|
||||
serial=self.serial) as dialog_serial_cfg:
|
||||
dialog_serial_cfg.CenterOnParent()
|
||||
result = dialog_serial_cfg.ShowModal()
|
||||
# open port if not called on startup, open it on startup and OK too
|
||||
if result == wx.ID_OK or event is not None:
|
||||
try:
|
||||
self.serial.open()
|
||||
except serial.SerialException as e:
|
||||
with wx.MessageDialog(self, str(e), "Serial Port Error", wx.OK | wx.ICON_ERROR)as dlg:
|
||||
dlg.ShowModal()
|
||||
else:
|
||||
self.StartThread()
|
||||
self.SetTitle("Serial Terminal on {} [{},{},{},{}{}{}]".format(
|
||||
self.serial.portstr,
|
||||
self.serial.baudrate,
|
||||
self.serial.bytesize,
|
||||
self.serial.parity,
|
||||
self.serial.stopbits,
|
||||
' RTS/CTS' if self.serial.rtscts else '',
|
||||
' Xon/Xoff' if self.serial.xonxoff else '',
|
||||
))
|
||||
ok = True
|
||||
else:
|
||||
# on startup, dialog aborted
|
||||
self.alive.clear()
|
||||
ok = True
|
||||
|
||||
def OnTermSettings(self, event): # wxGlade: TerminalFrame.<event_handler>
|
||||
"""\
|
||||
Menu point Terminal Settings. Show the settings dialog
|
||||
with the current terminal settings.
|
||||
"""
|
||||
with TerminalSettingsDialog(self, -1, "", settings=self.settings) as dialog:
|
||||
dialog.CenterOnParent()
|
||||
dialog.ShowModal()
|
||||
|
||||
def OnKey(self, event):
|
||||
"""\
|
||||
Key event handler. If the key is in the ASCII range, write it to the
|
||||
serial port. Newline handling and local echo is also done here.
|
||||
"""
|
||||
code = event.GetUnicodeKey()
|
||||
# if code < 256: # XXX bug in some versions of wx returning only capital letters
|
||||
# code = event.GetKeyCode()
|
||||
if code == 13: # is it a newline? (check for CR which is the RETURN key)
|
||||
if self.settings.echo: # do echo if needed
|
||||
self.text_ctrl_output.AppendText('\n')
|
||||
if self.settings.newline == NEWLINE_CR:
|
||||
self.serial.write(b'\r') # send CR
|
||||
elif self.settings.newline == NEWLINE_LF:
|
||||
self.serial.write(b'\n') # send LF
|
||||
elif self.settings.newline == NEWLINE_CRLF:
|
||||
self.serial.write(b'\r\n') # send CR+LF
|
||||
else:
|
||||
char = unichr(code)
|
||||
if self.settings.echo: # do echo if needed
|
||||
self.WriteText(char)
|
||||
self.serial.write(char.encode('UTF-8', 'replace')) # send the character
|
||||
event.StopPropagation()
|
||||
|
||||
def WriteText(self, text):
|
||||
if self.settings.unprintable:
|
||||
text = ''.join([c if (c >= ' ' and c != '\x7f') else unichr(0x2400 + ord(c)) for c in text])
|
||||
self.text_ctrl_output.AppendText(text)
|
||||
|
||||
def OnSerialRead(self, event):
|
||||
"""Handle input from the serial port."""
|
||||
self.WriteText(event.data.decode('UTF-8', 'replace'))
|
||||
|
||||
def ComPortThread(self):
|
||||
"""\
|
||||
Thread that handles the incoming traffic. Does the basic input
|
||||
transformation (newlines) and generates an SerialRxEvent
|
||||
"""
|
||||
while self.alive.is_set():
|
||||
b = self.serial.read(self.serial.in_waiting or 1)
|
||||
if b:
|
||||
# newline transformation
|
||||
if self.settings.newline == NEWLINE_CR:
|
||||
b = b.replace(b'\r', b'\n')
|
||||
elif self.settings.newline == NEWLINE_LF:
|
||||
pass
|
||||
elif self.settings.newline == NEWLINE_CRLF:
|
||||
b = b.replace(b'\r\n', b'\n')
|
||||
wx.PostEvent(self, SerialRxEvent(data=b))
|
||||
|
||||
def OnRTS(self, event): # wxGlade: TerminalFrame.<event_handler>
|
||||
self.serial.rts = event.IsChecked()
|
||||
|
||||
def OnDTR(self, event): # wxGlade: TerminalFrame.<event_handler>
|
||||
self.serial.dtr = event.IsChecked()
|
||||
|
||||
# end of class TerminalFrame
|
||||
|
||||
|
||||
class MyApp(wx.App):
|
||||
def OnInit(self):
|
||||
frame_terminal = TerminalFrame(None, -1, "")
|
||||
self.SetTopWindow(frame_terminal)
|
||||
frame_terminal.Show(True)
|
||||
return 1
|
||||
|
||||
# end of class MyApp
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = MyApp(0)
|
||||
app.MainLoop()
|
||||
@@ -1,154 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- generated by wxGlade 0.6.5 on Wed Oct 14 18:12:48 2015 -->
|
||||
|
||||
<application path="wxTerminal.py" name="app" class="MyApp" option="0" language="python" top_window="frame_terminal" encoding="ISO-8859-1" use_gettext="0" overwrite="0" use_new_namespace="1" for_version="2.8" is_template="0" indent_amount="4" indent_symbol="space" source_extension=".cpp" header_extension=".h">
|
||||
<object class="TerminalFrame" name="frame_terminal" base="EditFrame">
|
||||
<style>wxDEFAULT_FRAME_STYLE</style>
|
||||
<title>Serial Terminal</title>
|
||||
<menubar>1</menubar>
|
||||
<size>546, 383</size>
|
||||
<object class="wxMenuBar" name="frame_terminal_menubar" base="EditMenuBar">
|
||||
<menus>
|
||||
<menu name="" label="&File">
|
||||
<item>
|
||||
<label>&Clear</label>
|
||||
<id>ID_CLEAR</id>
|
||||
<handler>OnClear</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>&Save Text As...</label>
|
||||
<id>ID_SAVEAS</id>
|
||||
<handler>OnSaveAs</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>---</label>
|
||||
<id>---</id>
|
||||
<name>---</name>
|
||||
</item>
|
||||
<item>
|
||||
<label>&Terminal Settings...</label>
|
||||
<id>ID_TERM</id>
|
||||
<handler>OnTermSettings</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>---</label>
|
||||
<name>---</name>
|
||||
</item>
|
||||
<item>
|
||||
<label>&Exit</label>
|
||||
<id>ID_EXIT</id>
|
||||
<handler>OnExit</handler>
|
||||
</item>
|
||||
</menu>
|
||||
<menu name="" label="Serial Port">
|
||||
<item>
|
||||
<label>RTS</label>
|
||||
<id>ID_RTS</id>
|
||||
<checkable>1</checkable>
|
||||
<handler>OnRTS</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>&DTR</label>
|
||||
<id>ID_DTR</id>
|
||||
<checkable>1</checkable>
|
||||
<handler>OnDTR</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>&Port Settings...</label>
|
||||
<id>ID_SETTINGS</id>
|
||||
<handler>OnPortSettings</handler>
|
||||
</item>
|
||||
</menu>
|
||||
</menus>
|
||||
</object>
|
||||
<object class="wxBoxSizer" name="sizer_1" base="EditBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>1</option>
|
||||
<object class="wxTextCtrl" name="text_ctrl_output" base="EditTextCtrl">
|
||||
<style>wxTE_MULTILINE|wxTE_READONLY</style>
|
||||
<font>
|
||||
<size>9</size>
|
||||
<family>modern</family>
|
||||
<style>normal</style>
|
||||
<weight>normal</weight>
|
||||
<underlined>0</underlined>
|
||||
<face></face>
|
||||
</font>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="TerminalSettingsDialog" name="dialog_terminal_Settings" base="EditDialog">
|
||||
<style>wxDEFAULT_DIALOG_STYLE</style>
|
||||
<title>Terminal Settings</title>
|
||||
<object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxStaticBoxSizer" name="sizer_4" base="EditStaticBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<label>Input/Output</label>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxCheckBox" name="checkbox_echo" base="EditCheckBox">
|
||||
<label>Local Echo</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxCheckBox" name="checkbox_unprintable" base="EditCheckBox">
|
||||
<label>Show unprintable characters</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxRadioBox" name="radio_box_newline" base="EditRadioBox">
|
||||
<style>wxRA_SPECIFY_ROWS</style>
|
||||
<selection>0</selection>
|
||||
<dimension>0</dimension>
|
||||
<label>Newline Handling</label>
|
||||
<choices>
|
||||
<choice>CR only</choice>
|
||||
<choice>LF only</choice>
|
||||
<choice>CR+LF</choice>
|
||||
</choices>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_RIGHT</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxBoxSizer" name="sizer_3" base="EditBoxSizer">
|
||||
<orient>wxHORIZONTAL</orient>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxButton" name="button_ok" base="EditButton">
|
||||
<stockitem>OK</stockitem>
|
||||
<default>1</default>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxButton" name="button_cancel" base="EditButton">
|
||||
<stockitem>CANCEL</stockitem>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</application>
|
||||
@@ -1,378 +0,0 @@
|
||||
[MASTER]
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=1
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code
|
||||
extension-pkg-whitelist=
|
||||
|
||||
# Allow optimization of some AST trees. This will activate a peephole AST
|
||||
# optimizer, which will apply various small optimizations. For instance, it can
|
||||
# be used to obtain the result of joining multiple strings with the addition
|
||||
# operator. Joining a lot of strings can lead to a maximum recursion error in
|
||||
# Pylint and this flag can prevent that. It has one side effect, the resulting
|
||||
# AST will be different than the one from reality.
|
||||
optimize-ast=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time. See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
msg-template={path}:{line}: {msg_id} {symbol}, {obj} {msg}
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=120
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
# List of optional constructs for which whitespace checking is disabled. `dict-
|
||||
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
|
||||
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
|
||||
# `empty-line` allows space-only lines.
|
||||
no-space-check=trailing-comma,dict-separator
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,input
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,j,k,ex,Run,_,n,z,c,b,e,ri,cd
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for function names
|
||||
function-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for variable names
|
||||
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct constant names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Naming hint for constant names
|
||||
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Regular expression matching correct attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for attribute names
|
||||
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct argument names
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for argument names
|
||||
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Naming hint for class attribute names
|
||||
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Regular expression matching correct inline iteration names
|
||||
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Naming hint for inline iteration names
|
||||
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Naming hint for class names
|
||||
class-name-hint=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression matching correct module names
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Naming hint for module names
|
||||
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for method names
|
||||
method-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=^_
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
|
||||
[ELIF]
|
||||
|
||||
# Maximum number of nested blocks for function / method body
|
||||
max-nested-blocks=5
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis. It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=urllib,asyncio,msvcrt,queue,socket
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamically set). This supports can work
|
||||
# with qualified names.
|
||||
ignored-classes=
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=_$|dummy
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,_cb
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=60
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=12
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
# Maximum number of boolean expressions in a if statement
|
||||
max-bool-expr=5
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,TERMIOS,Bastion,rexec
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,_fields,_replace,_source,_make
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
||||
@@ -1,7 +0,0 @@
|
||||
[bdist_wheel]
|
||||
universal=1
|
||||
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
ignore = E265, E126, E241
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
# setup.py for pySerial
|
||||
#
|
||||
# Direct install (all systems):
|
||||
# "python setup.py install"
|
||||
#
|
||||
# For Python 3.x use the corresponding Python executable,
|
||||
# e.g. "python3 setup.py ..."
|
||||
#
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
from distutils.core import setup
|
||||
|
||||
|
||||
def read(*names, **kwargs):
|
||||
"""Python 2 and Python 3 compatible text file reading.
|
||||
|
||||
Required for single-sourcing the version string.
|
||||
"""
|
||||
with io.open(
|
||||
os.path.join(os.path.dirname(__file__), *names),
|
||||
encoding=kwargs.get("encoding", "utf8")
|
||||
) as fp:
|
||||
return fp.read()
|
||||
|
||||
|
||||
def find_version(*file_paths):
|
||||
"""
|
||||
Search the file for a version string.
|
||||
|
||||
file_path contain string path components.
|
||||
|
||||
Reads the supplied Python module as text without importing it.
|
||||
"""
|
||||
version_file = read(*file_paths)
|
||||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
|
||||
version_file, re.M)
|
||||
if version_match:
|
||||
return version_match.group(1)
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
version = find_version('serial', '__init__.py')
|
||||
|
||||
|
||||
setup(
|
||||
name="pyserial",
|
||||
description="Python Serial Port Extension",
|
||||
version=version,
|
||||
author="Chris Liechti",
|
||||
author_email="cliechti@gmx.net",
|
||||
url="https://github.com/pyserial/pyserial",
|
||||
packages=['serial', 'serial.tools', 'serial.urlhandler', 'serial.threaded'],
|
||||
license="BSD",
|
||||
long_description="""\
|
||||
Python Serial Port Extension for Win32, OSX, Linux, BSD, Jython, IronPython
|
||||
|
||||
Stable:
|
||||
|
||||
- Documentation: http://pythonhosted.org/pyserial/
|
||||
- Download Page: https://pypi.python.org/pypi/pyserial
|
||||
|
||||
Latest:
|
||||
|
||||
- Documentation: http://pyserial.readthedocs.io/en/latest/
|
||||
- Project Homepage: https://github.com/pyserial/pyserial
|
||||
""",
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: POSIX',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Topic :: Communications',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Terminals :: Serial',
|
||||
],
|
||||
platforms='any',
|
||||
entry_points = {
|
||||
'console_scripts': [
|
||||
'pyserial-miniterm=serial.tools.miniterm:main',
|
||||
'pyserial-ports=serial.tools.list_ports:main'
|
||||
],
|
||||
},
|
||||
extras_require = {
|
||||
'cp2110': ['hidapi'],
|
||||
},
|
||||
)
|
||||
@@ -1,202 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Python Serial Port Extension for Win32, Linux, BSD, Jython
|
||||
# see __init__.py
|
||||
#
|
||||
# This module implements a URL dummy handler for serial_for_url.
|
||||
#
|
||||
# (C) 2011 Chris Liechti <cliechti@gmx.net>
|
||||
# this is distributed under a free software license, see license.txt
|
||||
#
|
||||
# URL format: test://
|
||||
|
||||
from serial.serialutil import *
|
||||
import time
|
||||
import socket
|
||||
import logging
|
||||
|
||||
# map log level names to constants. used in fromURL()
|
||||
LOGGER_LEVELS = {
|
||||
'debug': logging.DEBUG,
|
||||
'info': logging.INFO,
|
||||
'warning': logging.WARNING,
|
||||
'error': logging.ERROR,
|
||||
}
|
||||
|
||||
class DummySerial(SerialBase):
|
||||
"""Serial port implementation for plain sockets."""
|
||||
|
||||
def open(self):
|
||||
"""Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened."""
|
||||
self.logger = None
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
# not that there anything to configure...
|
||||
self._reconfigurePort()
|
||||
# all things set up get, now a clean start
|
||||
self._isOpen = True
|
||||
|
||||
def _reconfigurePort(self):
|
||||
"""Set communication parameters on opened port. for the test://
|
||||
protocol all settings are ignored!"""
|
||||
if self.logger:
|
||||
self.logger.info('ignored port configuration change')
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self._isOpen:
|
||||
self._isOpen = False
|
||||
|
||||
def makeDeviceName(self, port):
|
||||
raise SerialException("there is no sensible way to turn numbers into URLs")
|
||||
|
||||
def fromURL(self, url):
|
||||
"""extract host and port from an URL string"""
|
||||
if url.lower().startswith("test://"): url = url[7:]
|
||||
try:
|
||||
# is there a "path" (our options)?
|
||||
if '/' in url:
|
||||
# cut away options
|
||||
url, options = url.split('/', 1)
|
||||
# process options now, directly altering self
|
||||
for option in options.split('/'):
|
||||
if '=' in option:
|
||||
option, value = option.split('=', 1)
|
||||
else:
|
||||
value = None
|
||||
if option == 'logging':
|
||||
logging.basicConfig() # XXX is that good to call it here?
|
||||
self.logger = logging.getLogger('pySerial.test')
|
||||
self.logger.setLevel(LOGGER_LEVELS[value])
|
||||
self.logger.debug('enabled logging')
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
except ValueError as e:
|
||||
raise SerialException('expected a string in the form "[test://][option[/option...]]": {}'.format(e))
|
||||
return (host, port)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def inWaiting(self):
|
||||
"""Return the number of characters currently in the input buffer."""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
# set this one to debug as the function could be called often...
|
||||
self.logger.debug('WARNING: inWaiting returns dummy value')
|
||||
return 0 # hmmm, see comment in read()
|
||||
|
||||
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."""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
data = '123' # dummy data
|
||||
return bytes(data)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given string over the serial port. Can block if the
|
||||
connection is blocked. May raise SerialException if the connection is
|
||||
closed."""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
# nothing done
|
||||
return len(data)
|
||||
|
||||
def flushInput(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored flushInput')
|
||||
|
||||
def flushOutput(self):
|
||||
"""Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer."""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored flushOutput')
|
||||
|
||||
def sendBreak(self, duration=0.25):
|
||||
"""Send break condition. Timed, returns to idle state after given
|
||||
duration."""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored sendBreak({!r})'.format(duration))
|
||||
|
||||
def setBreak(self, level=True):
|
||||
"""Set break: Controls TXD. When active, to transmitting is
|
||||
possible."""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored setBreak({!r})'.format(level))
|
||||
|
||||
def setRTS(self, level=True):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored setRTS({!r})'.format(level))
|
||||
|
||||
def setDTR(self, level=True):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored setDTR({!r})'.format(level))
|
||||
|
||||
def getCTS(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for getCTS()')
|
||||
return True
|
||||
|
||||
def getDSR(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for getDSR()')
|
||||
return True
|
||||
|
||||
def getRI(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for getRI()')
|
||||
return False
|
||||
|
||||
def getCD(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self._isOpen: raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for getCD()')
|
||||
return True
|
||||
|
||||
# - - - platform specific - - -
|
||||
# None so far
|
||||
|
||||
|
||||
# assemble Serial class with the platform specific implementation and the base
|
||||
# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
|
||||
# library, derive from io.RawIOBase
|
||||
try:
|
||||
import io
|
||||
except ImportError:
|
||||
# classic version with our own file-like emulation
|
||||
class Serial(DummySerial, FileLike):
|
||||
pass
|
||||
else:
|
||||
# io library present
|
||||
class Serial(DummySerial, io.RawIOBase):
|
||||
pass
|
||||
|
||||
|
||||
# simple client test
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
s = Serial('test://logging=debug')
|
||||
sys.stdout.write('{}\n'.format(s))
|
||||
|
||||
sys.stdout.write("write...\n")
|
||||
s.write("hello\n")
|
||||
s.flush()
|
||||
sys.stdout.write("read: {}\n".format(s.read(5)))
|
||||
|
||||
s.close()
|
||||
@@ -1,55 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
UnitTest runner. This one searches for all files named test_*.py and collects
|
||||
all test cases from these files. Finally it runs all tests and prints a
|
||||
summary.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
import os
|
||||
|
||||
# inject local copy to avoid testing the installed version instead of the one in the repo
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
import serial # noqa
|
||||
print("Patching sys.path to test local version. Testing Version: {}".format(serial.VERSION))
|
||||
|
||||
PORT = 'loop://'
|
||||
if len(sys.argv) > 1:
|
||||
PORT = sys.argv[1]
|
||||
|
||||
# find files and the tests in them
|
||||
mainsuite = unittest.TestSuite()
|
||||
for modulename in [
|
||||
os.path.splitext(x)[0]
|
||||
for x in os.listdir(os.path.dirname(__file__) or '.')
|
||||
if x != __file__ and x.startswith("test") and x.endswith(".py")
|
||||
]:
|
||||
try:
|
||||
module = __import__(modulename)
|
||||
except ImportError:
|
||||
print("skipping {}".format(modulename))
|
||||
else:
|
||||
module.PORT = PORT
|
||||
testsuite = unittest.findTestCases(module)
|
||||
print("found {} tests in {!r}".format(testsuite.countTestCases(), modulename))
|
||||
mainsuite.addTest(testsuite)
|
||||
|
||||
verbosity = 1
|
||||
if '-v' in sys.argv[1:]:
|
||||
verbosity = 2
|
||||
print('-' * 78)
|
||||
|
||||
# run the collected tests
|
||||
testRunner = unittest.TextTestRunner(verbosity=verbosity)
|
||||
#~ testRunner = unittest.ConsoleTestRunner(verbosity=verbosity)
|
||||
result = testRunner.run(mainsuite)
|
||||
|
||||
# set exit code accordingly to test results
|
||||
sys.exit(not result.wasSuccessful())
|
||||
@@ -1,233 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Some tests for the serial module.
|
||||
Part of pyserial (http://pyserial.sf.net) (C)2001-2015 cliechti@gmx.net
|
||||
|
||||
Intended to be run on different platforms, to ensure portability of
|
||||
the code.
|
||||
|
||||
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
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
# indirection via bytearray b/c bytes(range(256)) does something else in Python 2.7
|
||||
bytes_0to255 = bytes(bytearray(range(256)))
|
||||
|
||||
|
||||
def segments(data, size=16):
|
||||
for a in range(0, len(data), size):
|
||||
yield data[a:a + size]
|
||||
|
||||
|
||||
class Test4_Nonblocking(unittest.TestCase):
|
||||
"""Test with timeouts"""
|
||||
timeout = 0
|
||||
|
||||
def setUp(self):
|
||||
self.s = serial.serial_for_url(PORT, timeout=self.timeout)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test0_Messy(self):
|
||||
"""NonBlocking (timeout=0)"""
|
||||
# this is only here to write out the message in verbose mode
|
||||
# because Test3 and Test4 print the same messages
|
||||
|
||||
def test1_ReadEmpty(self):
|
||||
"""timeout: After port open, the input buffer must be empty"""
|
||||
self.assertEqual(self.s.read(1), b'', "expected empty buffer")
|
||||
|
||||
def test2_Loopback(self):
|
||||
"""timeout: each sent character should return (binary test).
|
||||
this is also a test for the binary capability of a port."""
|
||||
for block in segments(bytes_0to255):
|
||||
length = len(block)
|
||||
self.s.write(block)
|
||||
# there might be a small delay until the character is ready (especially on win32)
|
||||
time.sleep(0.05)
|
||||
self.assertEqual(self.s.in_waiting, length, "expected exactly {} character for inWainting()".format(length))
|
||||
self.assertEqual(self.s.read(length), block) #, "expected a %r which was written before" % block)
|
||||
self.assertEqual(self.s.read(1), b'', "expected empty buffer after all sent chars are read")
|
||||
|
||||
def test2_LoopbackTimeout(self):
|
||||
"""timeout: test the timeout/immediate return.
|
||||
partial results should be returned."""
|
||||
self.s.write(b"HELLO")
|
||||
time.sleep(0.1) # there might be a small delay until the character is ready (especially on win32 and rfc2217)
|
||||
# read more characters as are available to run in the timeout
|
||||
self.assertEqual(self.s.read(10), b'HELLO', "expected the 'HELLO' which was written before")
|
||||
self.assertEqual(self.s.read(1), b'', "expected empty buffer after all sent chars are read")
|
||||
|
||||
|
||||
class Test3_Timeout(Test4_Nonblocking):
|
||||
"""Same tests as the NonBlocking ones but this time with timeout"""
|
||||
timeout = 1
|
||||
|
||||
def test0_Messy(self):
|
||||
"""Blocking (timeout=1)"""
|
||||
# this is only here to write out the message in verbose mode
|
||||
# because Test3 and Test4 print the same messages
|
||||
|
||||
|
||||
class SendEvent(threading.Thread):
|
||||
def __init__(self, serial, delay=3):
|
||||
threading.Thread.__init__(self)
|
||||
self.serial = serial
|
||||
self.delay = delay
|
||||
self.x = threading.Event()
|
||||
self.stopped = 0
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
time.sleep(self.delay)
|
||||
self.x.set()
|
||||
if not self.stopped:
|
||||
self.serial.write(b"E")
|
||||
self.serial.flush()
|
||||
|
||||
def is_set(self):
|
||||
return self.x.is_set()
|
||||
|
||||
def stop(self):
|
||||
self.stopped = 1
|
||||
self.x.wait()
|
||||
|
||||
|
||||
class Test1_Forever(unittest.TestCase):
|
||||
"""Tests a port with no timeout. These tests require that a
|
||||
character is sent after some time to stop the test, this is done
|
||||
through the SendEvent class and the Loopback HW."""
|
||||
def setUp(self):
|
||||
self.s = serial.serial_for_url(PORT, timeout=None)
|
||||
self.event = SendEvent(self.s)
|
||||
|
||||
def tearDown(self):
|
||||
self.event.stop()
|
||||
self.s.close()
|
||||
|
||||
def test2_ReadEmpty(self):
|
||||
"""no timeout: after port open, the input buffer must be empty (read).
|
||||
a character is sent after some time to terminate the test (SendEvent)."""
|
||||
c = self.s.read(1)
|
||||
if not (self.event.is_set() and c == b'E'):
|
||||
self.fail("expected marker (evt={!r}, c={!r})".format(self.event.is_set(), c))
|
||||
|
||||
|
||||
class Test2_Forever(unittest.TestCase):
|
||||
"""Tests a port with no timeout"""
|
||||
def setUp(self):
|
||||
self.s = serial.serial_for_url(PORT, timeout=None)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test1_inWaitingEmpty(self):
|
||||
"""no timeout: after port open, the input buffer must be empty (in_waiting)"""
|
||||
self.assertEqual(self.s.in_waiting, 0, "expected empty buffer")
|
||||
|
||||
def test2_Loopback(self):
|
||||
"""no timeout: each sent character should return (binary test).
|
||||
this is also a test for the binary capability of a port."""
|
||||
for block in segments(bytes_0to255):
|
||||
length = len(block)
|
||||
self.s.write(block)
|
||||
# there might be a small delay until the character is ready (especially on win32 and rfc2217)
|
||||
time.sleep(0.05)
|
||||
self.assertEqual(self.s.in_waiting, length) #, "expected exactly %d character for inWainting()" % length)
|
||||
self.assertEqual(self.s.read(length), block) #, "expected %r which was written before" % block)
|
||||
self.assertEqual(self.s.in_waiting, 0, "expected empty buffer after all sent chars are read")
|
||||
|
||||
|
||||
class Test0_DataWires(unittest.TestCase):
|
||||
"""Test modem control lines"""
|
||||
def setUp(self):
|
||||
self.s = serial.serial_for_url(PORT)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test1_RTS(self):
|
||||
"""Test RTS/CTS"""
|
||||
self.s.rts = False
|
||||
time.sleep(1.1)
|
||||
self.assertTrue(not self.s.cts, "CTS -> 0")
|
||||
self.s.rts = True
|
||||
time.sleep(1.1)
|
||||
self.assertTrue(self.s.cts, "CTS -> 1")
|
||||
|
||||
def test2_DTR(self):
|
||||
"""Test DTR/DSR"""
|
||||
self.s.dtr = False
|
||||
time.sleep(1.1)
|
||||
self.assertTrue(not self.s.dsr, "DSR -> 0")
|
||||
self.s.dtr = True
|
||||
time.sleep(1.1)
|
||||
self.assertTrue(self.s.dsr, "DSR -> 1")
|
||||
|
||||
def test3_RI(self):
|
||||
"""Test RI"""
|
||||
self.assertTrue(not self.s.ri, "RI -> 0")
|
||||
|
||||
|
||||
class Test_MoreTimeouts(unittest.TestCase):
|
||||
"""Test with timeouts"""
|
||||
def setUp(self):
|
||||
# create an closed serial port
|
||||
self.s = serial.serial_for_url(PORT, do_not_open=True)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.reset_output_buffer()
|
||||
self.s.flush()
|
||||
#~ self.s.write(serial.XON)
|
||||
self.s.close()
|
||||
# reopen... some faulty USB-serial adapter make next test fail otherwise...
|
||||
self.s.timeout = 1
|
||||
self.s.xonxoff = False
|
||||
self.s.open()
|
||||
self.s.read(3000)
|
||||
self.s.close()
|
||||
|
||||
def test_WriteTimeout(self):
|
||||
"""Test write() timeout."""
|
||||
# use xonxoff setting and the loop-back adapter to switch traffic on hold
|
||||
self.s.port = PORT
|
||||
self.s.write_timeout = 1.0
|
||||
self.s.xonxoff = True
|
||||
self.s.open()
|
||||
self.s.write(serial.XOFF)
|
||||
time.sleep(0.5) # some systems need a little delay so that they can react on XOFF
|
||||
t1 = time.time()
|
||||
self.assertRaises(serial.SerialTimeoutException, self.s.write, b"timeout please" * 200)
|
||||
t2 = time.time()
|
||||
self.assertTrue(0.9 <= (t2 - t1) < 2.1, "Timeout not in the given interval ({})".format(t2 - t1))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stdout.write(__doc__)
|
||||
if len(sys.argv) > 1:
|
||||
PORT = sys.argv[1]
|
||||
sys.stdout.write("Testing port: {!r}\n".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,161 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Some tests for the serial module.
|
||||
Part of pyserial (http://pyserial.sf.net) (C)2002 cliechti@gmx.net
|
||||
|
||||
Intended to be run on different platforms, to ensure portability of
|
||||
the code.
|
||||
|
||||
These tests open a serial port and change all the settings on the fly.
|
||||
If the port is really correctly configured cannot be determined - that
|
||||
would require external hardware or a null modem cable and an other
|
||||
serial port library... Thus it mainly tests that all features are
|
||||
correctly implemented and that the interface does what it should.
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
|
||||
class Test_ChangeAttributes(unittest.TestCase):
|
||||
"""Test with timeouts"""
|
||||
|
||||
def setUp(self):
|
||||
# create a closed serial port
|
||||
self.s = serial.serial_for_url(PORT, do_not_open=True)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test_PortSetting(self):
|
||||
self.s.port = PORT
|
||||
self.assertEqual(self.s.portstr.lower(), PORT.lower())
|
||||
# test internals
|
||||
self.assertEqual(self.s._port, PORT)
|
||||
# test on the fly change
|
||||
self.s.open()
|
||||
self.assertTrue(self.s.isOpen())
|
||||
|
||||
def test_DoubleOpen(self):
|
||||
self.s.open()
|
||||
# calling open for a second time is an error
|
||||
self.assertRaises(serial.SerialException, self.s.open)
|
||||
|
||||
def test_BaudrateSetting(self):
|
||||
self.s.open()
|
||||
for baudrate in (300, 9600, 19200, 115200):
|
||||
self.s.baudrate = baudrate
|
||||
# test get method
|
||||
self.assertEqual(self.s.baudrate, baudrate)
|
||||
# test internals
|
||||
self.assertEqual(self.s._baudrate, baudrate)
|
||||
# test illegal values
|
||||
for illegal_value in (-300, -1, 'a', None):
|
||||
self.assertRaises(ValueError, setattr, self.s, 'baudrate', illegal_value)
|
||||
|
||||
# skip this test as pyserial now tries to set even non standard baud rates.
|
||||
# therefore the test can not choose a value that fails on any system.
|
||||
def disabled_test_BaudrateSetting2(self):
|
||||
# test illegal values, depending on machine/port some of these may be valid...
|
||||
self.s.open()
|
||||
for illegal_value in (500000, 576000, 921600, 92160):
|
||||
self.assertRaises(ValueError, setattr, self.s, 'baudrate', illegal_value)
|
||||
|
||||
def test_BytesizeSetting(self):
|
||||
for bytesize in (5, 6, 7, 8):
|
||||
self.s.bytesize = bytesize
|
||||
# test get method
|
||||
self.assertEqual(self.s.bytesize, bytesize)
|
||||
# test internals
|
||||
self.assertEqual(self.s._bytesize, bytesize)
|
||||
# test illegal values
|
||||
for illegal_value in (0, 1, 3, 4, 9, 10, 'a', None):
|
||||
self.assertRaises(ValueError, setattr, self.s, 'bytesize', illegal_value)
|
||||
|
||||
def test_ParitySetting(self):
|
||||
for parity in (serial.PARITY_NONE, serial.PARITY_EVEN, serial.PARITY_ODD):
|
||||
self.s.parity = parity
|
||||
# test get method
|
||||
self.assertEqual(self.s.parity, parity)
|
||||
# test internals
|
||||
self.assertEqual(self.s._parity, parity)
|
||||
# test illegal values
|
||||
for illegal_value in (0, 57, 'a', None):
|
||||
self.assertRaises(ValueError, setattr, self.s, 'parity', illegal_value)
|
||||
|
||||
def test_StopbitsSetting(self):
|
||||
for stopbits in (1, 2):
|
||||
self.s.stopbits = stopbits
|
||||
# test get method
|
||||
self.assertEqual(self.s.stopbits, stopbits)
|
||||
# test internals
|
||||
self.assertEqual(self.s._stopbits, stopbits)
|
||||
# test illegal values
|
||||
for illegal_value in (0, 3, 2.5, 57, 'a', None):
|
||||
self.assertRaises(ValueError, setattr, self.s, 'stopbits', illegal_value)
|
||||
|
||||
def test_TimeoutSetting(self):
|
||||
for timeout in (None, 0, 1, 3.14159, 10, 1000, 3600):
|
||||
self.s.timeout = timeout
|
||||
# test get method
|
||||
self.assertEqual(self.s.timeout, timeout)
|
||||
# test internals
|
||||
self.assertEqual(self.s._timeout, timeout)
|
||||
# test illegal values
|
||||
for illegal_value in (-1, 'a'):
|
||||
self.assertRaises(ValueError, setattr, self.s, 'timeout', illegal_value)
|
||||
|
||||
def test_XonXoffSetting(self):
|
||||
for xonxoff in (True, False):
|
||||
self.s.xonxoff = xonxoff
|
||||
# test get method
|
||||
self.assertEqual(self.s.xonxoff, xonxoff)
|
||||
# test internals
|
||||
self.assertEqual(self.s._xonxoff, xonxoff)
|
||||
# no illegal values here, normal rules for the boolean value of an
|
||||
# object are used thus all objects have a truth value.
|
||||
|
||||
def test_RtsCtsSetting(self):
|
||||
for rtscts in (True, False):
|
||||
self.s.rtscts = rtscts
|
||||
# test get method
|
||||
self.assertEqual(self.s.rtscts, rtscts)
|
||||
# test internals
|
||||
self.assertEqual(self.s._rtscts, rtscts)
|
||||
# no illegal values here, normal rules for the boolean value of an
|
||||
# object are used thus all objects have a truth value.
|
||||
|
||||
# this test does not work anymore since serial_for_url that is used
|
||||
# now, already sets a port
|
||||
def disabled_test_UnconfiguredPort(self):
|
||||
# an unconfigured port cannot be opened
|
||||
self.assertRaises(serial.SerialException, self.s.open)
|
||||
|
||||
def test_PortOpenClose(self):
|
||||
for i in range(3):
|
||||
# open the port and check flag
|
||||
self.assertTrue(not self.s.isOpen())
|
||||
self.s.open()
|
||||
self.assertTrue(self.s.isOpen())
|
||||
self.s.close()
|
||||
self.assertTrue(not self.s.isOpen())
|
||||
|
||||
|
||||
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".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Test asyncio related functionality.
|
||||
"""
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = '/dev/ttyUSB0'
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
import serial.aio
|
||||
except (ImportError, SyntaxError):
|
||||
# not compatible with python 2.x
|
||||
pass
|
||||
else:
|
||||
|
||||
@unittest.skipIf(os.name != 'posix', "asyncio not supported on platform")
|
||||
class Test_asyncio(unittest.TestCase):
|
||||
"""Test asyncio related functionality"""
|
||||
|
||||
def setUp(self):
|
||||
self.loop = asyncio.get_event_loop()
|
||||
# create a closed serial port
|
||||
|
||||
def tearDown(self):
|
||||
self.loop.close()
|
||||
|
||||
def test_asyncio(self):
|
||||
TEXT = b'hello world\n'
|
||||
received = []
|
||||
actions = []
|
||||
|
||||
class Output(asyncio.Protocol):
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
actions.append('open')
|
||||
transport.serial.rts = False
|
||||
transport.write(TEXT)
|
||||
|
||||
def data_received(self, data):
|
||||
#~ print('data received', repr(data))
|
||||
received.append(data)
|
||||
if b'\n' in data:
|
||||
self.transport.close()
|
||||
|
||||
def connection_lost(self, exc):
|
||||
actions.append('close')
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
def pause_writing(self):
|
||||
actions.append('pause')
|
||||
print(self.transport.get_write_buffer_size())
|
||||
|
||||
def resume_writing(self):
|
||||
actions.append('resume')
|
||||
print(self.transport.get_write_buffer_size())
|
||||
|
||||
coro = serial.aio.create_serial_connection(self.loop, Output, PORT, baudrate=115200)
|
||||
self.loop.run_until_complete(coro)
|
||||
self.loop.run_forever()
|
||||
self.assertEqual(b''.join(received), TEXT)
|
||||
self.assertEqual(actions, ['open', 'close'])
|
||||
|
||||
|
||||
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".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""
|
||||
Test cancel functionality.
|
||||
"""
|
||||
import sys
|
||||
import unittest
|
||||
import threading
|
||||
import time
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
|
||||
@unittest.skipIf(not hasattr(serial.Serial, 'cancel_read'), "cancel_read not supported on platform")
|
||||
class TestCancelRead(unittest.TestCase):
|
||||
"""Test cancel_read functionality"""
|
||||
|
||||
def setUp(self):
|
||||
# create a closed serial port
|
||||
self.s = serial.serial_for_url(PORT)
|
||||
self.assertTrue(hasattr(self.s, 'cancel_read'), "serial instance has no cancel_read")
|
||||
self.s.timeout = 10
|
||||
self.cancel_called = 0
|
||||
|
||||
def tearDown(self):
|
||||
self.s.reset_output_buffer()
|
||||
self.s.close()
|
||||
|
||||
def _cancel(self, num_times):
|
||||
for i in range(num_times):
|
||||
#~ print "cancel"
|
||||
self.cancel_called += 1
|
||||
self.s.cancel_read()
|
||||
|
||||
def test_cancel_once(self):
|
||||
"""Cancel read"""
|
||||
threading.Timer(1, self._cancel, ((1,))).start()
|
||||
t1 = time.time()
|
||||
self.s.read(1000)
|
||||
t2 = time.time()
|
||||
self.assertEqual(self.cancel_called, 1)
|
||||
self.assertTrue(0.5 < (t2 - t1) < 2.5, 'Function did not return in time: {}'.format(t2 - t1))
|
||||
#~ self.assertTrue(not self.s.isOpen())
|
||||
#~ self.assertRaises(serial.SerialException, self.s.open)
|
||||
|
||||
#~ def test_cancel_before_read(self):
|
||||
#~ self.s.cancel_read()
|
||||
#~ self.s.read()
|
||||
|
||||
|
||||
DATA = b'#' * 1024
|
||||
|
||||
|
||||
@unittest.skipIf(not hasattr(serial.Serial, 'cancel_write'), "cancel_read not supported on platform")
|
||||
class TestCancelWrite(unittest.TestCase):
|
||||
"""Test cancel_write functionality"""
|
||||
|
||||
def setUp(self):
|
||||
# create a closed serial port
|
||||
self.s = serial.serial_for_url(PORT, baudrate=300) # extra slow ~30B/s => 1kb ~ 34s
|
||||
self.assertTrue(hasattr(self.s, 'cancel_write'), "serial instance has no cancel_write")
|
||||
self.s.write_timeout = 10
|
||||
self.cancel_called = 0
|
||||
|
||||
def tearDown(self):
|
||||
self.s.reset_output_buffer()
|
||||
# not all USB-Serial adapters will actually flush the output (maybe
|
||||
# keeping the buffer in the MCU in the adapter) therefore, speed up by
|
||||
# changing the baudrate
|
||||
self.s.baudrate = 115200
|
||||
self.s.flush()
|
||||
self.s.close()
|
||||
|
||||
def _cancel(self, num_times):
|
||||
for i in range(num_times):
|
||||
self.cancel_called += 1
|
||||
self.s.cancel_write()
|
||||
|
||||
def test_cancel_once(self):
|
||||
"""Cancel write"""
|
||||
threading.Timer(1, self._cancel, ((1,))).start()
|
||||
t1 = time.time()
|
||||
self.s.write(DATA)
|
||||
t2 = time.time()
|
||||
self.assertEqual(self.cancel_called, 1)
|
||||
self.assertTrue(0.5 < (t2 - t1) < 2.5, 'Function did not return in time: {}'.format(t2 - t1))
|
||||
#~ self.assertTrue(not self.s.isOpen())
|
||||
#~ self.assertRaises(serial.SerialException, self.s.open)
|
||||
|
||||
#~ def test_cancel_before_write(self):
|
||||
#~ self.s.cancel_write()
|
||||
#~ self.s.write(DATA)
|
||||
#~ self.s.reset_output_buffer()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stdout.write(__doc__)
|
||||
if len(sys.argv) > 1:
|
||||
PORT = sys.argv[1]
|
||||
sys.stdout.write("Testing port: {!r}\n".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,58 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
# (C) 2023 Google LLC
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
import sys
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
class TestClose(unittest.TestCase):
|
||||
|
||||
def test_closed_true(self):
|
||||
# closed is True if a Serial port is not open
|
||||
s = serial.Serial()
|
||||
self.assertFalse(s.is_open)
|
||||
self.assertTrue(s.closed)
|
||||
|
||||
def test_closed_false(self):
|
||||
# closed is False if a Serial port is open
|
||||
s = serial.serial_for_url(PORT, timeout=1)
|
||||
self.assertTrue(s.is_open)
|
||||
self.assertFalse(s.closed)
|
||||
|
||||
s.close()
|
||||
self.assertTrue(s.closed)
|
||||
|
||||
def test_close_not_called_by_finalize_if_closed(self):
|
||||
close_calls = 0
|
||||
|
||||
class TestSerial(serial.Serial):
|
||||
def close(self):
|
||||
nonlocal close_calls
|
||||
close_calls += 1
|
||||
|
||||
with TestSerial() as s:
|
||||
pass
|
||||
# close() should be called here
|
||||
|
||||
# Trigger RawIOBase finalization.
|
||||
# Because we override .closed, close() should not be called
|
||||
# if Serial says it is already closed.
|
||||
del s
|
||||
|
||||
self.assertEqual(close_calls, 1)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
PORT = sys.argv[1]
|
||||
sys.stdout.write("Testing port: {!r}\n".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,49 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2017 Guillaume Galeazzi <guillaume.g@leazzi.ch>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Some tests for the serial module.
|
||||
Part of pySerial (http://pyserial.sf.net) (C)2001-2011 cliechti@gmx.net
|
||||
|
||||
Intended to be run on different platforms, to ensure portability of
|
||||
the code.
|
||||
|
||||
Cover some of the aspects of context management
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
|
||||
class Test_Context(unittest.TestCase):
|
||||
"""Test context"""
|
||||
|
||||
def setUp(self):
|
||||
# create a closed serial port
|
||||
self.s = serial.serial_for_url(PORT)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test_with_idempotent(self):
|
||||
with self.s as stream:
|
||||
stream.write(b'1234')
|
||||
|
||||
# do other stuff like calling an exe which use COM4
|
||||
|
||||
with self.s as stream:
|
||||
stream.write(b'5678')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.stdout.write(__doc__)
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2017 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Tests for exclusive access feature.
|
||||
"""
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import sys
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
class Test_exclusive(unittest.TestCase):
|
||||
"""Test serial port locking"""
|
||||
|
||||
def setUp(self):
|
||||
with serial.serial_for_url(PORT, do_not_open=True) as x:
|
||||
if not isinstance(x, serial.Serial):
|
||||
raise unittest.SkipTest("exclusive test only compatible with real serial port")
|
||||
|
||||
def test_exclusive_none(self):
|
||||
"""test for exclusive=None"""
|
||||
with serial.Serial(PORT, exclusive=None):
|
||||
pass # OK
|
||||
|
||||
@unittest.skipUnless(os.name == 'posix', "exclusive=False not supported on platform")
|
||||
def test_exclusive_false(self):
|
||||
"""test for exclusive=False"""
|
||||
with serial.Serial(PORT, exclusive=False):
|
||||
pass # OK
|
||||
|
||||
@unittest.skipUnless(os.name in ('posix', 'nt'), "exclusive=True setting not supported on platform")
|
||||
def test_exclusive_true(self):
|
||||
"""test for exclusive=True"""
|
||||
with serial.Serial(PORT, exclusive=True):
|
||||
with self.assertRaises(serial.SerialException):
|
||||
serial.Serial(PORT, exclusive=True) # fails to open twice
|
||||
|
||||
@unittest.skipUnless(os.name == 'nt', "platform is not restricted to exclusive=True (and None)")
|
||||
def test_exclusive_only_true(self):
|
||||
"""test if exclusive=False is not supported"""
|
||||
with self.assertRaises(ValueError):
|
||||
serial.Serial(PORT, exclusive=False) # expected to fail: False not supported
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stdout.write(__doc__)
|
||||
if len(sys.argv) > 1:
|
||||
PORT = sys.argv[1]
|
||||
sys.stdout.write("Testing port: {!r}\n".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""Some tests for the serial module.
|
||||
Part of pyserial (http://pyserial.sf.net) (C)2002-2003 cliechti@gmx.net
|
||||
|
||||
Intended to be run on different platforms, to ensure portability of
|
||||
the code.
|
||||
|
||||
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
|
||||
import sys
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
BAUDRATE = 115200
|
||||
#~ BAUDRATE=9600
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
bytes_0to255 = bytes(range(256))
|
||||
else:
|
||||
bytes_0to255 = ''.join([chr(x) for x in range(256)])
|
||||
|
||||
|
||||
class TestHighLoad(unittest.TestCase):
|
||||
"""Test sending and receiving large amount of data"""
|
||||
|
||||
N = 16
|
||||
#~ N = 1
|
||||
|
||||
def setUp(self):
|
||||
self.s = serial.serial_for_url(PORT, BAUDRATE, timeout=10)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test0_WriteReadLoopback(self):
|
||||
"""Send big strings, write/read order."""
|
||||
for i in range(self.N):
|
||||
q = bytes_0to255
|
||||
self.s.write(q)
|
||||
self.assertEqual(self.s.read(len(q)), q) # expected same which was written before
|
||||
self.assertEqual(self.s.inWaiting(), 0) # expected empty buffer after all sent chars are read
|
||||
|
||||
def test1_WriteWriteReadLoopback(self):
|
||||
"""Send big strings, multiple write one read."""
|
||||
q = bytes_0to255
|
||||
for i in range(self.N):
|
||||
self.s.write(q)
|
||||
read = self.s.read(len(q) * self.N)
|
||||
self.assertEqual(read, q * self.N, "expected what was written before. got {} bytes, expected {}".format(len(read), self.N * len(q)))
|
||||
self.assertEqual(self.s.inWaiting(), 0) # "expected empty buffer after all sent chars are read")
|
||||
|
||||
|
||||
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".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,61 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
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 the interaction between Serial and the io
|
||||
library. This only works on Python 2.6+ that introduced 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 io
|
||||
import sys
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
|
||||
class Test_SerialAndIO(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.s = serial.serial_for_url(PORT, timeout=1)
|
||||
#~ self.io = io.TextIOWrapper(self.s)
|
||||
self.io = io.TextIOWrapper(io.BufferedRWPair(self.s, self.s))
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test_hello_raw(self):
|
||||
self.io.write(b"hello\n".decode('utf-8'))
|
||||
self.io.flush() # it is buffering. required to get the data out
|
||||
hello = self.io.readline()
|
||||
self.assertEqual(hello, b"hello\n".decode('utf-8'))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
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".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""
|
||||
Test PTY related functionality.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import pty
|
||||
except ImportError:
|
||||
pty = None
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
DATA = b'Hello\n'
|
||||
|
||||
@unittest.skipIf(pty is None, "pty module not supported on platform")
|
||||
class Test_Pty_Serial_Open(unittest.TestCase):
|
||||
"""Test PTY serial open"""
|
||||
|
||||
def setUp(self):
|
||||
# Open PTY
|
||||
self.master, self.slave = pty.openpty()
|
||||
|
||||
def test_pty_serial_open_slave(self):
|
||||
with serial.Serial(os.ttyname(self.slave), timeout=1) as slave:
|
||||
pass # OK
|
||||
|
||||
def test_pty_serial_write(self):
|
||||
with serial.Serial(os.ttyname(self.slave), timeout=1) as slave:
|
||||
with os.fdopen(self.master, "wb") as fd:
|
||||
fd.write(DATA)
|
||||
fd.flush()
|
||||
out = slave.read(len(DATA))
|
||||
self.assertEqual(DATA, out)
|
||||
|
||||
def test_pty_serial_read(self):
|
||||
with serial.Serial(os.ttyname(self.slave), timeout=1) as slave:
|
||||
with os.fdopen(self.master, "rb") as fd:
|
||||
slave.write(DATA)
|
||||
slave.flush()
|
||||
out = fd.read(len(DATA))
|
||||
self.assertEqual(DATA, out)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stdout.write(__doc__)
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,104 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2010-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Some tests for the serial module.
|
||||
Part of pyserial (http://pyserial.sf.net) (C)2010 cliechti@gmx.net
|
||||
|
||||
Intended to be run on different platforms, to ensure portability of
|
||||
the code.
|
||||
|
||||
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
|
||||
import sys
|
||||
import serial
|
||||
|
||||
#~ print serial.VERSION
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
def data(string):
|
||||
return bytes(string, 'latin1')
|
||||
else:
|
||||
def data(string):
|
||||
return string
|
||||
|
||||
|
||||
class Test_Readline(unittest.TestCase):
|
||||
"""Test readline function"""
|
||||
|
||||
def setUp(self):
|
||||
self.s = serial.serial_for_url(PORT, timeout=1)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test_readline(self):
|
||||
"""Test readline method"""
|
||||
self.s.write(serial.to_bytes([0x31, 0x0a, 0x32, 0x0a, 0x33, 0x0a]))
|
||||
self.assertEqual(self.s.readline(), serial.to_bytes([0x31, 0x0a]))
|
||||
self.assertEqual(self.s.readline(), serial.to_bytes([0x32, 0x0a]))
|
||||
self.assertEqual(self.s.readline(), serial.to_bytes([0x33, 0x0a]))
|
||||
# this time we will get a timeout
|
||||
self.assertEqual(self.s.readline(), serial.to_bytes([]))
|
||||
|
||||
def test_readlines(self):
|
||||
"""Test readlines method"""
|
||||
self.s.write(serial.to_bytes([0x31, 0x0a, 0x32, 0x0a, 0x33, 0x0a]))
|
||||
self.assertEqual(
|
||||
self.s.readlines(),
|
||||
[serial.to_bytes([0x31, 0x0a]), serial.to_bytes([0x32, 0x0a]), serial.to_bytes([0x33, 0x0a])]
|
||||
)
|
||||
|
||||
def test_xreadlines(self):
|
||||
"""Test xreadlines method (skipped for io based systems)"""
|
||||
if hasattr(self.s, 'xreadlines'):
|
||||
self.s.write(serial.to_bytes([0x31, 0x0a, 0x32, 0x0a, 0x33, 0x0a]))
|
||||
self.assertEqual(
|
||||
list(self.s.xreadlines()),
|
||||
[serial.to_bytes([0x31, 0x0a]), serial.to_bytes([0x32, 0x0a]), serial.to_bytes([0x33, 0x0a])]
|
||||
)
|
||||
|
||||
def test_for_in(self):
|
||||
"""Test for line in s"""
|
||||
self.s.write(serial.to_bytes([0x31, 0x0a, 0x32, 0x0a, 0x33, 0x0a]))
|
||||
lines = []
|
||||
for line in self.s:
|
||||
lines.append(line)
|
||||
self.assertEqual(
|
||||
lines,
|
||||
[serial.to_bytes([0x31, 0x0a]), serial.to_bytes([0x32, 0x0a]), serial.to_bytes([0x33, 0x0a])]
|
||||
)
|
||||
|
||||
def test_alternate_eol(self):
|
||||
"""Test readline with alternative eol settings (skipped for io based systems)"""
|
||||
if hasattr(self.s, 'xreadlines'): # test if it is our FileLike base class
|
||||
self.s.write(serial.to_bytes("no\rno\nyes\r\n"))
|
||||
self.assertEqual(
|
||||
self.s.readline(eol=serial.to_bytes("\r\n")),
|
||||
serial.to_bytes("no\rno\nyes\r\n"))
|
||||
|
||||
|
||||
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".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,42 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Test RFC 2217 related functionality.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import serial
|
||||
import serial.rfc2217
|
||||
|
||||
|
||||
class Test_RFC2217(unittest.TestCase):
|
||||
"""Test RFC 2217 related functionality"""
|
||||
|
||||
def test_failed_connection(self):
|
||||
# connection to closed port
|
||||
s = serial.serial_for_url('rfc2217://127.99.99.99:2217', do_not_open=True)
|
||||
self.assertRaises(serial.SerialException, s.open)
|
||||
self.assertFalse(s.is_open)
|
||||
s.close() # no errors expected
|
||||
# invalid address
|
||||
s = serial.serial_for_url('rfc2217://127goingtofail', do_not_open=True)
|
||||
self.assertRaises(serial.SerialException, s.open)
|
||||
self.assertFalse(s.is_open)
|
||||
s.close() # no errors expected
|
||||
# close w/o open is also OK
|
||||
s = serial.serial_for_url('rfc2217://irrelevant', do_not_open=True)
|
||||
self.assertFalse(s.is_open)
|
||||
s.close() # no errors expected
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.stdout.write(__doc__)
|
||||
sys.stdout.write("Testing connection on localhost\n")
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Test RS485 related functionality.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import serial
|
||||
import serial.rs485
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
|
||||
class Test_RS485_settings(unittest.TestCase):
|
||||
"""Test RS485 related functionality"""
|
||||
|
||||
def setUp(self):
|
||||
# create a closed serial port
|
||||
self.s = serial.serial_for_url(PORT, do_not_open=True)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test_enable_RS485(self):
|
||||
# XXX open() port - but will result in fail for most HW...
|
||||
#~ self.s.open()
|
||||
self.assertEqual(self.s._rs485_mode, None, 'RS485 is disabled by default')
|
||||
self.assertEqual(self.s.rs485_mode, None, 'RS485 is disabled by default')
|
||||
self.s.rs485_mode = serial.rs485.RS485Settings()
|
||||
self.assertTrue(self.s._rs485_mode is not None, 'RS485 is enabled')
|
||||
self.assertTrue(self.s.rs485_mode is not None, 'RS485 is enabled')
|
||||
self.s.rs485_mode = None
|
||||
self.assertEqual(self.s._rs485_mode, None, 'RS485 is disabled again')
|
||||
self.assertEqual(self.s.rs485_mode, None, 'RS485 is disabled again')
|
||||
|
||||
|
||||
class Test_RS485_class(unittest.TestCase):
|
||||
"""Test RS485 class"""
|
||||
|
||||
def setUp(self):
|
||||
if not isinstance(serial.serial_for_url(PORT), serial.Serial):
|
||||
raise unittest.SkipTest("RS485 test only compatible with real serial port")
|
||||
self.s = serial.rs485.RS485(PORT, timeout=1)
|
||||
|
||||
def tearDown(self):
|
||||
self.s.close()
|
||||
|
||||
def test_RS485_class(self):
|
||||
self.s.rs485_mode = serial.rs485.RS485Settings()
|
||||
self.s.write(b'hello')
|
||||
self.assertEqual(self.s.read(5), b'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".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,80 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Test the ability to get and set the settings with a dictionary.
|
||||
|
||||
Part of pySerial (http://pyserial.sf.net) (C) 2002-2015 cliechti@gmx.net
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
|
||||
SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff',
|
||||
'dsrdtr', 'rtscts', 'timeout', 'write_timeout', 'inter_byte_timeout')
|
||||
|
||||
|
||||
class Test_SettingsDict(unittest.TestCase):
|
||||
"""Test with settings dictionary"""
|
||||
|
||||
def test_getsettings(self):
|
||||
"""the settings dict reflects the current settings"""
|
||||
ser = serial.serial_for_url(PORT, do_not_open=True)
|
||||
d = ser.get_settings()
|
||||
for setting in SETTINGS:
|
||||
self.assertEqual(getattr(ser, setting), d[setting])
|
||||
|
||||
def test_partial_settings(self):
|
||||
"""partial settings dictionaries are also accepted"""
|
||||
ser = serial.serial_for_url(PORT, do_not_open=True)
|
||||
d = ser.get_settings()
|
||||
del d['baudrate']
|
||||
del d['bytesize']
|
||||
ser.apply_settings(d)
|
||||
for setting in d:
|
||||
self.assertEqual(getattr(ser, setting), d[setting])
|
||||
|
||||
def test_unknown_settings(self):
|
||||
"""unknown settings are ignored"""
|
||||
ser = serial.serial_for_url(PORT, do_not_open=True)
|
||||
d = ser.get_settings()
|
||||
d['foobar'] = 'ignore me'
|
||||
ser.apply_settings(d)
|
||||
|
||||
def test_init_sets_the_correct_attrs(self):
|
||||
"""__init__ sets the fields that get_settings reads"""
|
||||
for setting, value in (
|
||||
('baudrate', 57600),
|
||||
('timeout', 7),
|
||||
('write_timeout', 12),
|
||||
('inter_byte_timeout', 15),
|
||||
('stopbits', serial.STOPBITS_TWO),
|
||||
('bytesize', serial.SEVENBITS),
|
||||
('parity', serial.PARITY_ODD),
|
||||
('xonxoff', True),
|
||||
('rtscts', True),
|
||||
('dsrdtr', True)):
|
||||
kwargs = {'do_not_open': True, setting: value}
|
||||
ser = serial.serial_for_url(PORT, **kwargs)
|
||||
d = ser.get_settings()
|
||||
self.assertEqual(getattr(ser, setting), value)
|
||||
self.assertEqual(d[setting], value)
|
||||
|
||||
|
||||
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".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Test serial.threaded related functionality.
|
||||
"""
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import serial
|
||||
import serial.threaded
|
||||
import time
|
||||
|
||||
|
||||
# on which port should the tests be performed:
|
||||
PORT = 'loop://'
|
||||
|
||||
class Test_threaded(unittest.TestCase):
|
||||
"""Test serial.threaded related functionality"""
|
||||
|
||||
def test_line_reader(self):
|
||||
"""simple test of line reader class"""
|
||||
|
||||
class TestLines(serial.threaded.LineReader):
|
||||
def __init__(self):
|
||||
super(TestLines, self).__init__()
|
||||
self.received_lines = []
|
||||
|
||||
def handle_line(self, data):
|
||||
self.received_lines.append(data)
|
||||
|
||||
ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
|
||||
with serial.threaded.ReaderThread(ser, TestLines) as protocol:
|
||||
protocol.write_line('hello')
|
||||
protocol.write_line('world')
|
||||
time.sleep(1)
|
||||
self.assertEqual(protocol.received_lines, ['hello', 'world'])
|
||||
|
||||
def test_framed_packet(self):
|
||||
"""simple test of line reader class"""
|
||||
|
||||
class TestFramedPacket(serial.threaded.FramedPacket):
|
||||
def __init__(self):
|
||||
super(TestFramedPacket, self).__init__()
|
||||
self.received_packets = []
|
||||
|
||||
def handle_packet(self, packet):
|
||||
self.received_packets.append(packet)
|
||||
|
||||
def send_packet(self, packet):
|
||||
self.transport.write(self.START)
|
||||
self.transport.write(packet)
|
||||
self.transport.write(self.STOP)
|
||||
|
||||
ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
|
||||
with serial.threaded.ReaderThread(ser, TestFramedPacket) as protocol:
|
||||
protocol.send_packet(b'1')
|
||||
protocol.send_packet(b'2')
|
||||
protocol.send_packet(b'3')
|
||||
time.sleep(1)
|
||||
self.assertEqual(protocol.received_packets, [b'1', b'2', b'3'])
|
||||
|
||||
|
||||
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".format(PORT))
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""
|
||||
Test Timeout helper class.
|
||||
"""
|
||||
import sys
|
||||
import unittest
|
||||
import time
|
||||
from serial import serialutil
|
||||
|
||||
|
||||
class TestTimeoutClass(unittest.TestCase):
|
||||
"""Test the Timeout class"""
|
||||
|
||||
def test_simple_timeout(self):
|
||||
"""Test simple timeout"""
|
||||
t = serialutil.Timeout(2)
|
||||
self.assertFalse(t.expired())
|
||||
self.assertTrue(t.time_left() > 0)
|
||||
time.sleep(2.1)
|
||||
self.assertTrue(t.expired())
|
||||
self.assertEqual(t.time_left(), 0)
|
||||
|
||||
def test_non_blocking(self):
|
||||
"""Test nonblocking case (0)"""
|
||||
t = serialutil.Timeout(0)
|
||||
self.assertTrue(t.is_non_blocking)
|
||||
self.assertFalse(t.is_infinite)
|
||||
self.assertTrue(t.expired())
|
||||
|
||||
def test_blocking(self):
|
||||
"""Test no timeout (None)"""
|
||||
t = serialutil.Timeout(None)
|
||||
self.assertFalse(t.is_non_blocking)
|
||||
self.assertTrue(t.is_infinite)
|
||||
#~ self.assertFalse(t.expired())
|
||||
|
||||
def test_changing_clock(self):
|
||||
"""Test recovery from changing clock"""
|
||||
class T(serialutil.Timeout):
|
||||
def TIME(self):
|
||||
return test_time
|
||||
test_time = 1000
|
||||
t = T(10)
|
||||
self.assertEqual(t.target_time, 1010)
|
||||
self.assertFalse(t.expired())
|
||||
self.assertTrue(t.time_left() > 0)
|
||||
test_time = 100 # clock jumps way back
|
||||
self.assertTrue(t.time_left() > 0)
|
||||
self.assertTrue(t.time_left() <= 10)
|
||||
self.assertEqual(t.target_time, 110)
|
||||
test_time = 10000 # jump way forward
|
||||
self.assertEqual(t.time_left(), 0) # if will expire immediately
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stdout.write(__doc__)
|
||||
if len(sys.argv) > 1:
|
||||
PORT = sys.argv[1]
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,51 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Some tests for the serial module.
|
||||
Part of pySerial (http://pyserial.sf.net) (C)2001-2011 cliechti@gmx.net
|
||||
|
||||
Intended to be run on different platforms, to ensure portability of
|
||||
the code.
|
||||
|
||||
Cover some of the aspects of serial_for_url and the extension mechanism.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
|
||||
class Test_URL(unittest.TestCase):
|
||||
"""Test serial_for_url"""
|
||||
|
||||
def test_loop(self):
|
||||
"""loop interface"""
|
||||
serial.serial_for_url('loop://', do_not_open=True)
|
||||
|
||||
def test_bad_url(self):
|
||||
"""invalid protocol specified"""
|
||||
self.assertRaises(ValueError, serial.serial_for_url, "imnotknown://")
|
||||
|
||||
def test_custom_url(self):
|
||||
"""custom protocol handlers"""
|
||||
# it's unknown
|
||||
self.assertRaises(ValueError, serial.serial_for_url, "test://")
|
||||
# add search path
|
||||
serial.protocol_handler_packages.append('handlers')
|
||||
# now it should work
|
||||
serial.serial_for_url("test://")
|
||||
# remove our handler again
|
||||
serial.protocol_handler_packages.remove('handlers')
|
||||
# so it should not work anymore
|
||||
self.assertRaises(ValueError, serial.serial_for_url, "test://")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.stdout.write(__doc__)
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of pySerial - Cross platform serial port support for Python
|
||||
# (C) 2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Tests for utility functions of serualutil.
|
||||
"""
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import serial
|
||||
|
||||
|
||||
class Test_util(unittest.TestCase):
|
||||
"""Test serial utility functions"""
|
||||
|
||||
def test_to_bytes(self):
|
||||
self.assertEqual(serial.to_bytes([1, 2, 3]), b'\x01\x02\x03')
|
||||
self.assertEqual(serial.to_bytes(b'\x01\x02\x03'), b'\x01\x02\x03')
|
||||
self.assertEqual(serial.to_bytes(bytearray([1,2,3])), b'\x01\x02\x03')
|
||||
# unicode is not supported test. use decode() instead of u'' syntax to be
|
||||
# compatible to Python 3.x < 3.4
|
||||
self.assertRaises(TypeError, serial.to_bytes, b'hello'.decode('utf-8'))
|
||||
|
||||
def test_iterbytes(self):
|
||||
self.assertEqual(list(serial.iterbytes(b'\x01\x02\x03')), [b'\x01', b'\x02', b'\x03'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.stdout.write(__doc__)
|
||||
sys.argv[1:] = ['-v']
|
||||
# When this module is executed from the command-line, it runs all its tests
|
||||
unittest.main()
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a wrapper module for different platform implementations
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
from serial.serialutil import *
|
||||
#~ SerialBase, SerialException, to_bytes, iterbytes
|
||||
|
||||
__version__ = '3.5'
|
||||
|
||||
VERSION = __version__
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
if sys.platform == 'cli':
|
||||
from serial.serialcli import Serial
|
||||
else:
|
||||
import os
|
||||
# chose an implementation, depending on os
|
||||
if os.name == 'nt': # sys.platform == 'win32':
|
||||
from serial.serialwin32 import Serial
|
||||
elif os.name == 'posix':
|
||||
from serial.serialposix import Serial, PosixPollSerial, VTIMESerial # noqa
|
||||
elif os.name == 'java':
|
||||
from serial.serialjava import Serial
|
||||
else:
|
||||
raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
|
||||
|
||||
|
||||
protocol_handler_packages = [
|
||||
'serial.urlhandler',
|
||||
]
|
||||
|
||||
|
||||
def serial_for_url(url, *args, **kwargs):
|
||||
"""\
|
||||
Get an instance of the Serial class, depending on port/url. The port is not
|
||||
opened when the keyword parameter 'do_not_open' is true, by default it
|
||||
is. All other parameters are directly passed to the __init__ method when
|
||||
the port is instantiated.
|
||||
|
||||
The list of package names that is searched for protocol handlers is kept in
|
||||
``protocol_handler_packages``.
|
||||
|
||||
e.g. we want to support a URL ``foobar://``. A module
|
||||
``my_handlers.protocol_foobar`` is provided by the user. Then
|
||||
``protocol_handler_packages.append("my_handlers")`` would extend the search
|
||||
path so that ``serial_for_url("foobar://"))`` would work.
|
||||
"""
|
||||
# check and remove extra parameter to not confuse the Serial class
|
||||
do_open = not kwargs.pop('do_not_open', False)
|
||||
# the default is to use the native implementation
|
||||
klass = Serial
|
||||
try:
|
||||
url_lowercase = url.lower()
|
||||
except AttributeError:
|
||||
# it's not a string, use default
|
||||
pass
|
||||
else:
|
||||
# if it is an URL, try to import the handler module from the list of possible packages
|
||||
if '://' in url_lowercase:
|
||||
protocol = url_lowercase.split('://', 1)[0]
|
||||
module_name = '.protocol_{}'.format(protocol)
|
||||
for package_name in protocol_handler_packages:
|
||||
try:
|
||||
importlib.import_module(package_name)
|
||||
handler_module = importlib.import_module(module_name, package_name)
|
||||
except ImportError:
|
||||
continue
|
||||
else:
|
||||
if hasattr(handler_module, 'serial_class_for_url'):
|
||||
url, klass = handler_module.serial_class_for_url(url)
|
||||
else:
|
||||
klass = handler_module.Serial
|
||||
break
|
||||
else:
|
||||
raise ValueError('invalid URL, protocol {!r} not known'.format(protocol))
|
||||
# instantiate and open when desired
|
||||
instance = klass(None, *args, **kwargs)
|
||||
instance.port = url
|
||||
if do_open:
|
||||
instance.open()
|
||||
return instance
|
||||
@@ -1,3 +0,0 @@
|
||||
from .tools import miniterm
|
||||
|
||||
miniterm.main()
|
||||
-1351
File diff suppressed because it is too large
Load Diff
@@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# RS485 support
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
The settings for RS485 are stored in a dedicated object that can be applied to
|
||||
serial ports (where supported).
|
||||
NOTE: Some implementations may only support a subset of the settings.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import time
|
||||
import serial
|
||||
|
||||
|
||||
class RS485Settings(object):
|
||||
def __init__(
|
||||
self,
|
||||
rts_level_for_tx=True,
|
||||
rts_level_for_rx=False,
|
||||
loopback=False,
|
||||
delay_before_tx=None,
|
||||
delay_before_rx=None):
|
||||
self.rts_level_for_tx = rts_level_for_tx
|
||||
self.rts_level_for_rx = rts_level_for_rx
|
||||
self.loopback = loopback
|
||||
self.delay_before_tx = delay_before_tx
|
||||
self.delay_before_rx = delay_before_rx
|
||||
|
||||
|
||||
class RS485(serial.Serial):
|
||||
"""\
|
||||
A subclass that replaces the write method with one that toggles RTS
|
||||
according to the RS485 settings.
|
||||
|
||||
NOTE: This may work unreliably on some serial ports (control signals not
|
||||
synchronized or delayed compared to data). Using delays may be
|
||||
unreliable (varying times, larger than expected) as the OS may not
|
||||
support very fine grained delays (no smaller than in the order of
|
||||
tens of milliseconds).
|
||||
|
||||
NOTE: Some implementations support this natively. Better performance
|
||||
can be expected when the native version is used.
|
||||
|
||||
NOTE: The loopback property is ignored by this implementation. The actual
|
||||
behavior depends on the used hardware.
|
||||
|
||||
Usage:
|
||||
|
||||
ser = RS485(...)
|
||||
ser.rs485_mode = RS485Settings(...)
|
||||
ser.write(b'hello')
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RS485, self).__init__(*args, **kwargs)
|
||||
self._alternate_rs485_settings = None
|
||||
|
||||
def write(self, b):
|
||||
"""Write to port, controlling RTS before and after transmitting."""
|
||||
if self._alternate_rs485_settings is not None:
|
||||
# apply level for TX and optional delay
|
||||
self.setRTS(self._alternate_rs485_settings.rts_level_for_tx)
|
||||
if self._alternate_rs485_settings.delay_before_tx is not None:
|
||||
time.sleep(self._alternate_rs485_settings.delay_before_tx)
|
||||
# write and wait for data to be written
|
||||
super(RS485, self).write(b)
|
||||
super(RS485, self).flush()
|
||||
# optional delay and apply level for RX
|
||||
if self._alternate_rs485_settings.delay_before_rx is not None:
|
||||
time.sleep(self._alternate_rs485_settings.delay_before_rx)
|
||||
self.setRTS(self._alternate_rs485_settings.rts_level_for_rx)
|
||||
else:
|
||||
super(RS485, self).write(b)
|
||||
|
||||
# redirect where the property stores the settings so that underlying Serial
|
||||
# instance does not see them
|
||||
@property
|
||||
def rs485_mode(self):
|
||||
"""\
|
||||
Enable RS485 mode and apply new settings, set to None to disable.
|
||||
See serial.rs485.RS485Settings for more info about the value.
|
||||
"""
|
||||
return self._alternate_rs485_settings
|
||||
|
||||
@rs485_mode.setter
|
||||
def rs485_mode(self, rs485_settings):
|
||||
self._alternate_rs485_settings = rs485_settings
|
||||
@@ -1,253 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Backend for .NET/Mono (IronPython), .NET >= 2
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2008-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import System
|
||||
import System.IO.Ports
|
||||
from serial.serialutil import *
|
||||
|
||||
# must invoke function with byte array, make a helper to convert strings
|
||||
# to byte arrays
|
||||
sab = System.Array[System.Byte]
|
||||
|
||||
|
||||
def as_byte_array(string):
|
||||
return sab([ord(x) for x in string]) # XXX will require adaption when run with a 3.x compatible IronPython
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation for .NET/Mono."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
try:
|
||||
self._port_handle = System.IO.Ports.SerialPort(self.portstr)
|
||||
except Exception as msg:
|
||||
self._port_handle = None
|
||||
raise SerialException("could not open port %s: %s" % (self.portstr, msg))
|
||||
|
||||
# if RTS and/or DTR are not set before open, they default to True
|
||||
if self._rts_state is None:
|
||||
self._rts_state = True
|
||||
if self._dtr_state is None:
|
||||
self._dtr_state = True
|
||||
|
||||
self._reconfigure_port()
|
||||
self._port_handle.Open()
|
||||
self.is_open = True
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
self.reset_input_buffer()
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self._port_handle:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
#~ self._port_handle.ReceivedBytesThreshold = 1
|
||||
|
||||
if self._timeout is None:
|
||||
self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
|
||||
else:
|
||||
self._port_handle.ReadTimeout = int(self._timeout * 1000)
|
||||
|
||||
# if self._timeout != 0 and self._interCharTimeout is not None:
|
||||
# timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:]
|
||||
|
||||
if self._write_timeout is None:
|
||||
self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
|
||||
else:
|
||||
self._port_handle.WriteTimeout = int(self._write_timeout * 1000)
|
||||
|
||||
# Setup the connection info.
|
||||
try:
|
||||
self._port_handle.BaudRate = self._baudrate
|
||||
except IOError as e:
|
||||
# catch errors from illegal baudrate settings
|
||||
raise ValueError(str(e))
|
||||
|
||||
if self._bytesize == FIVEBITS:
|
||||
self._port_handle.DataBits = 5
|
||||
elif self._bytesize == SIXBITS:
|
||||
self._port_handle.DataBits = 6
|
||||
elif self._bytesize == SEVENBITS:
|
||||
self._port_handle.DataBits = 7
|
||||
elif self._bytesize == EIGHTBITS:
|
||||
self._port_handle.DataBits = 8
|
||||
else:
|
||||
raise ValueError("Unsupported number of data bits: %r" % self._bytesize)
|
||||
|
||||
if self._parity == PARITY_NONE:
|
||||
self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k
|
||||
elif self._parity == PARITY_EVEN:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Even
|
||||
elif self._parity == PARITY_ODD:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Odd
|
||||
elif self._parity == PARITY_MARK:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Mark
|
||||
elif self._parity == PARITY_SPACE:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Space
|
||||
else:
|
||||
raise ValueError("Unsupported parity mode: %r" % self._parity)
|
||||
|
||||
if self._stopbits == STOPBITS_ONE:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.One
|
||||
elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive
|
||||
elif self._stopbits == STOPBITS_TWO:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.Two
|
||||
else:
|
||||
raise ValueError("Unsupported number of stop bits: %r" % self._stopbits)
|
||||
|
||||
if self._rtscts and self._xonxoff:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff
|
||||
elif self._rtscts:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend
|
||||
elif self._xonxoff:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff
|
||||
else:
|
||||
self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None') # reserved keyword in Py3k
|
||||
|
||||
#~ def __del__(self):
|
||||
#~ self.close()
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self._port_handle:
|
||||
try:
|
||||
self._port_handle.Close()
|
||||
except System.IO.Ports.InvalidOperationException:
|
||||
# ignore errors. can happen for unplugged USB serial devices
|
||||
pass
|
||||
self._port_handle = None
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of characters currently in the input buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self._port_handle.BytesToRead
|
||||
|
||||
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.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
# must use single byte reads as this is the only way to read
|
||||
# without applying encodings
|
||||
data = bytearray()
|
||||
while size:
|
||||
try:
|
||||
data.append(self._port_handle.ReadByte())
|
||||
except System.TimeoutException:
|
||||
break
|
||||
else:
|
||||
size -= 1
|
||||
return bytes(data)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given string over the serial port."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
#~ if not isinstance(data, (bytes, bytearray)):
|
||||
#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
try:
|
||||
# must call overloaded method with byte array argument
|
||||
# as this is the only one not applying encodings
|
||||
self._port_handle.Write(as_byte_array(data), 0, len(data))
|
||||
except System.TimeoutException:
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
return len(data)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.DiscardInBuffer()
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.DiscardOutBuffer()
|
||||
|
||||
def _update_break_state(self):
|
||||
"""
|
||||
Set break: Controls TXD. When active, to transmitting is possible.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.BreakState = bool(self._break_state)
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.RtsEnable = bool(self._rts_state)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.DtrEnable = bool(self._dtr_state)
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self._port_handle.CtsHolding
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self._port_handle.DsrHolding
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
#~ return self._port_handle.XXX
|
||||
return False # XXX an error would be better
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self._port_handle.CDHolding
|
||||
|
||||
# - - platform specific - - - -
|
||||
# none
|
||||
@@ -1,251 +0,0 @@
|
||||
#!jython
|
||||
#
|
||||
# Backend Jython with JavaComm
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from serial.serialutil import *
|
||||
|
||||
|
||||
def my_import(name):
|
||||
mod = __import__(name)
|
||||
components = name.split('.')
|
||||
for comp in components[1:]:
|
||||
mod = getattr(mod, comp)
|
||||
return mod
|
||||
|
||||
|
||||
def detect_java_comm(names):
|
||||
"""try given list of modules and return that imports"""
|
||||
for name in names:
|
||||
try:
|
||||
mod = my_import(name)
|
||||
mod.SerialPort
|
||||
return mod
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
raise ImportError("No Java Communications API implementation found")
|
||||
|
||||
|
||||
# Java Communications API implementations
|
||||
# http://mho.republika.pl/java/comm/
|
||||
|
||||
comm = detect_java_comm([
|
||||
'javax.comm', # Sun/IBM
|
||||
'gnu.io', # RXTX
|
||||
])
|
||||
|
||||
|
||||
def device(portnumber):
|
||||
"""Turn a port number into a device name"""
|
||||
enum = comm.CommPortIdentifier.getPortIdentifiers()
|
||||
ports = []
|
||||
while enum.hasMoreElements():
|
||||
el = enum.nextElement()
|
||||
if el.getPortType() == comm.CommPortIdentifier.PORT_SERIAL:
|
||||
ports.append(el)
|
||||
return ports[portnumber].getName()
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""\
|
||||
Serial port class, implemented with Java Communications API and
|
||||
thus usable with jython and the appropriate java extension.
|
||||
"""
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
if type(self._port) == type(''): # strings are taken directly
|
||||
portId = comm.CommPortIdentifier.getPortIdentifier(self._port)
|
||||
else:
|
||||
portId = comm.CommPortIdentifier.getPortIdentifier(device(self._port)) # numbers are transformed to a comport id obj
|
||||
try:
|
||||
self.sPort = portId.open("python serial module", 10)
|
||||
except Exception as msg:
|
||||
self.sPort = None
|
||||
raise SerialException("Could not open port: %s" % msg)
|
||||
self._reconfigurePort()
|
||||
self._instream = self.sPort.getInputStream()
|
||||
self._outstream = self.sPort.getOutputStream()
|
||||
self.is_open = True
|
||||
|
||||
def _reconfigurePort(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self.sPort:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
self.sPort.enableReceiveTimeout(30)
|
||||
if self._bytesize == FIVEBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_5
|
||||
elif self._bytesize == SIXBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_6
|
||||
elif self._bytesize == SEVENBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_7
|
||||
elif self._bytesize == EIGHTBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_8
|
||||
else:
|
||||
raise ValueError("unsupported bytesize: %r" % self._bytesize)
|
||||
|
||||
if self._stopbits == STOPBITS_ONE:
|
||||
jstopbits = comm.SerialPort.STOPBITS_1
|
||||
elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
|
||||
jstopbits = comm.SerialPort.STOPBITS_1_5
|
||||
elif self._stopbits == STOPBITS_TWO:
|
||||
jstopbits = comm.SerialPort.STOPBITS_2
|
||||
else:
|
||||
raise ValueError("unsupported number of stopbits: %r" % self._stopbits)
|
||||
|
||||
if self._parity == PARITY_NONE:
|
||||
jparity = comm.SerialPort.PARITY_NONE
|
||||
elif self._parity == PARITY_EVEN:
|
||||
jparity = comm.SerialPort.PARITY_EVEN
|
||||
elif self._parity == PARITY_ODD:
|
||||
jparity = comm.SerialPort.PARITY_ODD
|
||||
elif self._parity == PARITY_MARK:
|
||||
jparity = comm.SerialPort.PARITY_MARK
|
||||
elif self._parity == PARITY_SPACE:
|
||||
jparity = comm.SerialPort.PARITY_SPACE
|
||||
else:
|
||||
raise ValueError("unsupported parity type: %r" % self._parity)
|
||||
|
||||
jflowin = jflowout = 0
|
||||
if self._rtscts:
|
||||
jflowin |= comm.SerialPort.FLOWCONTROL_RTSCTS_IN
|
||||
jflowout |= comm.SerialPort.FLOWCONTROL_RTSCTS_OUT
|
||||
if self._xonxoff:
|
||||
jflowin |= comm.SerialPort.FLOWCONTROL_XONXOFF_IN
|
||||
jflowout |= comm.SerialPort.FLOWCONTROL_XONXOFF_OUT
|
||||
|
||||
self.sPort.setSerialPortParams(self._baudrate, jdatabits, jstopbits, jparity)
|
||||
self.sPort.setFlowControlMode(jflowin | jflowout)
|
||||
|
||||
if self._timeout >= 0:
|
||||
self.sPort.enableReceiveTimeout(int(self._timeout*1000))
|
||||
else:
|
||||
self.sPort.disableReceiveTimeout()
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self.sPort:
|
||||
self._instream.close()
|
||||
self._outstream.close()
|
||||
self.sPort.close()
|
||||
self.sPort = None
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of characters currently in the input buffer."""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
return self._instream.available()
|
||||
|
||||
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.
|
||||
"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
read = bytearray()
|
||||
if size > 0:
|
||||
while len(read) < size:
|
||||
x = self._instream.read()
|
||||
if x == -1:
|
||||
if self.timeout >= 0:
|
||||
break
|
||||
else:
|
||||
read.append(x)
|
||||
return bytes(read)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given string over the serial port."""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
if not isinstance(data, (bytes, bytearray)):
|
||||
raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
self._outstream.write(data)
|
||||
return len(data)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self._instream.skip(self._instream.available())
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self._outstream.flush()
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""Send break condition. Timed, returns to idle state after given duration."""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.sendBreak(duration*1000.0)
|
||||
|
||||
def _update_break_state(self):
|
||||
"""Set break: Controls TXD. When active, to transmitting is possible."""
|
||||
if self.fd is None:
|
||||
raise PortNotOpenError()
|
||||
raise SerialException("The _update_break_state function is not implemented in java.")
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.setRTS(self._rts_state)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.setDTR(self._dtr_state)
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.isCTS()
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.isDSR()
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.isRI()
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.isCD()
|
||||
@@ -1,907 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# backend for serial IO for POSIX compatible systems, like Linux, OSX
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# parts based on code from Grant B. Edwards <grante@visi.com>:
|
||||
# ftp://ftp.visi.com/users/grante/python/PosixSerial.py
|
||||
#
|
||||
# references: http://www.easysw.com/~mike/serial/serial.html
|
||||
|
||||
# Collection of port names (was previously used by number_to_device which was
|
||||
# removed.
|
||||
# - Linux /dev/ttyS%d (confirmed)
|
||||
# - cygwin/win32 /dev/com%d (confirmed)
|
||||
# - openbsd (OpenBSD) /dev/cua%02d
|
||||
# - bsd*, freebsd* /dev/cuad%d
|
||||
# - darwin (OS X) /dev/cuad%d
|
||||
# - netbsd /dev/dty%02d (NetBSD 1.6 testing by Erk)
|
||||
# - irix (IRIX) /dev/ttyf%d (partially tested) names depending on flow control
|
||||
# - hp (HP-UX) /dev/tty%dp0 (not tested)
|
||||
# - sunos (Solaris/SunOS) /dev/tty%c (letters, 'a'..'z') (confirmed)
|
||||
# - aix (AIX) /dev/tty%d
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
import errno
|
||||
import fcntl
|
||||
import os
|
||||
import platform
|
||||
import select
|
||||
import struct
|
||||
import sys
|
||||
import termios
|
||||
|
||||
import serial
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, \
|
||||
PortNotOpenError, SerialTimeoutException, Timeout
|
||||
|
||||
|
||||
class PlatformSpecificBase(object):
|
||||
BAUDRATE_CONSTANTS = {}
|
||||
|
||||
def _set_special_baudrate(self, baudrate):
|
||||
raise NotImplementedError('non-standard baudrates are not supported on this platform')
|
||||
|
||||
def _set_rs485_mode(self, rs485_settings):
|
||||
raise NotImplementedError('RS485 not supported on this platform')
|
||||
|
||||
def set_low_latency_mode(self, low_latency_settings):
|
||||
raise NotImplementedError('Low latency not supported on this platform')
|
||||
|
||||
def _update_break_state(self):
|
||||
"""\
|
||||
Set break: Controls TXD. When active, no transmitting is possible.
|
||||
"""
|
||||
if self._break_state:
|
||||
fcntl.ioctl(self.fd, TIOCSBRK)
|
||||
else:
|
||||
fcntl.ioctl(self.fd, TIOCCBRK)
|
||||
|
||||
|
||||
# some systems support an extra flag to enable the two in POSIX unsupported
|
||||
# paritiy settings for MARK and SPACE
|
||||
CMSPAR = 0 # default, for unsupported platforms, override below
|
||||
|
||||
# try to detect the OS so that a device can be selected...
|
||||
# this code block should supply a device() and set_special_baudrate() function
|
||||
# for the platform
|
||||
plat = sys.platform.lower()
|
||||
|
||||
if plat[:5] == 'linux': # Linux (confirmed) # noqa
|
||||
import array
|
||||
|
||||
# extra termios flags
|
||||
CMSPAR = 0o10000000000 # Use "stick" (mark/space) parity
|
||||
|
||||
# baudrate ioctls
|
||||
if platform.machine().lower() == "mips":
|
||||
TCGETS2 = 0x4030542A
|
||||
TCSETS2 = 0x8030542B
|
||||
BAUDRATE_OFFSET = 10
|
||||
else:
|
||||
TCGETS2 = 0x802C542A
|
||||
TCSETS2 = 0x402C542B
|
||||
BAUDRATE_OFFSET = 9
|
||||
BOTHER = 0o010000
|
||||
|
||||
# RS485 ioctls
|
||||
TIOCGRS485 = 0x542E
|
||||
TIOCSRS485 = 0x542F
|
||||
SER_RS485_ENABLED = 0b00000001
|
||||
SER_RS485_RTS_ON_SEND = 0b00000010
|
||||
SER_RS485_RTS_AFTER_SEND = 0b00000100
|
||||
SER_RS485_RX_DURING_TX = 0b00010000
|
||||
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
BAUDRATE_CONSTANTS = {
|
||||
0: 0o000000, # hang up
|
||||
50: 0o000001,
|
||||
75: 0o000002,
|
||||
110: 0o000003,
|
||||
134: 0o000004,
|
||||
150: 0o000005,
|
||||
200: 0o000006,
|
||||
300: 0o000007,
|
||||
600: 0o000010,
|
||||
1200: 0o000011,
|
||||
1800: 0o000012,
|
||||
2400: 0o000013,
|
||||
4800: 0o000014,
|
||||
9600: 0o000015,
|
||||
19200: 0o000016,
|
||||
38400: 0o000017,
|
||||
57600: 0o010001,
|
||||
115200: 0o010002,
|
||||
230400: 0o010003,
|
||||
460800: 0o010004,
|
||||
500000: 0o010005,
|
||||
576000: 0o010006,
|
||||
921600: 0o010007,
|
||||
1000000: 0o010010,
|
||||
1152000: 0o010011,
|
||||
1500000: 0o010012,
|
||||
2000000: 0o010013,
|
||||
2500000: 0o010014,
|
||||
3000000: 0o010015,
|
||||
3500000: 0o010016,
|
||||
4000000: 0o010017
|
||||
}
|
||||
|
||||
def set_low_latency_mode(self, low_latency_settings):
|
||||
buf = array.array('i', [0] * 32)
|
||||
|
||||
try:
|
||||
# get serial_struct
|
||||
fcntl.ioctl(self.fd, termios.TIOCGSERIAL, buf)
|
||||
|
||||
# set or unset ASYNC_LOW_LATENCY flag
|
||||
if low_latency_settings:
|
||||
buf[4] |= 0x2000
|
||||
else:
|
||||
buf[4] &= ~0x2000
|
||||
|
||||
# set serial_struct
|
||||
fcntl.ioctl(self.fd, termios.TIOCSSERIAL, buf)
|
||||
except IOError as e:
|
||||
raise ValueError('Failed to update ASYNC_LOW_LATENCY flag to {}: {}'.format(low_latency_settings, e))
|
||||
|
||||
def _set_special_baudrate(self, baudrate):
|
||||
# right size is 44 on x86_64, allow for some growth
|
||||
buf = array.array('i', [0] * 64)
|
||||
try:
|
||||
# get serial_struct
|
||||
fcntl.ioctl(self.fd, TCGETS2, buf)
|
||||
# set custom speed
|
||||
buf[2] &= ~termios.CBAUD
|
||||
buf[2] |= BOTHER
|
||||
buf[BAUDRATE_OFFSET] = buf[BAUDRATE_OFFSET + 1] = baudrate
|
||||
|
||||
# set serial_struct
|
||||
fcntl.ioctl(self.fd, TCSETS2, buf)
|
||||
except IOError as e:
|
||||
raise ValueError('Failed to set custom baud rate ({}): {}'.format(baudrate, e))
|
||||
|
||||
def _set_rs485_mode(self, rs485_settings):
|
||||
buf = array.array('i', [0] * 8) # flags, delaytx, delayrx, padding
|
||||
try:
|
||||
fcntl.ioctl(self.fd, TIOCGRS485, buf)
|
||||
buf[0] |= SER_RS485_ENABLED
|
||||
if rs485_settings is not None:
|
||||
if rs485_settings.loopback:
|
||||
buf[0] |= SER_RS485_RX_DURING_TX
|
||||
else:
|
||||
buf[0] &= ~SER_RS485_RX_DURING_TX
|
||||
if rs485_settings.rts_level_for_tx:
|
||||
buf[0] |= SER_RS485_RTS_ON_SEND
|
||||
else:
|
||||
buf[0] &= ~SER_RS485_RTS_ON_SEND
|
||||
if rs485_settings.rts_level_for_rx:
|
||||
buf[0] |= SER_RS485_RTS_AFTER_SEND
|
||||
else:
|
||||
buf[0] &= ~SER_RS485_RTS_AFTER_SEND
|
||||
if rs485_settings.delay_before_tx is not None:
|
||||
buf[1] = int(rs485_settings.delay_before_tx * 1000)
|
||||
if rs485_settings.delay_before_rx is not None:
|
||||
buf[2] = int(rs485_settings.delay_before_rx * 1000)
|
||||
else:
|
||||
buf[0] = 0 # clear SER_RS485_ENABLED
|
||||
fcntl.ioctl(self.fd, TIOCSRS485, buf)
|
||||
except IOError as e:
|
||||
raise ValueError('Failed to set RS485 mode: {}'.format(e))
|
||||
|
||||
|
||||
elif plat == 'cygwin': # cygwin/win32 (confirmed)
|
||||
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
BAUDRATE_CONSTANTS = {
|
||||
128000: 0x01003,
|
||||
256000: 0x01005,
|
||||
500000: 0x01007,
|
||||
576000: 0x01008,
|
||||
921600: 0x01009,
|
||||
1000000: 0x0100a,
|
||||
1152000: 0x0100b,
|
||||
1500000: 0x0100c,
|
||||
2000000: 0x0100d,
|
||||
2500000: 0x0100e,
|
||||
3000000: 0x0100f
|
||||
}
|
||||
|
||||
|
||||
elif plat[:6] == 'darwin': # OS X
|
||||
import array
|
||||
IOSSIOSPEED = 0x80045402 # _IOW('T', 2, speed_t)
|
||||
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
osx_version = os.uname()[2].split('.')
|
||||
TIOCSBRK = 0x2000747B # _IO('t', 123)
|
||||
TIOCCBRK = 0x2000747A # _IO('t', 122)
|
||||
|
||||
# Tiger or above can support arbitrary serial speeds
|
||||
if int(osx_version[0]) >= 8:
|
||||
def _set_special_baudrate(self, baudrate):
|
||||
# use IOKit-specific call to set up high speeds
|
||||
buf = array.array('i', [baudrate])
|
||||
fcntl.ioctl(self.fd, IOSSIOSPEED, buf, 1)
|
||||
|
||||
def _update_break_state(self):
|
||||
"""\
|
||||
Set break: Controls TXD. When active, no transmitting is possible.
|
||||
"""
|
||||
if self._break_state:
|
||||
fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK)
|
||||
else:
|
||||
fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK)
|
||||
|
||||
elif plat[:3] == 'bsd' or \
|
||||
plat[:7] == 'freebsd' or \
|
||||
plat[:6] == 'netbsd' or \
|
||||
plat[:7] == 'openbsd':
|
||||
|
||||
class ReturnBaudrate(object):
|
||||
def __getitem__(self, key):
|
||||
return key
|
||||
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
# Only tested on FreeBSD:
|
||||
# The baud rate may be passed in as
|
||||
# a literal value.
|
||||
BAUDRATE_CONSTANTS = ReturnBaudrate()
|
||||
|
||||
TIOCSBRK = 0x2000747B # _IO('t', 123)
|
||||
TIOCCBRK = 0x2000747A # _IO('t', 122)
|
||||
|
||||
|
||||
def _update_break_state(self):
|
||||
"""\
|
||||
Set break: Controls TXD. When active, no transmitting is possible.
|
||||
"""
|
||||
if self._break_state:
|
||||
fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK)
|
||||
else:
|
||||
fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK)
|
||||
|
||||
else:
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
pass
|
||||
|
||||
|
||||
# load some constants for later use.
|
||||
# try to use values from termios, use defaults from linux otherwise
|
||||
TIOCMGET = getattr(termios, 'TIOCMGET', 0x5415)
|
||||
TIOCMBIS = getattr(termios, 'TIOCMBIS', 0x5416)
|
||||
TIOCMBIC = getattr(termios, 'TIOCMBIC', 0x5417)
|
||||
TIOCMSET = getattr(termios, 'TIOCMSET', 0x5418)
|
||||
|
||||
# TIOCM_LE = getattr(termios, 'TIOCM_LE', 0x001)
|
||||
TIOCM_DTR = getattr(termios, 'TIOCM_DTR', 0x002)
|
||||
TIOCM_RTS = getattr(termios, 'TIOCM_RTS', 0x004)
|
||||
# TIOCM_ST = getattr(termios, 'TIOCM_ST', 0x008)
|
||||
# TIOCM_SR = getattr(termios, 'TIOCM_SR', 0x010)
|
||||
|
||||
TIOCM_CTS = getattr(termios, 'TIOCM_CTS', 0x020)
|
||||
TIOCM_CAR = getattr(termios, 'TIOCM_CAR', 0x040)
|
||||
TIOCM_RNG = getattr(termios, 'TIOCM_RNG', 0x080)
|
||||
TIOCM_DSR = getattr(termios, 'TIOCM_DSR', 0x100)
|
||||
TIOCM_CD = getattr(termios, 'TIOCM_CD', TIOCM_CAR)
|
||||
TIOCM_RI = getattr(termios, 'TIOCM_RI', TIOCM_RNG)
|
||||
# TIOCM_OUT1 = getattr(termios, 'TIOCM_OUT1', 0x2000)
|
||||
# TIOCM_OUT2 = getattr(termios, 'TIOCM_OUT2', 0x4000)
|
||||
if hasattr(termios, 'TIOCINQ'):
|
||||
TIOCINQ = termios.TIOCINQ
|
||||
else:
|
||||
TIOCINQ = getattr(termios, 'FIONREAD', 0x541B)
|
||||
TIOCOUTQ = getattr(termios, 'TIOCOUTQ', 0x5411)
|
||||
|
||||
TIOCM_zero_str = struct.pack('I', 0)
|
||||
TIOCM_RTS_str = struct.pack('I', TIOCM_RTS)
|
||||
TIOCM_DTR_str = struct.pack('I', TIOCM_DTR)
|
||||
|
||||
TIOCSBRK = getattr(termios, 'TIOCSBRK', 0x5427)
|
||||
TIOCCBRK = getattr(termios, 'TIOCCBRK', 0x5428)
|
||||
|
||||
|
||||
class Serial(SerialBase, PlatformSpecific):
|
||||
"""\
|
||||
Serial port class POSIX implementation. Serial port configuration is
|
||||
done with termios and fcntl. Runs on Linux and many other Un*x like
|
||||
systems.
|
||||
"""
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened."""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
self.fd = None
|
||||
# open
|
||||
try:
|
||||
self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
|
||||
except OSError as msg:
|
||||
self.fd = None
|
||||
raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
|
||||
#~ fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # set blocking
|
||||
|
||||
self.pipe_abort_read_r, self.pipe_abort_read_w = None, None
|
||||
self.pipe_abort_write_r, self.pipe_abort_write_w = None, None
|
||||
|
||||
try:
|
||||
self._reconfigure_port(force_update=True)
|
||||
|
||||
try:
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
except IOError as e:
|
||||
# ignore Invalid argument and Inappropriate ioctl
|
||||
if e.errno not in (errno.EINVAL, errno.ENOTTY):
|
||||
raise
|
||||
|
||||
self._reset_input_buffer()
|
||||
|
||||
self.pipe_abort_read_r, self.pipe_abort_read_w = os.pipe()
|
||||
self.pipe_abort_write_r, self.pipe_abort_write_w = os.pipe()
|
||||
fcntl.fcntl(self.pipe_abort_read_r, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
fcntl.fcntl(self.pipe_abort_write_r, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
except BaseException:
|
||||
try:
|
||||
os.close(self.fd)
|
||||
except Exception:
|
||||
# ignore any exception when closing the port
|
||||
# also to keep original exception that happened when setting up
|
||||
pass
|
||||
self.fd = None
|
||||
|
||||
if self.pipe_abort_read_w is not None:
|
||||
os.close(self.pipe_abort_read_w)
|
||||
self.pipe_abort_read_w = None
|
||||
if self.pipe_abort_read_r is not None:
|
||||
os.close(self.pipe_abort_read_r)
|
||||
self.pipe_abort_read_r = None
|
||||
if self.pipe_abort_write_w is not None:
|
||||
os.close(self.pipe_abort_write_w)
|
||||
self.pipe_abort_write_w = None
|
||||
if self.pipe_abort_write_r is not None:
|
||||
os.close(self.pipe_abort_write_r)
|
||||
self.pipe_abort_write_r = None
|
||||
|
||||
raise
|
||||
|
||||
self.is_open = True
|
||||
|
||||
def _reconfigure_port(self, force_update=False):
|
||||
"""Set communication parameters on opened port."""
|
||||
if self.fd is None:
|
||||
raise SerialException("Can only operate on a valid file descriptor")
|
||||
|
||||
# if exclusive lock is requested, create it before we modify anything else
|
||||
if self._exclusive is not None:
|
||||
if self._exclusive:
|
||||
try:
|
||||
fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except IOError as msg:
|
||||
raise SerialException(msg.errno, "Could not exclusively lock port {}: {}".format(self._port, msg))
|
||||
else:
|
||||
fcntl.flock(self.fd, fcntl.LOCK_UN)
|
||||
|
||||
custom_baud = None
|
||||
|
||||
vmin = vtime = 0 # timeout is done via select
|
||||
if self._inter_byte_timeout is not None:
|
||||
vmin = 1
|
||||
vtime = int(self._inter_byte_timeout * 10)
|
||||
try:
|
||||
orig_attr = termios.tcgetattr(self.fd)
|
||||
iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr
|
||||
except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here
|
||||
raise SerialException("Could not configure port: {}".format(msg))
|
||||
# set up raw mode / no echo / binary
|
||||
cflag |= (termios.CLOCAL | termios.CREAD)
|
||||
lflag &= ~(termios.ICANON | termios.ECHO | termios.ECHOE |
|
||||
termios.ECHOK | termios.ECHONL |
|
||||
termios.ISIG | termios.IEXTEN) # |termios.ECHOPRT
|
||||
for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk
|
||||
if hasattr(termios, flag):
|
||||
lflag &= ~getattr(termios, flag)
|
||||
|
||||
oflag &= ~(termios.OPOST | termios.ONLCR | termios.OCRNL)
|
||||
iflag &= ~(termios.INLCR | termios.IGNCR | termios.ICRNL | termios.IGNBRK)
|
||||
if hasattr(termios, 'IUCLC'):
|
||||
iflag &= ~termios.IUCLC
|
||||
if hasattr(termios, 'PARMRK'):
|
||||
iflag &= ~termios.PARMRK
|
||||
|
||||
# setup baud rate
|
||||
try:
|
||||
ispeed = ospeed = getattr(termios, 'B{}'.format(self._baudrate))
|
||||
except AttributeError:
|
||||
try:
|
||||
ispeed = ospeed = self.BAUDRATE_CONSTANTS[self._baudrate]
|
||||
except KeyError:
|
||||
#~ raise ValueError('Invalid baud rate: %r' % self._baudrate)
|
||||
|
||||
# See if BOTHER is defined for this platform; if it is, use
|
||||
# this for a speed not defined in the baudrate constants list.
|
||||
try:
|
||||
ispeed = ospeed = BOTHER
|
||||
except NameError:
|
||||
# may need custom baud rate, it isn't in our list.
|
||||
ispeed = ospeed = getattr(termios, 'B38400')
|
||||
|
||||
try:
|
||||
custom_baud = int(self._baudrate) # store for later
|
||||
except ValueError:
|
||||
raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate))
|
||||
else:
|
||||
if custom_baud < 0:
|
||||
raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate))
|
||||
|
||||
# setup char len
|
||||
cflag &= ~termios.CSIZE
|
||||
if self._bytesize == 8:
|
||||
cflag |= termios.CS8
|
||||
elif self._bytesize == 7:
|
||||
cflag |= termios.CS7
|
||||
elif self._bytesize == 6:
|
||||
cflag |= termios.CS6
|
||||
elif self._bytesize == 5:
|
||||
cflag |= termios.CS5
|
||||
else:
|
||||
raise ValueError('Invalid char len: {!r}'.format(self._bytesize))
|
||||
# setup stop bits
|
||||
if self._stopbits == serial.STOPBITS_ONE:
|
||||
cflag &= ~(termios.CSTOPB)
|
||||
elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
|
||||
cflag |= (termios.CSTOPB) # XXX same as TWO.. there is no POSIX support for 1.5
|
||||
elif self._stopbits == serial.STOPBITS_TWO:
|
||||
cflag |= (termios.CSTOPB)
|
||||
else:
|
||||
raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits))
|
||||
# setup parity
|
||||
iflag &= ~(termios.INPCK | termios.ISTRIP)
|
||||
if self._parity == serial.PARITY_NONE:
|
||||
cflag &= ~(termios.PARENB | termios.PARODD | CMSPAR)
|
||||
elif self._parity == serial.PARITY_EVEN:
|
||||
cflag &= ~(termios.PARODD | CMSPAR)
|
||||
cflag |= (termios.PARENB)
|
||||
elif self._parity == serial.PARITY_ODD:
|
||||
cflag &= ~CMSPAR
|
||||
cflag |= (termios.PARENB | termios.PARODD)
|
||||
elif self._parity == serial.PARITY_MARK and CMSPAR:
|
||||
cflag |= (termios.PARENB | CMSPAR | termios.PARODD)
|
||||
elif self._parity == serial.PARITY_SPACE and CMSPAR:
|
||||
cflag |= (termios.PARENB | CMSPAR)
|
||||
cflag &= ~(termios.PARODD)
|
||||
else:
|
||||
raise ValueError('Invalid parity: {!r}'.format(self._parity))
|
||||
# setup flow control
|
||||
# xonxoff
|
||||
if hasattr(termios, 'IXANY'):
|
||||
if self._xonxoff:
|
||||
iflag |= (termios.IXON | termios.IXOFF) # |termios.IXANY)
|
||||
else:
|
||||
iflag &= ~(termios.IXON | termios.IXOFF | termios.IXANY)
|
||||
else:
|
||||
if self._xonxoff:
|
||||
iflag |= (termios.IXON | termios.IXOFF)
|
||||
else:
|
||||
iflag &= ~(termios.IXON | termios.IXOFF)
|
||||
# rtscts
|
||||
if hasattr(termios, 'CRTSCTS'):
|
||||
if self._rtscts:
|
||||
cflag |= (termios.CRTSCTS)
|
||||
else:
|
||||
cflag &= ~(termios.CRTSCTS)
|
||||
elif hasattr(termios, 'CNEW_RTSCTS'): # try it with alternate constant name
|
||||
if self._rtscts:
|
||||
cflag |= (termios.CNEW_RTSCTS)
|
||||
else:
|
||||
cflag &= ~(termios.CNEW_RTSCTS)
|
||||
# XXX should there be a warning if setting up rtscts (and xonxoff etc) fails??
|
||||
|
||||
# buffer
|
||||
# vmin "minimal number of characters to be read. 0 for non blocking"
|
||||
if vmin < 0 or vmin > 255:
|
||||
raise ValueError('Invalid vmin: {!r}'.format(vmin))
|
||||
cc[termios.VMIN] = vmin
|
||||
# vtime
|
||||
if vtime < 0 or vtime > 255:
|
||||
raise ValueError('Invalid vtime: {!r}'.format(vtime))
|
||||
cc[termios.VTIME] = vtime
|
||||
# activate settings
|
||||
if force_update or [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr:
|
||||
termios.tcsetattr(
|
||||
self.fd,
|
||||
termios.TCSANOW,
|
||||
[iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
|
||||
|
||||
# apply custom baud rate, if any
|
||||
if custom_baud is not None:
|
||||
self._set_special_baudrate(custom_baud)
|
||||
|
||||
if self._rs485_mode is not None:
|
||||
self._set_rs485_mode(self._rs485_mode)
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self.fd is not None:
|
||||
os.close(self.fd)
|
||||
self.fd = None
|
||||
os.close(self.pipe_abort_read_w)
|
||||
os.close(self.pipe_abort_read_r)
|
||||
os.close(self.pipe_abort_write_w)
|
||||
os.close(self.pipe_abort_write_r)
|
||||
self.pipe_abort_read_r, self.pipe_abort_read_w = None, None
|
||||
self.pipe_abort_write_r, self.pipe_abort_write_w = None, None
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
#~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str)
|
||||
s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0]
|
||||
|
||||
# select based implementation, proved to work on many systems
|
||||
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.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
read = bytearray()
|
||||
timeout = Timeout(self._timeout)
|
||||
while len(read) < size:
|
||||
try:
|
||||
ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left())
|
||||
if self.pipe_abort_read_r in ready:
|
||||
os.read(self.pipe_abort_read_r, 1000)
|
||||
break
|
||||
# If select was used with a timeout, and the timeout occurs, it
|
||||
# returns with empty lists -> thus abort read operation.
|
||||
# For timeout == 0 (non-blocking operation) also abort when
|
||||
# there is nothing to read.
|
||||
if not ready:
|
||||
break # timeout
|
||||
buf = os.read(self.fd, size - len(read))
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
except select.error as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
else:
|
||||
# read should always return some data as select reported it was
|
||||
# ready to read when we get to this point.
|
||||
if not buf:
|
||||
# Disconnected devices, at least on Linux, show the
|
||||
# behavior that they are always ready to read immediately
|
||||
# but reading returns nothing.
|
||||
raise SerialException(
|
||||
'device reports readiness to read but returned no data '
|
||||
'(device disconnected or multiple access on port?)')
|
||||
read.extend(buf)
|
||||
|
||||
if timeout.expired():
|
||||
break
|
||||
return bytes(read)
|
||||
|
||||
def cancel_read(self):
|
||||
if self.is_open:
|
||||
os.write(self.pipe_abort_read_w, b"x")
|
||||
|
||||
def cancel_write(self):
|
||||
if self.is_open:
|
||||
os.write(self.pipe_abort_write_w, b"x")
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given byte string over the serial port."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
d = to_bytes(data)
|
||||
tx_len = length = len(d)
|
||||
timeout = Timeout(self._write_timeout)
|
||||
while tx_len > 0:
|
||||
try:
|
||||
n = os.write(self.fd, d)
|
||||
if timeout.is_non_blocking:
|
||||
# Zero timeout indicates non-blocking - simply return the
|
||||
# number of bytes of data actually written
|
||||
return n
|
||||
elif not timeout.is_infinite:
|
||||
# when timeout is set, use select to wait for being ready
|
||||
# with the time left as timeout
|
||||
if timeout.expired():
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], timeout.time_left())
|
||||
if abort:
|
||||
os.read(self.pipe_abort_write_r, 1000)
|
||||
break
|
||||
if not ready:
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
else:
|
||||
assert timeout.time_left() is None
|
||||
# wait for write operation
|
||||
abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], None)
|
||||
if abort:
|
||||
os.read(self.pipe_abort_write_r, 1)
|
||||
break
|
||||
if not ready:
|
||||
raise SerialException('write failed (select)')
|
||||
d = d[n:]
|
||||
tx_len -= n
|
||||
except SerialException:
|
||||
raise
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('write failed: {}'.format(e))
|
||||
except select.error as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('write failed: {}'.format(e))
|
||||
if not timeout.is_non_blocking and timeout.expired():
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
return length - len(d)
|
||||
|
||||
def flush(self):
|
||||
"""\
|
||||
Flush of file like objects. In this case, wait until all data
|
||||
is written.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
termios.tcdrain(self.fd)
|
||||
|
||||
def _reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
termios.tcflush(self.fd, termios.TCIFLUSH)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._reset_input_buffer()
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and discarding all
|
||||
that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
termios.tcflush(self.fd, termios.TCOFLUSH)
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""\
|
||||
Send break condition. Timed, returns to idle state after given
|
||||
duration.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
termios.tcsendbreak(self.fd, int(duration / 0.25))
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self._rts_state:
|
||||
fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str)
|
||||
else:
|
||||
fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self._dtr_state:
|
||||
fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str)
|
||||
else:
|
||||
fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str)
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0] & TIOCM_CTS != 0
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0] & TIOCM_DSR != 0
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0] & TIOCM_RI != 0
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0] & TIOCM_CD != 0
|
||||
|
||||
# - - platform specific - - - -
|
||||
|
||||
@property
|
||||
def out_waiting(self):
|
||||
"""Return the number of bytes currently in the output buffer."""
|
||||
#~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str)
|
||||
s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0]
|
||||
|
||||
def fileno(self):
|
||||
"""\
|
||||
For easier use of the serial port instance with select.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self.fd
|
||||
|
||||
def set_input_flow_control(self, enable=True):
|
||||
"""\
|
||||
Manually control flow - when software flow control is enabled.
|
||||
This will send XON (true) or XOFF (false) to the other device.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if enable:
|
||||
termios.tcflow(self.fd, termios.TCION)
|
||||
else:
|
||||
termios.tcflow(self.fd, termios.TCIOFF)
|
||||
|
||||
def set_output_flow_control(self, enable=True):
|
||||
"""\
|
||||
Manually control flow of outgoing data - when hardware or software flow
|
||||
control is enabled.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if enable:
|
||||
termios.tcflow(self.fd, termios.TCOON)
|
||||
else:
|
||||
termios.tcflow(self.fd, termios.TCOOFF)
|
||||
|
||||
def nonblocking(self):
|
||||
"""DEPRECATED - has no use"""
|
||||
import warnings
|
||||
warnings.warn("nonblocking() has no effect, already nonblocking", DeprecationWarning)
|
||||
|
||||
|
||||
class PosixPollSerial(Serial):
|
||||
"""\
|
||||
Poll based read implementation. Not all systems support poll properly.
|
||||
However this one has better handling of errors, such as a device
|
||||
disconnecting while it's in use (e.g. USB-serial unplugged).
|
||||
"""
|
||||
|
||||
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.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
read = bytearray()
|
||||
timeout = Timeout(self._timeout)
|
||||
poll = select.poll()
|
||||
poll.register(self.fd, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
|
||||
poll.register(self.pipe_abort_read_r, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
|
||||
if size > 0:
|
||||
while len(read) < size:
|
||||
# print "\tread(): size",size, "have", len(read) #debug
|
||||
# wait until device becomes ready to read (or something fails)
|
||||
for fd, event in poll.poll(None if timeout.is_infinite else (timeout.time_left() * 1000)):
|
||||
if fd == self.pipe_abort_read_r:
|
||||
break
|
||||
if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
|
||||
raise SerialException('device reports error (poll)')
|
||||
# we don't care if it is select.POLLIN or timeout, that's
|
||||
# handled below
|
||||
if fd == self.pipe_abort_read_r:
|
||||
os.read(self.pipe_abort_read_r, 1000)
|
||||
break
|
||||
buf = os.read(self.fd, size - len(read))
|
||||
read.extend(buf)
|
||||
if timeout.expired() \
|
||||
or (self._inter_byte_timeout is not None and self._inter_byte_timeout > 0) and not buf:
|
||||
break # early abort on timeout
|
||||
return bytes(read)
|
||||
|
||||
|
||||
class VTIMESerial(Serial):
|
||||
"""\
|
||||
Implement timeout using vtime of tty device instead of using select.
|
||||
This means that no inter character timeout can be specified and that
|
||||
the error handling is degraded.
|
||||
|
||||
Overall timeout is disabled when inter-character timeout is used.
|
||||
|
||||
Note that this implementation does NOT support cancel_read(), it will
|
||||
just ignore that.
|
||||
"""
|
||||
|
||||
def _reconfigure_port(self, force_update=True):
|
||||
"""Set communication parameters on opened port."""
|
||||
super(VTIMESerial, self)._reconfigure_port()
|
||||
fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # clear O_NONBLOCK
|
||||
|
||||
if self._inter_byte_timeout is not None:
|
||||
vmin = 1
|
||||
vtime = int(self._inter_byte_timeout * 10)
|
||||
elif self._timeout is None:
|
||||
vmin = 1
|
||||
vtime = 0
|
||||
else:
|
||||
vmin = 0
|
||||
vtime = int(self._timeout * 10)
|
||||
try:
|
||||
orig_attr = termios.tcgetattr(self.fd)
|
||||
iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr
|
||||
except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here
|
||||
raise serial.SerialException("Could not configure port: {}".format(msg))
|
||||
|
||||
if vtime < 0 or vtime > 255:
|
||||
raise ValueError('Invalid vtime: {!r}'.format(vtime))
|
||||
cc[termios.VTIME] = vtime
|
||||
cc[termios.VMIN] = vmin
|
||||
|
||||
termios.tcsetattr(
|
||||
self.fd,
|
||||
termios.TCSANOW,
|
||||
[iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
|
||||
|
||||
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.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
read = bytearray()
|
||||
while len(read) < size:
|
||||
buf = os.read(self.fd, size - len(read))
|
||||
if not buf:
|
||||
break
|
||||
read.extend(buf)
|
||||
return bytes(read)
|
||||
|
||||
# hack to make hasattr return false
|
||||
cancel_read = property()
|
||||
@@ -1,707 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Base class and support functions used by various backends.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import io
|
||||
import time
|
||||
|
||||
# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)``
|
||||
# isn't returning the contents (very unfortunate). Therefore we need special
|
||||
# cases and test for it. Ensure that there is a ``memoryview`` object for older
|
||||
# Python versions. This is easier than making every test dependent on its
|
||||
# existence.
|
||||
try:
|
||||
memoryview
|
||||
except (NameError, AttributeError):
|
||||
# implementation does not matter as we do not really use it.
|
||||
# it just must not inherit from something else we might care for.
|
||||
class memoryview(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
pass
|
||||
|
||||
try:
|
||||
unicode
|
||||
except (NameError, AttributeError):
|
||||
unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name
|
||||
|
||||
try:
|
||||
basestring
|
||||
except (NameError, AttributeError):
|
||||
basestring = (str,) # for Python 3, pylint: disable=redefined-builtin,invalid-name
|
||||
|
||||
|
||||
# "for byte in data" fails for python3 as it returns ints instead of bytes
|
||||
def iterbytes(b):
|
||||
"""Iterate over bytes, returning bytes instead of ints (python3)"""
|
||||
if isinstance(b, memoryview):
|
||||
b = b.tobytes()
|
||||
i = 0
|
||||
while True:
|
||||
a = b[i:i + 1]
|
||||
i += 1
|
||||
if a:
|
||||
yield a
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11'
|
||||
# so a simple ``bytes(sequence)`` doesn't work for all versions
|
||||
def to_bytes(seq):
|
||||
"""convert a sequence to a bytes type"""
|
||||
if isinstance(seq, bytes):
|
||||
return seq
|
||||
elif isinstance(seq, bytearray):
|
||||
return bytes(seq)
|
||||
elif isinstance(seq, memoryview):
|
||||
return seq.tobytes()
|
||||
elif isinstance(seq, unicode):
|
||||
raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq))
|
||||
else:
|
||||
# handle list of integers and bytes (one or more items) for Python 2 and 3
|
||||
return bytes(bytearray(seq))
|
||||
|
||||
|
||||
# create control bytes
|
||||
XON = to_bytes([17])
|
||||
XOFF = to_bytes([19])
|
||||
|
||||
CR = to_bytes([13])
|
||||
LF = to_bytes([10])
|
||||
|
||||
|
||||
PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S'
|
||||
STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2)
|
||||
FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8)
|
||||
|
||||
PARITY_NAMES = {
|
||||
PARITY_NONE: 'None',
|
||||
PARITY_EVEN: 'Even',
|
||||
PARITY_ODD: 'Odd',
|
||||
PARITY_MARK: 'Mark',
|
||||
PARITY_SPACE: 'Space',
|
||||
}
|
||||
|
||||
|
||||
class SerialException(IOError):
|
||||
"""Base class for serial port related exceptions."""
|
||||
|
||||
|
||||
class SerialTimeoutException(SerialException):
|
||||
"""Write timeouts give an exception"""
|
||||
|
||||
|
||||
class PortNotOpenError(SerialException):
|
||||
"""Port is not open"""
|
||||
def __init__(self):
|
||||
super(PortNotOpenError, self).__init__('Attempting to use a port that is not open')
|
||||
|
||||
|
||||
class Timeout(object):
|
||||
"""\
|
||||
Abstraction for timeout operations. Using time.monotonic() if available
|
||||
or time.time() in all other cases.
|
||||
|
||||
The class can also be initialized with 0 or None, in order to support
|
||||
non-blocking and fully blocking I/O operations. The attributes
|
||||
is_non_blocking and is_infinite are set accordingly.
|
||||
"""
|
||||
if hasattr(time, 'monotonic'):
|
||||
# Timeout implementation with time.monotonic(). This function is only
|
||||
# supported by Python 3.3 and above. It returns a time in seconds
|
||||
# (float) just as time.time(), but is not affected by system clock
|
||||
# adjustments.
|
||||
TIME = time.monotonic
|
||||
else:
|
||||
# Timeout implementation with time.time(). This is compatible with all
|
||||
# Python versions but has issues if the clock is adjusted while the
|
||||
# timeout is running.
|
||||
TIME = time.time
|
||||
|
||||
def __init__(self, duration):
|
||||
"""Initialize a timeout with given duration"""
|
||||
self.is_infinite = (duration is None)
|
||||
self.is_non_blocking = (duration == 0)
|
||||
self.duration = duration
|
||||
if duration is not None:
|
||||
self.target_time = self.TIME() + duration
|
||||
else:
|
||||
self.target_time = None
|
||||
|
||||
def expired(self):
|
||||
"""Return a boolean, telling if the timeout has expired"""
|
||||
return self.target_time is not None and self.time_left() <= 0
|
||||
|
||||
def time_left(self):
|
||||
"""Return how many seconds are left until the timeout expires"""
|
||||
if self.is_non_blocking:
|
||||
return 0
|
||||
elif self.is_infinite:
|
||||
return None
|
||||
else:
|
||||
delta = self.target_time - self.TIME()
|
||||
if delta > self.duration:
|
||||
# clock jumped, recalculate
|
||||
self.target_time = self.TIME() + self.duration
|
||||
return self.duration
|
||||
else:
|
||||
return max(0, delta)
|
||||
|
||||
def restart(self, duration):
|
||||
"""\
|
||||
Restart a timeout, only supported if a timeout was already set up
|
||||
before.
|
||||
"""
|
||||
self.duration = duration
|
||||
self.target_time = self.TIME() + duration
|
||||
|
||||
|
||||
class SerialBase(io.RawIOBase):
|
||||
"""\
|
||||
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)
|
||||
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)
|
||||
|
||||
def __init__(self,
|
||||
port=None,
|
||||
baudrate=9600,
|
||||
bytesize=EIGHTBITS,
|
||||
parity=PARITY_NONE,
|
||||
stopbits=STOPBITS_ONE,
|
||||
timeout=None,
|
||||
xonxoff=False,
|
||||
rtscts=False,
|
||||
write_timeout=None,
|
||||
dsrdtr=False,
|
||||
inter_byte_timeout=None,
|
||||
exclusive=None,
|
||||
**kwargs):
|
||||
"""\
|
||||
Initialize comm port object. If a "port" is given, then the port will be
|
||||
opened immediately. Otherwise a Serial port object in closed state
|
||||
is returned.
|
||||
"""
|
||||
|
||||
self.is_open = False
|
||||
self.portstr = None
|
||||
self.name = None
|
||||
# correct values are assigned below through properties
|
||||
self._port = None
|
||||
self._baudrate = None
|
||||
self._bytesize = None
|
||||
self._parity = None
|
||||
self._stopbits = None
|
||||
self._timeout = None
|
||||
self._write_timeout = None
|
||||
self._xonxoff = None
|
||||
self._rtscts = None
|
||||
self._dsrdtr = None
|
||||
self._inter_byte_timeout = None
|
||||
self._rs485_mode = None # disabled by default
|
||||
self._rts_state = True
|
||||
self._dtr_state = True
|
||||
self._break_state = False
|
||||
self._exclusive = None
|
||||
|
||||
# assign values using get/set methods using the properties feature
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.bytesize = bytesize
|
||||
self.parity = parity
|
||||
self.stopbits = stopbits
|
||||
self.timeout = timeout
|
||||
self.write_timeout = write_timeout
|
||||
self.xonxoff = xonxoff
|
||||
self.rtscts = rtscts
|
||||
self.dsrdtr = dsrdtr
|
||||
self.inter_byte_timeout = inter_byte_timeout
|
||||
self.exclusive = exclusive
|
||||
|
||||
# watch for backward compatible kwargs
|
||||
if 'writeTimeout' in kwargs:
|
||||
self.write_timeout = kwargs.pop('writeTimeout')
|
||||
if 'interCharTimeout' in kwargs:
|
||||
self.inter_byte_timeout = kwargs.pop('interCharTimeout')
|
||||
if kwargs:
|
||||
raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs))
|
||||
|
||||
if port is not None:
|
||||
self.open()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# to be implemented by subclasses:
|
||||
# def open(self):
|
||||
# def close(self):
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
"""\
|
||||
Get the current port setting. The value that was passed on init or using
|
||||
setPort() is passed back.
|
||||
"""
|
||||
return self._port
|
||||
|
||||
@port.setter
|
||||
def port(self, port):
|
||||
"""\
|
||||
Change the port.
|
||||
"""
|
||||
if port is not None and not isinstance(port, basestring):
|
||||
raise ValueError('"port" must be None or a string, not {}'.format(type(port)))
|
||||
was_open = self.is_open
|
||||
if was_open:
|
||||
self.close()
|
||||
self.portstr = port
|
||||
self._port = port
|
||||
self.name = self.portstr
|
||||
if was_open:
|
||||
self.open()
|
||||
|
||||
@property
|
||||
def baudrate(self):
|
||||
"""Get the current baud rate setting."""
|
||||
return self._baudrate
|
||||
|
||||
@baudrate.setter
|
||||
def baudrate(self, baudrate):
|
||||
"""\
|
||||
Change baud rate. It raises a ValueError if the port is open and the
|
||||
baud rate is not possible. If the port is closed, then the value is
|
||||
accepted and the exception is raised when the port is opened.
|
||||
"""
|
||||
try:
|
||||
b = int(baudrate)
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
|
||||
else:
|
||||
if b < 0:
|
||||
raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
|
||||
self._baudrate = b
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def bytesize(self):
|
||||
"""Get the current byte size setting."""
|
||||
return self._bytesize
|
||||
|
||||
@bytesize.setter
|
||||
def bytesize(self, bytesize):
|
||||
"""Change byte size."""
|
||||
if bytesize not in self.BYTESIZES:
|
||||
raise ValueError("Not a valid byte size: {!r}".format(bytesize))
|
||||
self._bytesize = bytesize
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def exclusive(self):
|
||||
"""Get the current exclusive access setting."""
|
||||
return self._exclusive
|
||||
|
||||
@exclusive.setter
|
||||
def exclusive(self, exclusive):
|
||||
"""Change the exclusive access setting."""
|
||||
self._exclusive = exclusive
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def parity(self):
|
||||
"""Get the current parity setting."""
|
||||
return self._parity
|
||||
|
||||
@parity.setter
|
||||
def parity(self, parity):
|
||||
"""Change parity setting."""
|
||||
if parity not in self.PARITIES:
|
||||
raise ValueError("Not a valid parity: {!r}".format(parity))
|
||||
self._parity = parity
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def stopbits(self):
|
||||
"""Get the current stop bits setting."""
|
||||
return self._stopbits
|
||||
|
||||
@stopbits.setter
|
||||
def stopbits(self, stopbits):
|
||||
"""Change stop bits size."""
|
||||
if stopbits not in self.STOPBITS:
|
||||
raise ValueError("Not a valid stop bit size: {!r}".format(stopbits))
|
||||
self._stopbits = stopbits
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""Get the current timeout setting."""
|
||||
return self._timeout
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, timeout):
|
||||
"""Change timeout setting."""
|
||||
if timeout is not None:
|
||||
try:
|
||||
timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
if timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
self._timeout = timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def write_timeout(self):
|
||||
"""Get the current timeout setting."""
|
||||
return self._write_timeout
|
||||
|
||||
@write_timeout.setter
|
||||
def write_timeout(self, timeout):
|
||||
"""Change timeout setting."""
|
||||
if timeout is not None:
|
||||
if timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
try:
|
||||
timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
|
||||
self._write_timeout = timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def inter_byte_timeout(self):
|
||||
"""Get the current inter-character timeout setting."""
|
||||
return self._inter_byte_timeout
|
||||
|
||||
@inter_byte_timeout.setter
|
||||
def inter_byte_timeout(self, ic_timeout):
|
||||
"""Change inter-byte timeout setting."""
|
||||
if ic_timeout is not None:
|
||||
if ic_timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
|
||||
try:
|
||||
ic_timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
|
||||
|
||||
self._inter_byte_timeout = ic_timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def xonxoff(self):
|
||||
"""Get the current XON/XOFF setting."""
|
||||
return self._xonxoff
|
||||
|
||||
@xonxoff.setter
|
||||
def xonxoff(self, xonxoff):
|
||||
"""Change XON/XOFF setting."""
|
||||
self._xonxoff = xonxoff
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def rtscts(self):
|
||||
"""Get the current RTS/CTS flow control setting."""
|
||||
return self._rtscts
|
||||
|
||||
@rtscts.setter
|
||||
def rtscts(self, rtscts):
|
||||
"""Change RTS/CTS flow control setting."""
|
||||
self._rtscts = rtscts
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def dsrdtr(self):
|
||||
"""Get the current DSR/DTR flow control setting."""
|
||||
return self._dsrdtr
|
||||
|
||||
@dsrdtr.setter
|
||||
def dsrdtr(self, dsrdtr=None):
|
||||
"""Change DsrDtr flow control setting."""
|
||||
if dsrdtr is None:
|
||||
# if not set, keep backwards compatibility and follow rtscts setting
|
||||
self._dsrdtr = self._rtscts
|
||||
else:
|
||||
# if defined independently, follow its value
|
||||
self._dsrdtr = dsrdtr
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def rts(self):
|
||||
return self._rts_state
|
||||
|
||||
@rts.setter
|
||||
def rts(self, value):
|
||||
self._rts_state = value
|
||||
if self.is_open:
|
||||
self._update_rts_state()
|
||||
|
||||
@property
|
||||
def dtr(self):
|
||||
return self._dtr_state
|
||||
|
||||
@dtr.setter
|
||||
def dtr(self, value):
|
||||
self._dtr_state = value
|
||||
if self.is_open:
|
||||
self._update_dtr_state()
|
||||
|
||||
@property
|
||||
def break_condition(self):
|
||||
return self._break_state
|
||||
|
||||
@break_condition.setter
|
||||
def break_condition(self, value):
|
||||
self._break_state = value
|
||||
if self.is_open:
|
||||
self._update_break_state()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# functions useful for RS-485 adapters
|
||||
|
||||
@property
|
||||
def rs485_mode(self):
|
||||
"""\
|
||||
Enable RS485 mode and apply new settings, set to None to disable.
|
||||
See serial.rs485.RS485Settings for more info about the value.
|
||||
"""
|
||||
return self._rs485_mode
|
||||
|
||||
@rs485_mode.setter
|
||||
def rs485_mode(self, rs485_settings):
|
||||
self._rs485_mode = rs485_settings
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
_SAVED_SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff',
|
||||
'dsrdtr', 'rtscts', 'timeout', 'write_timeout',
|
||||
'inter_byte_timeout')
|
||||
|
||||
def get_settings(self):
|
||||
"""\
|
||||
Get current port settings as a dictionary. For use with
|
||||
apply_settings().
|
||||
"""
|
||||
return dict([(key, getattr(self, '_' + key)) for key in self._SAVED_SETTINGS])
|
||||
|
||||
def apply_settings(self, d):
|
||||
"""\
|
||||
Apply stored settings from a dictionary returned from
|
||||
get_settings(). It's allowed to delete keys from the dictionary. These
|
||||
values will simply left unchanged.
|
||||
"""
|
||||
for key in self._SAVED_SETTINGS:
|
||||
if key in d and d[key] != getattr(self, '_' + key): # check against internal "_" value
|
||||
setattr(self, key, d[key]) # set non "_" value to use properties write function
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of the current port settings and its state."""
|
||||
return '{name}<id=0x{id:x}, open={p.is_open}>(port={p.portstr!r}, ' \
|
||||
'baudrate={p.baudrate!r}, bytesize={p.bytesize!r}, parity={p.parity!r}, ' \
|
||||
'stopbits={p.stopbits!r}, timeout={p.timeout!r}, xonxoff={p.xonxoff!r}, ' \
|
||||
'rtscts={p.rtscts!r}, dsrdtr={p.dsrdtr!r})'.format(
|
||||
name=self.__class__.__name__, id=id(self), p=self)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# compatibility with io library
|
||||
# pylint: disable=invalid-name,missing-docstring
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def seekable(self):
|
||||
return False
|
||||
|
||||
def readinto(self, b):
|
||||
data = self.read(len(b))
|
||||
n = len(data)
|
||||
try:
|
||||
b[:n] = data
|
||||
except TypeError as err:
|
||||
import array
|
||||
if not isinstance(b, array.array):
|
||||
raise err
|
||||
b[:n] = array.array('b', data)
|
||||
return n
|
||||
|
||||
def close(self):
|
||||
# Do not call RawIOBase.close() as that will try to flush().
|
||||
pass
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
# Overrides RawIOBase.closed, as RawIOBase can only be closed once,
|
||||
# but a Serial object can be opened/closed multiple times.
|
||||
return not self.is_open
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# context manager
|
||||
|
||||
def __enter__(self):
|
||||
if self._port is not None and not self.is_open:
|
||||
self.open()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.close()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""\
|
||||
Send break condition. Timed, returns to idle state after given
|
||||
duration.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self.break_condition = True
|
||||
time.sleep(duration)
|
||||
self.break_condition = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# backwards compatibility / deprecated functions
|
||||
|
||||
def flushInput(self):
|
||||
self.reset_input_buffer()
|
||||
|
||||
def flushOutput(self):
|
||||
self.reset_output_buffer()
|
||||
|
||||
def inWaiting(self):
|
||||
return self.in_waiting
|
||||
|
||||
def sendBreak(self, duration=0.25):
|
||||
self.send_break(duration)
|
||||
|
||||
def setRTS(self, value=1):
|
||||
self.rts = value
|
||||
|
||||
def setDTR(self, value=1):
|
||||
self.dtr = value
|
||||
|
||||
def getCTS(self):
|
||||
return self.cts
|
||||
|
||||
def getDSR(self):
|
||||
return self.dsr
|
||||
|
||||
def getRI(self):
|
||||
return self.ri
|
||||
|
||||
def getCD(self):
|
||||
return self.cd
|
||||
|
||||
def setPort(self, port):
|
||||
self.port = port
|
||||
|
||||
@property
|
||||
def writeTimeout(self):
|
||||
return self.write_timeout
|
||||
|
||||
@writeTimeout.setter
|
||||
def writeTimeout(self, timeout):
|
||||
self.write_timeout = timeout
|
||||
|
||||
@property
|
||||
def interCharTimeout(self):
|
||||
return self.inter_byte_timeout
|
||||
|
||||
@interCharTimeout.setter
|
||||
def interCharTimeout(self, interCharTimeout):
|
||||
self.inter_byte_timeout = interCharTimeout
|
||||
|
||||
def getSettingsDict(self):
|
||||
return self.get_settings()
|
||||
|
||||
def applySettingsDict(self, d):
|
||||
self.apply_settings(d)
|
||||
|
||||
def isOpen(self):
|
||||
return self.is_open
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# additional functionality
|
||||
|
||||
def read_all(self):
|
||||
"""\
|
||||
Read all bytes currently available in the buffer of the OS.
|
||||
"""
|
||||
return self.read(self.in_waiting)
|
||||
|
||||
def read_until(self, expected=LF, size=None):
|
||||
"""\
|
||||
Read until an expected sequence is found (line feed by default), the size
|
||||
is exceeded or until timeout occurs.
|
||||
"""
|
||||
lenterm = len(expected)
|
||||
line = bytearray()
|
||||
timeout = Timeout(self._timeout)
|
||||
while True:
|
||||
c = self.read(1)
|
||||
if c:
|
||||
line += c
|
||||
if line[-lenterm:] == expected:
|
||||
break
|
||||
if size is not None and len(line) >= size:
|
||||
break
|
||||
else:
|
||||
break
|
||||
if timeout.expired():
|
||||
break
|
||||
return bytes(line)
|
||||
|
||||
def iread_until(self, *args, **kwargs):
|
||||
"""\
|
||||
Read lines, implemented as generator. It will raise StopIteration on
|
||||
timeout (empty read).
|
||||
"""
|
||||
while True:
|
||||
line = self.read_until(*args, **kwargs)
|
||||
if not line:
|
||||
break
|
||||
yield line
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
s = SerialBase()
|
||||
sys.stdout.write('port name: {}\n'.format(s.name))
|
||||
sys.stdout.write('baud rates: {}\n'.format(s.BAUDRATES))
|
||||
sys.stdout.write('byte sizes: {}\n'.format(s.BYTESIZES))
|
||||
sys.stdout.write('parities: {}\n'.format(s.PARITIES))
|
||||
sys.stdout.write('stop bits: {}\n'.format(s.STOPBITS))
|
||||
sys.stdout.write('{}\n'.format(s))
|
||||
@@ -1,477 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# backend for Windows ("win32" incl. 32/64 bit support)
|
||||
#
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# Initial patch to use ctypes by Giovanni Bajo <rasky@develer.com>
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# pylint: disable=invalid-name,too-few-public-methods
|
||||
import ctypes
|
||||
import time
|
||||
from serial import win32
|
||||
|
||||
import serial
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, PortNotOpenError, SerialTimeoutException
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation for Win32 based on ctypes."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._port_handle = None
|
||||
self._overlapped_read = None
|
||||
self._overlapped_write = None
|
||||
super(Serial, self).__init__(*args, **kwargs)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
# the "\\.\COMx" format is required for devices other than COM1-COM8
|
||||
# not all versions of windows seem to support this properly
|
||||
# so that the first few ports are used with the DOS device name
|
||||
port = self.name
|
||||
try:
|
||||
if port.upper().startswith('COM') and int(port[3:]) > 8:
|
||||
port = '\\\\.\\' + port
|
||||
except ValueError:
|
||||
# for like COMnotanumber
|
||||
pass
|
||||
self._port_handle = win32.CreateFile(
|
||||
port,
|
||||
win32.GENERIC_READ | win32.GENERIC_WRITE,
|
||||
0, # exclusive access
|
||||
None, # no security
|
||||
win32.OPEN_EXISTING,
|
||||
win32.FILE_ATTRIBUTE_NORMAL | win32.FILE_FLAG_OVERLAPPED,
|
||||
0)
|
||||
if self._port_handle == win32.INVALID_HANDLE_VALUE:
|
||||
self._port_handle = None # 'cause __del__ is called anyway
|
||||
raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError()))
|
||||
|
||||
try:
|
||||
self._overlapped_read = win32.OVERLAPPED()
|
||||
self._overlapped_read.hEvent = win32.CreateEvent(None, 1, 0, None)
|
||||
self._overlapped_write = win32.OVERLAPPED()
|
||||
#~ self._overlapped_write.hEvent = win32.CreateEvent(None, 1, 0, None)
|
||||
self._overlapped_write.hEvent = win32.CreateEvent(None, 0, 0, None)
|
||||
|
||||
# Setup a 4k buffer
|
||||
win32.SetupComm(self._port_handle, 4096, 4096)
|
||||
|
||||
# Save original timeout values:
|
||||
self._orgTimeouts = win32.COMMTIMEOUTS()
|
||||
win32.GetCommTimeouts(self._port_handle, ctypes.byref(self._orgTimeouts))
|
||||
|
||||
self._reconfigure_port()
|
||||
|
||||
# Clear buffers:
|
||||
# Remove anything that was there
|
||||
win32.PurgeComm(
|
||||
self._port_handle,
|
||||
win32.PURGE_TXCLEAR | win32.PURGE_TXABORT |
|
||||
win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
|
||||
except:
|
||||
try:
|
||||
self._close()
|
||||
except:
|
||||
# ignore any exception when closing the port
|
||||
# also to keep original exception that happened when setting up
|
||||
pass
|
||||
self._port_handle = None
|
||||
raise
|
||||
else:
|
||||
self.is_open = True
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self._port_handle:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
# Set Windows timeout values
|
||||
# timeouts is a tuple with the following items:
|
||||
# (ReadIntervalTimeout,ReadTotalTimeoutMultiplier,
|
||||
# ReadTotalTimeoutConstant,WriteTotalTimeoutMultiplier,
|
||||
# WriteTotalTimeoutConstant)
|
||||
timeouts = win32.COMMTIMEOUTS()
|
||||
if self._timeout is None:
|
||||
pass # default of all zeros is OK
|
||||
elif self._timeout == 0:
|
||||
timeouts.ReadIntervalTimeout = win32.MAXDWORD
|
||||
else:
|
||||
timeouts.ReadTotalTimeoutConstant = max(int(self._timeout * 1000), 1)
|
||||
if self._timeout != 0 and self._inter_byte_timeout is not None:
|
||||
timeouts.ReadIntervalTimeout = max(int(self._inter_byte_timeout * 1000), 1)
|
||||
|
||||
if self._write_timeout is None:
|
||||
pass
|
||||
elif self._write_timeout == 0:
|
||||
timeouts.WriteTotalTimeoutConstant = win32.MAXDWORD
|
||||
else:
|
||||
timeouts.WriteTotalTimeoutConstant = max(int(self._write_timeout * 1000), 1)
|
||||
win32.SetCommTimeouts(self._port_handle, ctypes.byref(timeouts))
|
||||
|
||||
win32.SetCommMask(self._port_handle, win32.EV_ERR)
|
||||
|
||||
# Setup the connection info.
|
||||
# Get state and modify it:
|
||||
comDCB = win32.DCB()
|
||||
win32.GetCommState(self._port_handle, ctypes.byref(comDCB))
|
||||
comDCB.BaudRate = self._baudrate
|
||||
|
||||
if self._bytesize == serial.FIVEBITS:
|
||||
comDCB.ByteSize = 5
|
||||
elif self._bytesize == serial.SIXBITS:
|
||||
comDCB.ByteSize = 6
|
||||
elif self._bytesize == serial.SEVENBITS:
|
||||
comDCB.ByteSize = 7
|
||||
elif self._bytesize == serial.EIGHTBITS:
|
||||
comDCB.ByteSize = 8
|
||||
else:
|
||||
raise ValueError("Unsupported number of data bits: {!r}".format(self._bytesize))
|
||||
|
||||
if self._parity == serial.PARITY_NONE:
|
||||
comDCB.Parity = win32.NOPARITY
|
||||
comDCB.fParity = 0 # Disable Parity Check
|
||||
elif self._parity == serial.PARITY_EVEN:
|
||||
comDCB.Parity = win32.EVENPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_ODD:
|
||||
comDCB.Parity = win32.ODDPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_MARK:
|
||||
comDCB.Parity = win32.MARKPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_SPACE:
|
||||
comDCB.Parity = win32.SPACEPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
else:
|
||||
raise ValueError("Unsupported parity mode: {!r}".format(self._parity))
|
||||
|
||||
if self._stopbits == serial.STOPBITS_ONE:
|
||||
comDCB.StopBits = win32.ONESTOPBIT
|
||||
elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
|
||||
comDCB.StopBits = win32.ONE5STOPBITS
|
||||
elif self._stopbits == serial.STOPBITS_TWO:
|
||||
comDCB.StopBits = win32.TWOSTOPBITS
|
||||
else:
|
||||
raise ValueError("Unsupported number of stop bits: {!r}".format(self._stopbits))
|
||||
|
||||
comDCB.fBinary = 1 # Enable Binary Transmission
|
||||
# Char. w/ Parity-Err are replaced with 0xff (if fErrorChar is set to TRUE)
|
||||
if self._rs485_mode is None:
|
||||
if self._rtscts:
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_HANDSHAKE
|
||||
else:
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_ENABLE if self._rts_state else win32.RTS_CONTROL_DISABLE
|
||||
comDCB.fOutxCtsFlow = self._rtscts
|
||||
else:
|
||||
# checks for unsupported settings
|
||||
# XXX verify if platform really does not have a setting for those
|
||||
if not self._rs485_mode.rts_level_for_tx:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.rts_level_for_tx: {!r} (only True is allowed)'.format(
|
||||
self._rs485_mode.rts_level_for_tx,))
|
||||
if self._rs485_mode.rts_level_for_rx:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.rts_level_for_rx: {!r} (only False is allowed)'.format(
|
||||
self._rs485_mode.rts_level_for_rx,))
|
||||
if self._rs485_mode.delay_before_tx is not None:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.delay_before_tx: {!r} (only None is allowed)'.format(
|
||||
self._rs485_mode.delay_before_tx,))
|
||||
if self._rs485_mode.delay_before_rx is not None:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.delay_before_rx: {!r} (only None is allowed)'.format(
|
||||
self._rs485_mode.delay_before_rx,))
|
||||
if self._rs485_mode.loopback:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.loopback: {!r} (only False is allowed)'.format(
|
||||
self._rs485_mode.loopback,))
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_TOGGLE
|
||||
comDCB.fOutxCtsFlow = 0
|
||||
|
||||
if self._dsrdtr:
|
||||
comDCB.fDtrControl = win32.DTR_CONTROL_HANDSHAKE
|
||||
else:
|
||||
comDCB.fDtrControl = win32.DTR_CONTROL_ENABLE if self._dtr_state else win32.DTR_CONTROL_DISABLE
|
||||
comDCB.fOutxDsrFlow = self._dsrdtr
|
||||
comDCB.fOutX = self._xonxoff
|
||||
comDCB.fInX = self._xonxoff
|
||||
comDCB.fNull = 0
|
||||
comDCB.fErrorChar = 0
|
||||
comDCB.fAbortOnError = 0
|
||||
comDCB.XonChar = serial.XON
|
||||
comDCB.XoffChar = serial.XOFF
|
||||
|
||||
if not win32.SetCommState(self._port_handle, ctypes.byref(comDCB)):
|
||||
raise SerialException(
|
||||
'Cannot configure port, something went wrong. '
|
||||
'Original message: {!r}'.format(ctypes.WinError()))
|
||||
|
||||
#~ def __del__(self):
|
||||
#~ self.close()
|
||||
|
||||
def _close(self):
|
||||
"""internal close port helper"""
|
||||
if self._port_handle is not None:
|
||||
# Restore original timeout values:
|
||||
win32.SetCommTimeouts(self._port_handle, self._orgTimeouts)
|
||||
if self._overlapped_read is not None:
|
||||
self.cancel_read()
|
||||
win32.CloseHandle(self._overlapped_read.hEvent)
|
||||
self._overlapped_read = None
|
||||
if self._overlapped_write is not None:
|
||||
self.cancel_write()
|
||||
win32.CloseHandle(self._overlapped_write.hEvent)
|
||||
self._overlapped_write = None
|
||||
win32.CloseHandle(self._port_handle)
|
||||
self._port_handle = None
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
self._close()
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
return comstat.cbInQue
|
||||
|
||||
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.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if size > 0:
|
||||
win32.ResetEvent(self._overlapped_read.hEvent)
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
n = min(comstat.cbInQue, size) if self.timeout == 0 else size
|
||||
if n > 0:
|
||||
buf = ctypes.create_string_buffer(n)
|
||||
rc = win32.DWORD()
|
||||
read_ok = win32.ReadFile(
|
||||
self._port_handle,
|
||||
buf,
|
||||
n,
|
||||
ctypes.byref(rc),
|
||||
ctypes.byref(self._overlapped_read))
|
||||
if not read_ok and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
raise SerialException("ReadFile failed ({!r})".format(ctypes.WinError()))
|
||||
result_ok = win32.GetOverlappedResult(
|
||||
self._port_handle,
|
||||
ctypes.byref(self._overlapped_read),
|
||||
ctypes.byref(rc),
|
||||
True)
|
||||
if not result_ok:
|
||||
if win32.GetLastError() != win32.ERROR_OPERATION_ABORTED:
|
||||
raise SerialException("GetOverlappedResult failed ({!r})".format(ctypes.WinError()))
|
||||
read = buf.raw[:rc.value]
|
||||
else:
|
||||
read = bytes()
|
||||
else:
|
||||
read = bytes()
|
||||
return bytes(read)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given byte string over the serial port."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
#~ if not isinstance(data, (bytes, bytearray)):
|
||||
#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
# convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview
|
||||
data = to_bytes(data)
|
||||
if data:
|
||||
#~ win32event.ResetEvent(self._overlapped_write.hEvent)
|
||||
n = win32.DWORD()
|
||||
success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write)
|
||||
if self._write_timeout != 0: # if blocking (None) or w/ write timeout (>0)
|
||||
if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))
|
||||
|
||||
# Wait for the write to complete.
|
||||
#~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE)
|
||||
win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True)
|
||||
if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED:
|
||||
return n.value # canceled IO is no error
|
||||
if n.value != len(data):
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
return n.value
|
||||
else:
|
||||
errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError()
|
||||
if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY,
|
||||
win32.ERROR_OPERATION_ABORTED):
|
||||
return 0
|
||||
elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
# no info on true length provided by OS function in async mode
|
||||
return len(data)
|
||||
else:
|
||||
raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))
|
||||
else:
|
||||
return 0
|
||||
|
||||
def flush(self):
|
||||
"""\
|
||||
Flush of file like objects. In this case, wait until all data
|
||||
is written.
|
||||
"""
|
||||
while self.out_waiting:
|
||||
time.sleep(0.05)
|
||||
# XXX could also use WaitCommEvent with mask EV_TXEMPTY, but it would
|
||||
# require overlapped IO and it's also only possible to set a single mask
|
||||
# on the port---
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
win32.PurgeComm(self._port_handle, win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and discarding all
|
||||
that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
win32.PurgeComm(self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT)
|
||||
|
||||
def _update_break_state(self):
|
||||
"""Set break: Controls TXD. When active, to transmitting is possible."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self._break_state:
|
||||
win32.SetCommBreak(self._port_handle)
|
||||
else:
|
||||
win32.ClearCommBreak(self._port_handle)
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self._rts_state:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETRTS)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.CLRRTS)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self._dtr_state:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETDTR)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.CLRDTR)
|
||||
|
||||
def _GetCommModemStatus(self):
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
stat = win32.DWORD()
|
||||
win32.GetCommModemStatus(self._port_handle, ctypes.byref(stat))
|
||||
return stat.value
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
return win32.MS_CTS_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
return win32.MS_DSR_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
return win32.MS_RING_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
return win32.MS_RLSD_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
# - - platform specific - - - -
|
||||
|
||||
def set_buffer_size(self, rx_size=4096, tx_size=None):
|
||||
"""\
|
||||
Recommend a buffer size to the driver (device driver can ignore this
|
||||
value). Must be called after the port is opened.
|
||||
"""
|
||||
if tx_size is None:
|
||||
tx_size = rx_size
|
||||
win32.SetupComm(self._port_handle, rx_size, tx_size)
|
||||
|
||||
def set_output_flow_control(self, enable=True):
|
||||
"""\
|
||||
Manually control flow - when software flow control is enabled.
|
||||
This will do the same as if XON (true) or XOFF (false) are received
|
||||
from the other device and control the transmission accordingly.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if enable:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETXON)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETXOFF)
|
||||
|
||||
@property
|
||||
def out_waiting(self):
|
||||
"""Return how many bytes the in the outgoing buffer"""
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
return comstat.cbOutQue
|
||||
|
||||
def _cancel_overlapped_io(self, overlapped):
|
||||
"""Cancel a blocking read operation, may be called from other thread"""
|
||||
# check if read operation is pending
|
||||
rc = win32.DWORD()
|
||||
err = win32.GetOverlappedResult(
|
||||
self._port_handle,
|
||||
ctypes.byref(overlapped),
|
||||
ctypes.byref(rc),
|
||||
False)
|
||||
if not err and win32.GetLastError() in (win32.ERROR_IO_PENDING, win32.ERROR_IO_INCOMPLETE):
|
||||
# cancel, ignoring any errors (e.g. it may just have finished on its own)
|
||||
win32.CancelIoEx(self._port_handle, overlapped)
|
||||
|
||||
def cancel_read(self):
|
||||
"""Cancel a blocking read operation, may be called from other thread"""
|
||||
self._cancel_overlapped_io(self._overlapped_read)
|
||||
|
||||
def cancel_write(self):
|
||||
"""Cancel a blocking write operation, may be called from other thread"""
|
||||
self._cancel_overlapped_io(self._overlapped_write)
|
||||
|
||||
@SerialBase.exclusive.setter
|
||||
def exclusive(self, exclusive):
|
||||
"""Change the exclusive access setting."""
|
||||
if exclusive is not None and not exclusive:
|
||||
raise ValueError('win32 only supports exclusive access (not: {})'.format(exclusive))
|
||||
else:
|
||||
serial.SerialBase.exclusive.__set__(self, exclusive)
|
||||
@@ -1,297 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Working with threading and pySerial
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Support threading with serial ports.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import serial
|
||||
import threading
|
||||
|
||||
|
||||
class Protocol(object):
|
||||
"""\
|
||||
Protocol as used by the ReaderThread. This base class provides empty
|
||||
implementations of all methods.
|
||||
"""
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Called when reader thread is started"""
|
||||
|
||||
def data_received(self, data):
|
||||
"""Called with snippets received from the serial port"""
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""\
|
||||
Called when the serial port is closed or the reader loop terminated
|
||||
otherwise.
|
||||
"""
|
||||
if isinstance(exc, Exception):
|
||||
raise exc
|
||||
|
||||
|
||||
class Packetizer(Protocol):
|
||||
"""
|
||||
Read binary packets from serial port. Packets are expected to be terminated
|
||||
with a TERMINATOR byte (null byte by default).
|
||||
|
||||
The class also keeps track of the transport.
|
||||
"""
|
||||
|
||||
TERMINATOR = b'\0'
|
||||
|
||||
def __init__(self):
|
||||
self.buffer = bytearray()
|
||||
self.transport = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Store transport"""
|
||||
self.transport = transport
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""Forget transport"""
|
||||
self.transport = None
|
||||
super(Packetizer, self).connection_lost(exc)
|
||||
|
||||
def data_received(self, data):
|
||||
"""Buffer received data, find TERMINATOR, call handle_packet"""
|
||||
self.buffer.extend(data)
|
||||
while self.TERMINATOR in self.buffer:
|
||||
packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
|
||||
self.handle_packet(packet)
|
||||
|
||||
def handle_packet(self, packet):
|
||||
"""Process packets - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_packet')
|
||||
|
||||
|
||||
class FramedPacket(Protocol):
|
||||
"""
|
||||
Read binary packets. Packets are expected to have a start and stop marker.
|
||||
|
||||
The class also keeps track of the transport.
|
||||
"""
|
||||
|
||||
START = b'('
|
||||
STOP = b')'
|
||||
|
||||
def __init__(self):
|
||||
self.packet = bytearray()
|
||||
self.in_packet = False
|
||||
self.transport = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Store transport"""
|
||||
self.transport = transport
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""Forget transport"""
|
||||
self.transport = None
|
||||
self.in_packet = False
|
||||
del self.packet[:]
|
||||
super(FramedPacket, self).connection_lost(exc)
|
||||
|
||||
def data_received(self, data):
|
||||
"""Find data enclosed in START/STOP, call handle_packet"""
|
||||
for byte in serial.iterbytes(data):
|
||||
if byte == self.START:
|
||||
self.in_packet = True
|
||||
elif byte == self.STOP:
|
||||
self.in_packet = False
|
||||
self.handle_packet(bytes(self.packet)) # make read-only copy
|
||||
del self.packet[:]
|
||||
elif self.in_packet:
|
||||
self.packet.extend(byte)
|
||||
else:
|
||||
self.handle_out_of_packet_data(byte)
|
||||
|
||||
def handle_packet(self, packet):
|
||||
"""Process packets - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_packet')
|
||||
|
||||
def handle_out_of_packet_data(self, data):
|
||||
"""Process data that is received outside of packets"""
|
||||
pass
|
||||
|
||||
|
||||
class LineReader(Packetizer):
|
||||
"""
|
||||
Read and write (Unicode) lines from/to serial port.
|
||||
The encoding is applied.
|
||||
"""
|
||||
|
||||
TERMINATOR = b'\r\n'
|
||||
ENCODING = 'utf-8'
|
||||
UNICODE_HANDLING = 'replace'
|
||||
|
||||
def handle_packet(self, packet):
|
||||
self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING))
|
||||
|
||||
def handle_line(self, line):
|
||||
"""Process one line - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_line')
|
||||
|
||||
def write_line(self, text):
|
||||
"""
|
||||
Write text to the transport. ``text`` is a Unicode string and the encoding
|
||||
is applied before sending and also the newline is append.
|
||||
"""
|
||||
# + is not the best choice but bytes does not support % or .format in py3 and we want a single write call
|
||||
self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR)
|
||||
|
||||
|
||||
class ReaderThread(threading.Thread):
|
||||
"""\
|
||||
Implement a serial port read loop and dispatch to a Protocol instance (like
|
||||
the asyncio.Protocol) but do it with threads.
|
||||
|
||||
Calls to close() will close the serial port but it is also possible to just
|
||||
stop() this thread and continue the serial port instance otherwise.
|
||||
"""
|
||||
|
||||
def __init__(self, serial_instance, protocol_factory):
|
||||
"""\
|
||||
Initialize thread.
|
||||
|
||||
Note that the serial_instance' timeout is set to one second!
|
||||
Other settings are not changed.
|
||||
"""
|
||||
super(ReaderThread, self).__init__()
|
||||
self.daemon = True
|
||||
self.serial = serial_instance
|
||||
self.protocol_factory = protocol_factory
|
||||
self.alive = True
|
||||
self._lock = threading.Lock()
|
||||
self._connection_made = threading.Event()
|
||||
self.protocol = None
|
||||
|
||||
def stop(self):
|
||||
"""Stop the reader thread"""
|
||||
self.alive = False
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.join(2)
|
||||
|
||||
def run(self):
|
||||
"""Reader loop"""
|
||||
if not hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.timeout = 1
|
||||
self.protocol = self.protocol_factory()
|
||||
try:
|
||||
self.protocol.connection_made(self)
|
||||
except Exception as e:
|
||||
self.alive = False
|
||||
self.protocol.connection_lost(e)
|
||||
self._connection_made.set()
|
||||
return
|
||||
error = None
|
||||
self._connection_made.set()
|
||||
while self.alive and self.serial.is_open:
|
||||
try:
|
||||
# read all that is there or wait for one byte (blocking)
|
||||
data = self.serial.read(self.serial.in_waiting or 1)
|
||||
except serial.SerialException as e:
|
||||
# probably some I/O problem such as disconnected USB serial
|
||||
# adapters -> exit
|
||||
error = e
|
||||
break
|
||||
else:
|
||||
if data:
|
||||
# make a separated try-except for called user code
|
||||
try:
|
||||
self.protocol.data_received(data)
|
||||
except Exception as e:
|
||||
error = e
|
||||
break
|
||||
self.alive = False
|
||||
self.protocol.connection_lost(error)
|
||||
self.protocol = None
|
||||
|
||||
def write(self, data):
|
||||
"""Thread safe writing (uses lock)"""
|
||||
with self._lock:
|
||||
return self.serial.write(data)
|
||||
|
||||
def close(self):
|
||||
"""Close the serial port and exit reader thread (uses lock)"""
|
||||
# use the lock to let other threads finish writing
|
||||
with self._lock:
|
||||
# first stop reading, so that closing can be done on idle port
|
||||
self.stop()
|
||||
self.serial.close()
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Wait until connection is set up and return the transport and protocol
|
||||
instances.
|
||||
"""
|
||||
if self.alive:
|
||||
self._connection_made.wait()
|
||||
if not self.alive:
|
||||
raise RuntimeError('connection_lost already called')
|
||||
return (self, self.protocol)
|
||||
else:
|
||||
raise RuntimeError('already stopped')
|
||||
|
||||
# - - context manager, returns protocol
|
||||
|
||||
def __enter__(self):
|
||||
"""\
|
||||
Enter context handler. May raise RuntimeError in case the connection
|
||||
could not be created.
|
||||
"""
|
||||
self.start()
|
||||
self._connection_made.wait()
|
||||
if not self.alive:
|
||||
raise RuntimeError('connection_lost already called')
|
||||
return self.protocol
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Leave context: close port"""
|
||||
self.close()
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
# pylint: disable=wrong-import-position
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
#~ PORT = 'spy:///dev/ttyUSB0'
|
||||
PORT = 'loop://'
|
||||
|
||||
class PrintLines(LineReader):
|
||||
def connection_made(self, transport):
|
||||
super(PrintLines, self).connection_made(transport)
|
||||
sys.stdout.write('port opened\n')
|
||||
self.write_line('hello world')
|
||||
|
||||
def handle_line(self, data):
|
||||
sys.stdout.write('line received: {!r}\n'.format(data))
|
||||
|
||||
def connection_lost(self, exc):
|
||||
if exc:
|
||||
traceback.print_exc(exc)
|
||||
sys.stdout.write('port closed\n')
|
||||
|
||||
ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
|
||||
with ReaderThread(ser, PrintLines) as protocol:
|
||||
protocol.write_line('hello')
|
||||
time.sleep(2)
|
||||
|
||||
# alternative usage
|
||||
ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
|
||||
t = ReaderThread(ser, PrintLines)
|
||||
t.start()
|
||||
transport, protocol = t.connect()
|
||||
protocol.write_line('hello')
|
||||
time.sleep(2)
|
||||
t.close()
|
||||
@@ -1,126 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This is a codec to create and decode hexdumps with spaces between characters. used by miniterm.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Python 'hex' Codec - 2-digit hex with spaces content transfer encoding.
|
||||
|
||||
Encode and decode may be a bit misleading at first sight...
|
||||
|
||||
The textual representation is a hex dump: e.g. "40 41"
|
||||
The "encoded" data of this is the binary form, e.g. b"@A"
|
||||
|
||||
Therefore decoding is binary to text and thus converting binary data to hex dump.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import codecs
|
||||
import serial
|
||||
|
||||
|
||||
try:
|
||||
unicode
|
||||
except (NameError, AttributeError):
|
||||
unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name
|
||||
|
||||
|
||||
HEXDIGITS = '0123456789ABCDEF'
|
||||
|
||||
|
||||
# Codec APIs
|
||||
|
||||
def hex_encode(data, errors='strict'):
|
||||
"""'40 41 42' -> b'@ab'"""
|
||||
return (serial.to_bytes([int(h, 16) for h in data.split()]), len(data))
|
||||
|
||||
|
||||
def hex_decode(data, errors='strict'):
|
||||
"""b'@ab' -> '40 41 42'"""
|
||||
return (unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))), len(data))
|
||||
|
||||
|
||||
class Codec(codecs.Codec):
|
||||
def encode(self, data, errors='strict'):
|
||||
"""'40 41 42' -> b'@ab'"""
|
||||
return serial.to_bytes([int(h, 16) for h in data.split()])
|
||||
|
||||
def decode(self, data, errors='strict'):
|
||||
"""b'@ab' -> '40 41 42'"""
|
||||
return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data)))
|
||||
|
||||
|
||||
class IncrementalEncoder(codecs.IncrementalEncoder):
|
||||
"""Incremental hex encoder"""
|
||||
|
||||
def __init__(self, errors='strict'):
|
||||
self.errors = errors
|
||||
self.state = 0
|
||||
|
||||
def reset(self):
|
||||
self.state = 0
|
||||
|
||||
def getstate(self):
|
||||
return self.state
|
||||
|
||||
def setstate(self, state):
|
||||
self.state = state
|
||||
|
||||
def encode(self, data, final=False):
|
||||
"""\
|
||||
Incremental encode, keep track of digits and emit a byte when a pair
|
||||
of hex digits is found. The space is optional unless the error
|
||||
handling is defined to be 'strict'.
|
||||
"""
|
||||
state = self.state
|
||||
encoded = []
|
||||
for c in data.upper():
|
||||
if c in HEXDIGITS:
|
||||
z = HEXDIGITS.index(c)
|
||||
if state:
|
||||
encoded.append(z + (state & 0xf0))
|
||||
state = 0
|
||||
else:
|
||||
state = 0x100 + (z << 4)
|
||||
elif c == ' ': # allow spaces to separate values
|
||||
if state and self.errors == 'strict':
|
||||
raise UnicodeError('odd number of hex digits')
|
||||
state = 0
|
||||
else:
|
||||
if self.errors == 'strict':
|
||||
raise UnicodeError('non-hex digit found: {!r}'.format(c))
|
||||
self.state = state
|
||||
return serial.to_bytes(encoded)
|
||||
|
||||
|
||||
class IncrementalDecoder(codecs.IncrementalDecoder):
|
||||
"""Incremental decoder"""
|
||||
def decode(self, data, final=False):
|
||||
return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data)))
|
||||
|
||||
|
||||
class StreamWriter(Codec, codecs.StreamWriter):
|
||||
"""Combination of hexlify codec and StreamWriter"""
|
||||
|
||||
|
||||
class StreamReader(Codec, codecs.StreamReader):
|
||||
"""Combination of hexlify codec and StreamReader"""
|
||||
|
||||
|
||||
def getregentry():
|
||||
"""encodings module API"""
|
||||
return codecs.CodecInfo(
|
||||
name='hexlify',
|
||||
encode=hex_encode,
|
||||
decode=hex_decode,
|
||||
incrementalencoder=IncrementalEncoder,
|
||||
incrementaldecoder=IncrementalDecoder,
|
||||
streamwriter=StreamWriter,
|
||||
streamreader=StreamReader,
|
||||
#~ _is_text_encoding=True,
|
||||
)
|
||||
@@ -1,110 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Serial port enumeration. Console tool and backend selection.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
This module will provide a function called comports that returns an
|
||||
iterable (generator or list) that will enumerate available com ports. Note that
|
||||
on some systems non-existent ports may be listed.
|
||||
|
||||
Additionally a grep function is supplied that can be used to search for ports
|
||||
based on their descriptions or hardware ID.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
# chose an implementation, depending on os
|
||||
#~ if sys.platform == 'cli':
|
||||
#~ else:
|
||||
if os.name == 'nt': # sys.platform == 'win32':
|
||||
from serial.tools.list_ports_windows import comports
|
||||
elif os.name == 'posix':
|
||||
from serial.tools.list_ports_posix import comports
|
||||
#~ elif os.name == 'java':
|
||||
else:
|
||||
raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
def grep(regexp, include_links=False):
|
||||
"""\
|
||||
Search for ports using a regular expression. Port name, description and
|
||||
hardware ID are searched. The function returns an iterable that returns the
|
||||
same tuples as comport() would do.
|
||||
"""
|
||||
r = re.compile(regexp, re.I)
|
||||
for info in comports(include_links):
|
||||
port, desc, hwid = info
|
||||
if r.search(port) or r.search(desc) or r.search(hwid):
|
||||
yield info
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Serial port enumeration')
|
||||
|
||||
parser.add_argument(
|
||||
'regexp',
|
||||
nargs='?',
|
||||
help='only show ports that match this regex')
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='show more messages')
|
||||
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help='suppress all messages')
|
||||
|
||||
parser.add_argument(
|
||||
'-n',
|
||||
type=int,
|
||||
help='only output the N-th entry')
|
||||
|
||||
parser.add_argument(
|
||||
'-s', '--include-links',
|
||||
action='store_true',
|
||||
help='include entries that are symlinks to real devices')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
hits = 0
|
||||
# get iteraror w/ or w/o filter
|
||||
if args.regexp:
|
||||
if not args.quiet:
|
||||
sys.stderr.write("Filtered list with regexp: {!r}\n".format(args.regexp))
|
||||
iterator = sorted(grep(args.regexp, include_links=args.include_links))
|
||||
else:
|
||||
iterator = sorted(comports(include_links=args.include_links))
|
||||
# list them
|
||||
for n, (port, desc, hwid) in enumerate(iterator, 1):
|
||||
if args.n is None or args.n == n:
|
||||
sys.stdout.write("{:20}\n".format(port))
|
||||
if args.verbose:
|
||||
sys.stdout.write(" desc: {}\n".format(desc))
|
||||
sys.stdout.write(" hwid: {}\n".format(hwid))
|
||||
hits += 1
|
||||
if not args.quiet:
|
||||
if hits:
|
||||
sys.stderr.write("{} ports found\n".format(hits))
|
||||
else:
|
||||
sys.stderr.write("no ports found\n")
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a helper module for the various platform dependent list_port
|
||||
# implementations.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
|
||||
|
||||
def numsplit(text):
|
||||
"""\
|
||||
Convert string into a list of texts and numbers in order to support a
|
||||
natural sorting.
|
||||
"""
|
||||
result = []
|
||||
for group in re.split(r'(\d+)', text):
|
||||
if group:
|
||||
try:
|
||||
group = int(group)
|
||||
except ValueError:
|
||||
pass
|
||||
result.append(group)
|
||||
return result
|
||||
|
||||
|
||||
class ListPortInfo(object):
|
||||
"""Info collection base class for serial ports"""
|
||||
|
||||
def __init__(self, device, skip_link_detection=False):
|
||||
self.device = device
|
||||
self.name = os.path.basename(device)
|
||||
self.description = 'n/a'
|
||||
self.hwid = 'n/a'
|
||||
# USB specific data
|
||||
self.vid = None
|
||||
self.pid = None
|
||||
self.serial_number = None
|
||||
self.location = None
|
||||
self.manufacturer = None
|
||||
self.product = None
|
||||
self.interface = None
|
||||
# special handling for links
|
||||
if not skip_link_detection and device is not None and os.path.islink(device):
|
||||
self.hwid = 'LINK={}'.format(os.path.realpath(device))
|
||||
|
||||
def usb_description(self):
|
||||
"""return a short string to name the port based on USB info"""
|
||||
if self.interface is not None:
|
||||
return '{} - {}'.format(self.product, self.interface)
|
||||
elif self.product is not None:
|
||||
return self.product
|
||||
else:
|
||||
return self.name
|
||||
|
||||
def usb_info(self):
|
||||
"""return a string with USB related information about device"""
|
||||
return 'USB VID:PID={:04X}:{:04X}{}{}'.format(
|
||||
self.vid or 0,
|
||||
self.pid or 0,
|
||||
' SER={}'.format(self.serial_number) if self.serial_number is not None else '',
|
||||
' LOCATION={}'.format(self.location) if self.location is not None else '')
|
||||
|
||||
def apply_usb_info(self):
|
||||
"""update description and hwid from USB data"""
|
||||
self.description = self.usb_description()
|
||||
self.hwid = self.usb_info()
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, ListPortInfo) and self.device == other.device
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.device)
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, ListPortInfo):
|
||||
raise TypeError('unorderable types: {}() and {}()'.format(
|
||||
type(self).__name__,
|
||||
type(other).__name__))
|
||||
return numsplit(self.device) < numsplit(other.device)
|
||||
|
||||
def __str__(self):
|
||||
return '{} - {}'.format(self.device, self.description)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Item access: backwards compatible -> (port, desc, hwid)"""
|
||||
if index == 0:
|
||||
return self.device
|
||||
elif index == 1:
|
||||
return self.description
|
||||
elif index == 2:
|
||||
return self.hwid
|
||||
else:
|
||||
raise IndexError('{} > 2'.format(index))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def list_links(devices):
|
||||
"""\
|
||||
search all /dev devices and look for symlinks to known ports already
|
||||
listed in devices.
|
||||
"""
|
||||
links = []
|
||||
for device in glob.glob('/dev/*'):
|
||||
if os.path.islink(device) and os.path.realpath(device) in devices:
|
||||
links.append(device)
|
||||
return links
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
print(ListPortInfo('dummy'))
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports including details on
|
||||
# GNU/Linux systems.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import glob
|
||||
import os
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
|
||||
class SysFS(list_ports_common.ListPortInfo):
|
||||
"""Wrapper for easy sysfs access and device info"""
|
||||
|
||||
def __init__(self, device):
|
||||
super(SysFS, self).__init__(device)
|
||||
# special handling for links
|
||||
if device is not None and os.path.islink(device):
|
||||
device = os.path.realpath(device)
|
||||
is_link = True
|
||||
else:
|
||||
is_link = False
|
||||
self.usb_device_path = None
|
||||
if os.path.exists('/sys/class/tty/{}/device'.format(self.name)):
|
||||
self.device_path = os.path.realpath('/sys/class/tty/{}/device'.format(self.name))
|
||||
self.subsystem = os.path.basename(os.path.realpath(os.path.join(self.device_path, 'subsystem')))
|
||||
else:
|
||||
self.device_path = None
|
||||
self.subsystem = None
|
||||
# check device type
|
||||
if self.subsystem == 'usb-serial':
|
||||
self.usb_interface_path = os.path.dirname(self.device_path)
|
||||
elif self.subsystem == 'usb':
|
||||
self.usb_interface_path = self.device_path
|
||||
else:
|
||||
self.usb_interface_path = None
|
||||
# fill-in info for USB devices
|
||||
if self.usb_interface_path is not None:
|
||||
self.usb_device_path = os.path.dirname(self.usb_interface_path)
|
||||
|
||||
try:
|
||||
num_if = int(self.read_line(self.usb_device_path, 'bNumInterfaces'))
|
||||
except ValueError:
|
||||
num_if = 1
|
||||
|
||||
self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16)
|
||||
self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16)
|
||||
self.serial_number = self.read_line(self.usb_device_path, 'serial')
|
||||
if num_if > 1: # multi interface devices like FT4232
|
||||
self.location = os.path.basename(self.usb_interface_path)
|
||||
else:
|
||||
self.location = os.path.basename(self.usb_device_path)
|
||||
|
||||
self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer')
|
||||
self.product = self.read_line(self.usb_device_path, 'product')
|
||||
self.interface = self.read_line(self.usb_interface_path, 'interface')
|
||||
|
||||
if self.subsystem in ('usb', 'usb-serial'):
|
||||
self.apply_usb_info()
|
||||
#~ elif self.subsystem in ('pnp', 'amba'): # PCI based devices, raspi
|
||||
elif self.subsystem == 'pnp': # PCI based devices
|
||||
self.description = self.name
|
||||
self.hwid = self.read_line(self.device_path, 'id')
|
||||
elif self.subsystem == 'amba': # raspi
|
||||
self.description = self.name
|
||||
self.hwid = os.path.basename(self.device_path)
|
||||
|
||||
if is_link:
|
||||
self.hwid += ' LINK={}'.format(device)
|
||||
|
||||
def read_line(self, *args):
|
||||
"""\
|
||||
Helper function to read a single line from a file.
|
||||
One or more parameters are allowed, they are joined with os.path.join.
|
||||
Returns None on errors..
|
||||
"""
|
||||
try:
|
||||
with open(os.path.join(*args)) as f:
|
||||
line = f.readline().strip()
|
||||
return line
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
|
||||
def comports(include_links=False):
|
||||
devices = set()
|
||||
devices.update(glob.glob('/dev/ttyS*')) # built-in serial ports
|
||||
devices.update(glob.glob('/dev/ttyUSB*')) # usb-serial with own driver
|
||||
devices.update(glob.glob('/dev/ttyXRUSB*')) # xr-usb-serial port exar (DELL Edge 3001)
|
||||
devices.update(glob.glob('/dev/ttyACM*')) # usb-serial with CDC-ACM profile
|
||||
devices.update(glob.glob('/dev/ttyAMA*')) # ARM internal port (raspi)
|
||||
devices.update(glob.glob('/dev/rfcomm*')) # BT serial devices
|
||||
devices.update(glob.glob('/dev/ttyAP*')) # Advantech multi-port serial controllers
|
||||
devices.update(glob.glob('/dev/ttyGS*')) # https://www.kernel.org/doc/Documentation/usb/gadget_serial.txt
|
||||
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [info
|
||||
for info in [SysFS(d) for d in devices]
|
||||
if info.subsystem != "platform"] # hide non-present internal serial ports
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
for info in sorted(comports()):
|
||||
print("{0}: {0.subsystem}".format(info))
|
||||
@@ -1,299 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports including details on OSX
|
||||
#
|
||||
# code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools
|
||||
# with contributions from cibomahto, dgs3, FarMcKon, tedbrandston
|
||||
# and modifications by cliechti, hoihu, hardkrash
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2013-2020
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
|
||||
# List all of the callout devices in OS/X by querying IOKit.
|
||||
|
||||
# See the following for a reference of how to do this:
|
||||
# http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD
|
||||
|
||||
# More help from darwin_hid.py
|
||||
|
||||
# Also see the 'IORegistryExplorer' for an idea of what we are actually searching
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import ctypes
|
||||
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
|
||||
cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')
|
||||
|
||||
# kIOMasterPortDefault is no longer exported in BigSur but no biggie, using NULL works just the same
|
||||
kIOMasterPortDefault = 0 # WAS: ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
|
||||
kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
|
||||
|
||||
kCFStringEncodingMacRoman = 0
|
||||
kCFStringEncodingUTF8 = 0x08000100
|
||||
|
||||
# defined in `IOKit/usb/USBSpec.h`
|
||||
kUSBVendorString = 'USB Vendor Name'
|
||||
kUSBSerialNumberString = 'USB Serial Number'
|
||||
|
||||
# `io_name_t` defined as `typedef char io_name_t[128];`
|
||||
# in `device/device_types.h`
|
||||
io_name_size = 128
|
||||
|
||||
# defined in `mach/kern_return.h`
|
||||
KERN_SUCCESS = 0
|
||||
# kern_return_t defined as `typedef int kern_return_t;` in `mach/i386/kern_return.h`
|
||||
kern_return_t = ctypes.c_int
|
||||
|
||||
iokit.IOServiceMatching.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IOServiceGetMatchingServices.restype = kern_return_t
|
||||
|
||||
iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IOServiceGetMatchingServices.restype = kern_return_t
|
||||
|
||||
iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
|
||||
iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IORegistryEntryGetPath.restype = kern_return_t
|
||||
|
||||
iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IORegistryEntryGetName.restype = kern_return_t
|
||||
|
||||
iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IOObjectGetClass.restype = kern_return_t
|
||||
|
||||
iokit.IOObjectRelease.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32]
|
||||
cf.CFStringCreateWithCString.restype = ctypes.c_void_p
|
||||
|
||||
cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
|
||||
cf.CFStringGetCStringPtr.restype = ctypes.c_char_p
|
||||
|
||||
cf.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_uint32]
|
||||
cf.CFStringGetCString.restype = ctypes.c_bool
|
||||
|
||||
cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p]
|
||||
cf.CFNumberGetValue.restype = ctypes.c_void_p
|
||||
|
||||
# void CFRelease ( CFTypeRef cf );
|
||||
cf.CFRelease.argtypes = [ctypes.c_void_p]
|
||||
cf.CFRelease.restype = None
|
||||
|
||||
# CFNumber type defines
|
||||
kCFNumberSInt8Type = 1
|
||||
kCFNumberSInt16Type = 2
|
||||
kCFNumberSInt32Type = 3
|
||||
kCFNumberSInt64Type = 4
|
||||
|
||||
|
||||
def get_string_property(device_type, property):
|
||||
"""
|
||||
Search the given device for the specified string property
|
||||
|
||||
@param device_type Type of Device
|
||||
@param property String to search for
|
||||
@return Python string containing the value, or None if not found.
|
||||
"""
|
||||
key = cf.CFStringCreateWithCString(
|
||||
kCFAllocatorDefault,
|
||||
property.encode("utf-8"),
|
||||
kCFStringEncodingUTF8)
|
||||
|
||||
CFContainer = iokit.IORegistryEntryCreateCFProperty(
|
||||
device_type,
|
||||
key,
|
||||
kCFAllocatorDefault,
|
||||
0)
|
||||
output = None
|
||||
|
||||
if CFContainer:
|
||||
output = cf.CFStringGetCStringPtr(CFContainer, 0)
|
||||
if output is not None:
|
||||
output = output.decode('utf-8')
|
||||
else:
|
||||
buffer = ctypes.create_string_buffer(io_name_size);
|
||||
success = cf.CFStringGetCString(CFContainer, ctypes.byref(buffer), io_name_size, kCFStringEncodingUTF8)
|
||||
if success:
|
||||
output = buffer.value.decode('utf-8')
|
||||
cf.CFRelease(CFContainer)
|
||||
return output
|
||||
|
||||
|
||||
def get_int_property(device_type, property, cf_number_type):
|
||||
"""
|
||||
Search the given device for the specified string property
|
||||
|
||||
@param device_type Device to search
|
||||
@param property String to search for
|
||||
@param cf_number_type CFType number
|
||||
|
||||
@return Python string containing the value, or None if not found.
|
||||
"""
|
||||
key = cf.CFStringCreateWithCString(
|
||||
kCFAllocatorDefault,
|
||||
property.encode("utf-8"),
|
||||
kCFStringEncodingUTF8)
|
||||
|
||||
CFContainer = iokit.IORegistryEntryCreateCFProperty(
|
||||
device_type,
|
||||
key,
|
||||
kCFAllocatorDefault,
|
||||
0)
|
||||
|
||||
if CFContainer:
|
||||
if (cf_number_type == kCFNumberSInt32Type):
|
||||
number = ctypes.c_uint32()
|
||||
elif (cf_number_type == kCFNumberSInt16Type):
|
||||
number = ctypes.c_uint16()
|
||||
cf.CFNumberGetValue(CFContainer, cf_number_type, ctypes.byref(number))
|
||||
cf.CFRelease(CFContainer)
|
||||
return number.value
|
||||
return None
|
||||
|
||||
def IORegistryEntryGetName(device):
|
||||
devicename = ctypes.create_string_buffer(io_name_size);
|
||||
res = iokit.IORegistryEntryGetName(device, ctypes.byref(devicename))
|
||||
if res != KERN_SUCCESS:
|
||||
return None
|
||||
# this works in python2 but may not be valid. Also I don't know if
|
||||
# this encoding is guaranteed. It may be dependent on system locale.
|
||||
return devicename.value.decode('utf-8')
|
||||
|
||||
def IOObjectGetClass(device):
|
||||
classname = ctypes.create_string_buffer(io_name_size)
|
||||
iokit.IOObjectGetClass(device, ctypes.byref(classname))
|
||||
return classname.value
|
||||
|
||||
def GetParentDeviceByType(device, parent_type):
|
||||
""" Find the first parent of a device that implements the parent_type
|
||||
@param IOService Service to inspect
|
||||
@return Pointer to the parent type, or None if it was not found.
|
||||
"""
|
||||
# First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice.
|
||||
parent_type = parent_type.encode('utf-8')
|
||||
while IOObjectGetClass(device) != parent_type:
|
||||
parent = ctypes.c_void_p()
|
||||
response = iokit.IORegistryEntryGetParentEntry(
|
||||
device,
|
||||
"IOService".encode("utf-8"),
|
||||
ctypes.byref(parent))
|
||||
# If we weren't able to find a parent for the device, we're done.
|
||||
if response != KERN_SUCCESS:
|
||||
return None
|
||||
device = parent
|
||||
return device
|
||||
|
||||
|
||||
def GetIOServicesByType(service_type):
|
||||
"""
|
||||
returns iterator over specified service_type
|
||||
"""
|
||||
serial_port_iterator = ctypes.c_void_p()
|
||||
|
||||
iokit.IOServiceGetMatchingServices(
|
||||
kIOMasterPortDefault,
|
||||
iokit.IOServiceMatching(service_type.encode('utf-8')),
|
||||
ctypes.byref(serial_port_iterator))
|
||||
|
||||
services = []
|
||||
while iokit.IOIteratorIsValid(serial_port_iterator):
|
||||
service = iokit.IOIteratorNext(serial_port_iterator)
|
||||
if not service:
|
||||
break
|
||||
services.append(service)
|
||||
iokit.IOObjectRelease(serial_port_iterator)
|
||||
return services
|
||||
|
||||
|
||||
def location_to_string(locationID):
|
||||
"""
|
||||
helper to calculate port and bus number from locationID
|
||||
"""
|
||||
loc = ['{}-'.format(locationID >> 24)]
|
||||
while locationID & 0xf00000:
|
||||
if len(loc) > 1:
|
||||
loc.append('.')
|
||||
loc.append('{}'.format((locationID >> 20) & 0xf))
|
||||
locationID <<= 4
|
||||
return ''.join(loc)
|
||||
|
||||
|
||||
class SuitableSerialInterface(object):
|
||||
pass
|
||||
|
||||
|
||||
def scan_interfaces():
|
||||
"""
|
||||
helper function to scan USB interfaces
|
||||
returns a list of SuitableSerialInterface objects with name and id attributes
|
||||
"""
|
||||
interfaces = []
|
||||
for service in GetIOServicesByType('IOSerialBSDClient'):
|
||||
device = get_string_property(service, "IOCalloutDevice")
|
||||
if device:
|
||||
usb_device = GetParentDeviceByType(service, "IOUSBInterface")
|
||||
if usb_device:
|
||||
name = get_string_property(usb_device, "USB Interface Name") or None
|
||||
locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) or ''
|
||||
i = SuitableSerialInterface()
|
||||
i.id = locationID
|
||||
i.name = name
|
||||
interfaces.append(i)
|
||||
return interfaces
|
||||
|
||||
|
||||
def search_for_locationID_in_interfaces(serial_interfaces, locationID):
|
||||
for interface in serial_interfaces:
|
||||
if (interface.id == locationID):
|
||||
return interface.name
|
||||
return None
|
||||
|
||||
|
||||
def comports(include_links=False):
|
||||
# XXX include_links is currently ignored. are links in /dev even supported here?
|
||||
# Scan for all iokit serial ports
|
||||
services = GetIOServicesByType('IOSerialBSDClient')
|
||||
ports = []
|
||||
serial_interfaces = scan_interfaces()
|
||||
for service in services:
|
||||
# First, add the callout device file.
|
||||
device = get_string_property(service, "IOCalloutDevice")
|
||||
if device:
|
||||
info = list_ports_common.ListPortInfo(device)
|
||||
# If the serial port is implemented by IOUSBDevice
|
||||
# NOTE IOUSBDevice was deprecated as of 10.11 and finally on Apple Silicon
|
||||
# devices has been completely removed. Thanks to @oskay for this patch.
|
||||
usb_device = GetParentDeviceByType(service, "IOUSBHostDevice")
|
||||
if not usb_device:
|
||||
usb_device = GetParentDeviceByType(service, "IOUSBDevice")
|
||||
if usb_device:
|
||||
# fetch some useful information from properties
|
||||
info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type)
|
||||
info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type)
|
||||
info.serial_number = get_string_property(usb_device, kUSBSerialNumberString)
|
||||
# We know this is a usb device, so the
|
||||
# IORegistryEntryName should always be aliased to the
|
||||
# usb product name string descriptor.
|
||||
info.product = IORegistryEntryGetName(usb_device) or 'n/a'
|
||||
info.manufacturer = get_string_property(usb_device, kUSBVendorString)
|
||||
locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type)
|
||||
info.location = location_to_string(locationID)
|
||||
info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID)
|
||||
info.apply_usb_info()
|
||||
ports.append(info)
|
||||
return ports
|
||||
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
for port, desc, hwid in sorted(comports()):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
@@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports on POSIXy systems.
|
||||
# For some specific implementations, see also list_ports_linux, list_ports_osx
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
The ``comports`` function is expected to return an iterable that yields tuples
|
||||
of 3 strings: port name, human readable description and a hardware ID.
|
||||
|
||||
As currently no method is known to get the second two strings easily, they are
|
||||
currently just identical to the port name.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import glob
|
||||
import sys
|
||||
import os
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
# try to detect the OS so that a device can be selected...
|
||||
plat = sys.platform.lower()
|
||||
|
||||
if plat[:5] == 'linux': # Linux (confirmed) # noqa
|
||||
from serial.tools.list_ports_linux import comports
|
||||
|
||||
elif plat[:6] == 'darwin': # OS X (confirmed)
|
||||
from serial.tools.list_ports_osx import comports
|
||||
|
||||
elif plat == 'cygwin': # cygwin/win32
|
||||
# cygwin accepts /dev/com* in many contexts
|
||||
# (such as 'open' call, explicit 'ls'), but 'glob.glob'
|
||||
# and bare 'ls' do not; so use /dev/ttyS* instead
|
||||
def comports(include_links=False):
|
||||
devices = set(glob.glob('/dev/ttyS*'))
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:7] == 'openbsd': # OpenBSD
|
||||
def comports(include_links=False):
|
||||
devices = set(glob.glob('/dev/cua*'))
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:3] == 'bsd' or plat[:7] == 'freebsd':
|
||||
def comports(include_links=False):
|
||||
devices = set(glob.glob('/dev/cua*[!.init][!.lock]'))
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:6] == 'netbsd': # NetBSD
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = set(glob.glob('/dev/dty*'))
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:4] == 'irix': # IRIX
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = set(glob.glob('/dev/ttyf*'))
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:2] == 'hp': # HP-UX (not tested)
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = set(glob.glob('/dev/tty*p0'))
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:5] == 'sunos': # Solaris/SunOS
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = set(glob.glob('/dev/tty*c'))
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:3] == 'aix': # AIX
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = set(glob.glob('/dev/tty*'))
|
||||
if include_links:
|
||||
devices.update(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
else:
|
||||
# platform detection has failed...
|
||||
import serial
|
||||
sys.stderr.write("""\
|
||||
don't know how to enumerate ttys on this system.
|
||||
! I you know how the serial ports are named send this information to
|
||||
! the author of this module:
|
||||
|
||||
sys.platform = {!r}
|
||||
os.name = {!r}
|
||||
pySerial version = {}
|
||||
|
||||
also add the naming scheme of the serial ports and with a bit luck you can get
|
||||
this module running...
|
||||
""".format(sys.platform, os.name, serial.VERSION))
|
||||
raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
|
||||
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
for port, desc, hwid in sorted(comports()):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
@@ -1,427 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Enumerate serial ports on Windows including a human readable description
|
||||
# and hardware information.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# pylint: disable=invalid-name,too-few-public-methods
|
||||
import re
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL
|
||||
from ctypes.wintypes import HWND
|
||||
from ctypes.wintypes import DWORD
|
||||
from ctypes.wintypes import WORD
|
||||
from ctypes.wintypes import LONG
|
||||
from ctypes.wintypes import ULONG
|
||||
from ctypes.wintypes import HKEY
|
||||
from ctypes.wintypes import BYTE
|
||||
import serial
|
||||
from serial.win32 import ULONG_PTR
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
|
||||
def ValidHandle(value, func, arguments):
|
||||
if value == 0:
|
||||
raise ctypes.WinError()
|
||||
return value
|
||||
|
||||
|
||||
NULL = 0
|
||||
HDEVINFO = ctypes.c_void_p
|
||||
LPCTSTR = ctypes.c_wchar_p
|
||||
PCTSTR = ctypes.c_wchar_p
|
||||
PTSTR = ctypes.c_wchar_p
|
||||
LPDWORD = PDWORD = ctypes.POINTER(DWORD)
|
||||
#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE)
|
||||
LPBYTE = PBYTE = ctypes.c_void_p # XXX avoids error about types
|
||||
|
||||
ACCESS_MASK = DWORD
|
||||
REGSAM = ACCESS_MASK
|
||||
|
||||
|
||||
class GUID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Data1', DWORD),
|
||||
('Data2', WORD),
|
||||
('Data3', WORD),
|
||||
('Data4', BYTE * 8),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format(
|
||||
self.Data1,
|
||||
self.Data2,
|
||||
self.Data3,
|
||||
''.join(["{:02x}".format(d) for d in self.Data4[:2]]),
|
||||
''.join(["{:02x}".format(d) for d in self.Data4[2:]]),
|
||||
)
|
||||
|
||||
|
||||
class SP_DEVINFO_DATA(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('cbSize', DWORD),
|
||||
('ClassGuid', GUID),
|
||||
('DevInst', DWORD),
|
||||
('Reserved', ULONG_PTR),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "ClassGuid:{} DevInst:{}".format(self.ClassGuid, self.DevInst)
|
||||
|
||||
|
||||
PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA)
|
||||
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p
|
||||
|
||||
setupapi = ctypes.windll.LoadLibrary("setupapi")
|
||||
SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList
|
||||
SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO]
|
||||
SetupDiDestroyDeviceInfoList.restype = BOOL
|
||||
|
||||
SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameW
|
||||
SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD]
|
||||
SetupDiClassGuidsFromName.restype = BOOL
|
||||
|
||||
SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo
|
||||
SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA]
|
||||
SetupDiEnumDeviceInfo.restype = BOOL
|
||||
|
||||
SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW
|
||||
SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD]
|
||||
SetupDiGetClassDevs.restype = HDEVINFO
|
||||
SetupDiGetClassDevs.errcheck = ValidHandle
|
||||
|
||||
SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW
|
||||
SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD]
|
||||
SetupDiGetDeviceRegistryProperty.restype = BOOL
|
||||
|
||||
SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdW
|
||||
SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD]
|
||||
SetupDiGetDeviceInstanceId.restype = BOOL
|
||||
|
||||
SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey
|
||||
SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM]
|
||||
SetupDiOpenDevRegKey.restype = HKEY
|
||||
|
||||
advapi32 = ctypes.windll.LoadLibrary("Advapi32")
|
||||
RegCloseKey = advapi32.RegCloseKey
|
||||
RegCloseKey.argtypes = [HKEY]
|
||||
RegCloseKey.restype = LONG
|
||||
|
||||
RegQueryValueEx = advapi32.RegQueryValueExW
|
||||
RegQueryValueEx.argtypes = [HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD]
|
||||
RegQueryValueEx.restype = LONG
|
||||
|
||||
cfgmgr32 = ctypes.windll.LoadLibrary("Cfgmgr32")
|
||||
CM_Get_Parent = cfgmgr32.CM_Get_Parent
|
||||
CM_Get_Parent.argtypes = [PDWORD, DWORD, ULONG]
|
||||
CM_Get_Parent.restype = LONG
|
||||
|
||||
CM_Get_Device_IDW = cfgmgr32.CM_Get_Device_IDW
|
||||
CM_Get_Device_IDW.argtypes = [DWORD, PTSTR, ULONG, ULONG]
|
||||
CM_Get_Device_IDW.restype = LONG
|
||||
|
||||
CM_MapCrToWin32Err = cfgmgr32.CM_MapCrToWin32Err
|
||||
CM_MapCrToWin32Err.argtypes = [DWORD, DWORD]
|
||||
CM_MapCrToWin32Err.restype = DWORD
|
||||
|
||||
|
||||
DIGCF_PRESENT = 2
|
||||
DIGCF_DEVICEINTERFACE = 16
|
||||
INVALID_HANDLE_VALUE = 0
|
||||
ERROR_INSUFFICIENT_BUFFER = 122
|
||||
ERROR_NOT_FOUND = 1168
|
||||
SPDRP_HARDWAREID = 1
|
||||
SPDRP_FRIENDLYNAME = 12
|
||||
SPDRP_LOCATION_PATHS = 35
|
||||
SPDRP_MFG = 11
|
||||
DICS_FLAG_GLOBAL = 1
|
||||
DIREG_DEV = 0x00000001
|
||||
KEY_READ = 0x20019
|
||||
|
||||
|
||||
MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH = 5
|
||||
|
||||
|
||||
def get_parent_serial_number(child_devinst, child_vid, child_pid, depth=0, last_serial_number=None):
|
||||
""" Get the serial number of the parent of a device.
|
||||
|
||||
Args:
|
||||
child_devinst: The device instance handle to get the parent serial number of.
|
||||
child_vid: The vendor ID of the child device.
|
||||
child_pid: The product ID of the child device.
|
||||
depth: The current iteration depth of the USB device tree.
|
||||
"""
|
||||
|
||||
# If the traversal depth is beyond the max, abandon attempting to find the serial number.
|
||||
if depth > MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH:
|
||||
return '' if not last_serial_number else last_serial_number
|
||||
|
||||
# Get the parent device instance.
|
||||
devinst = DWORD()
|
||||
ret = CM_Get_Parent(ctypes.byref(devinst), child_devinst, 0)
|
||||
|
||||
if ret:
|
||||
win_error = CM_MapCrToWin32Err(DWORD(ret), DWORD(0))
|
||||
|
||||
# If there is no parent available, the child was the root device. We cannot traverse
|
||||
# further.
|
||||
if win_error == ERROR_NOT_FOUND:
|
||||
return '' if not last_serial_number else last_serial_number
|
||||
|
||||
raise ctypes.WinError(win_error)
|
||||
|
||||
# Get the ID of the parent device and parse it for vendor ID, product ID, and serial number.
|
||||
parentHardwareID = ctypes.create_unicode_buffer(250)
|
||||
|
||||
ret = CM_Get_Device_IDW(
|
||||
devinst,
|
||||
parentHardwareID,
|
||||
ctypes.sizeof(parentHardwareID) - 1,
|
||||
0)
|
||||
|
||||
if ret:
|
||||
raise ctypes.WinError(CM_MapCrToWin32Err(DWORD(ret), DWORD(0)))
|
||||
|
||||
parentHardwareID_str = parentHardwareID.value
|
||||
m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?',
|
||||
parentHardwareID_str,
|
||||
re.I)
|
||||
|
||||
# return early if we have no matches (likely malformed serial, traversed too far)
|
||||
if not m:
|
||||
return '' if not last_serial_number else last_serial_number
|
||||
|
||||
vid = None
|
||||
pid = None
|
||||
serial_number = None
|
||||
if m.group(1):
|
||||
vid = int(m.group(1), 16)
|
||||
if m.group(3):
|
||||
pid = int(m.group(3), 16)
|
||||
if m.group(7):
|
||||
serial_number = m.group(7)
|
||||
|
||||
# store what we found as a fallback for malformed serial values up the chain
|
||||
found_serial_number = serial_number
|
||||
|
||||
# Check that the USB serial number only contains alphanumeric characters. It may be a windows
|
||||
# device ID (ephemeral ID).
|
||||
if serial_number and not re.match(r'^\w+$', serial_number):
|
||||
serial_number = None
|
||||
|
||||
if not vid or not pid:
|
||||
# If pid and vid are not available at this device level, continue to the parent.
|
||||
return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number)
|
||||
|
||||
if pid != child_pid or vid != child_vid:
|
||||
# If the VID or PID has changed, we are no longer looking at the same physical device. The
|
||||
# serial number is unknown.
|
||||
return '' if not last_serial_number else last_serial_number
|
||||
|
||||
# In this case, the vid and pid of the parent device are identical to the child. However, if
|
||||
# there still isn't a serial number available, continue to the next parent.
|
||||
if not serial_number:
|
||||
return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number)
|
||||
|
||||
# Finally, the VID and PID are identical to the child and a serial number is present, so return
|
||||
# it.
|
||||
return serial_number
|
||||
|
||||
|
||||
def iterate_comports():
|
||||
"""Return a generator that yields descriptions for serial ports"""
|
||||
PortsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough...
|
||||
ports_guids_size = DWORD()
|
||||
if not SetupDiClassGuidsFromName(
|
||||
"Ports",
|
||||
PortsGUIDs,
|
||||
ctypes.sizeof(PortsGUIDs),
|
||||
ctypes.byref(ports_guids_size)):
|
||||
raise ctypes.WinError()
|
||||
|
||||
ModemsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough...
|
||||
modems_guids_size = DWORD()
|
||||
if not SetupDiClassGuidsFromName(
|
||||
"Modem",
|
||||
ModemsGUIDs,
|
||||
ctypes.sizeof(ModemsGUIDs),
|
||||
ctypes.byref(modems_guids_size)):
|
||||
raise ctypes.WinError()
|
||||
|
||||
GUIDs = PortsGUIDs[:ports_guids_size.value] + ModemsGUIDs[:modems_guids_size.value]
|
||||
|
||||
# repeat for all possible GUIDs
|
||||
for index in range(len(GUIDs)):
|
||||
bInterfaceNumber = None
|
||||
g_hdi = SetupDiGetClassDevs(
|
||||
ctypes.byref(GUIDs[index]),
|
||||
None,
|
||||
NULL,
|
||||
DIGCF_PRESENT) # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports
|
||||
|
||||
devinfo = SP_DEVINFO_DATA()
|
||||
devinfo.cbSize = ctypes.sizeof(devinfo)
|
||||
index = 0
|
||||
while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)):
|
||||
index += 1
|
||||
|
||||
# get the real com port name
|
||||
hkey = SetupDiOpenDevRegKey(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
DICS_FLAG_GLOBAL,
|
||||
0,
|
||||
DIREG_DEV, # DIREG_DRV for SW info
|
||||
KEY_READ)
|
||||
port_name_buffer = ctypes.create_unicode_buffer(250)
|
||||
port_name_length = ULONG(ctypes.sizeof(port_name_buffer))
|
||||
RegQueryValueEx(
|
||||
hkey,
|
||||
"PortName",
|
||||
None,
|
||||
None,
|
||||
ctypes.byref(port_name_buffer),
|
||||
ctypes.byref(port_name_length))
|
||||
RegCloseKey(hkey)
|
||||
|
||||
# unfortunately does this method also include parallel ports.
|
||||
# we could check for names starting with COM or just exclude LPT
|
||||
# and hope that other "unknown" names are serial ports...
|
||||
if port_name_buffer.value.startswith('LPT'):
|
||||
continue
|
||||
|
||||
# hardware ID
|
||||
szHardwareID = ctypes.create_unicode_buffer(250)
|
||||
# try to get ID that includes serial number
|
||||
if not SetupDiGetDeviceInstanceId(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
#~ ctypes.byref(szHardwareID),
|
||||
szHardwareID,
|
||||
ctypes.sizeof(szHardwareID) - 1,
|
||||
None):
|
||||
# fall back to more generic hardware ID if that would fail
|
||||
if not SetupDiGetDeviceRegistryProperty(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
SPDRP_HARDWAREID,
|
||||
None,
|
||||
ctypes.byref(szHardwareID),
|
||||
ctypes.sizeof(szHardwareID) - 1,
|
||||
None):
|
||||
# Ignore ERROR_INSUFFICIENT_BUFFER
|
||||
if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
|
||||
raise ctypes.WinError()
|
||||
# stringify
|
||||
szHardwareID_str = szHardwareID.value
|
||||
|
||||
info = list_ports_common.ListPortInfo(port_name_buffer.value, skip_link_detection=True)
|
||||
|
||||
# in case of USB, make a more readable string, similar to that form
|
||||
# that we also generate on other platforms
|
||||
if szHardwareID_str.startswith('USB'):
|
||||
m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?', szHardwareID_str, re.I)
|
||||
if m:
|
||||
info.vid = int(m.group(1), 16)
|
||||
if m.group(3):
|
||||
info.pid = int(m.group(3), 16)
|
||||
if m.group(5):
|
||||
bInterfaceNumber = int(m.group(5))
|
||||
|
||||
# Check that the USB serial number only contains alphanumeric characters. It
|
||||
# may be a windows device ID (ephemeral ID) for composite devices.
|
||||
if m.group(7) and re.match(r'^\w+$', m.group(7)):
|
||||
info.serial_number = m.group(7)
|
||||
else:
|
||||
info.serial_number = get_parent_serial_number(devinfo.DevInst, info.vid, info.pid)
|
||||
|
||||
# calculate a location string
|
||||
loc_path_str = ctypes.create_unicode_buffer(500)
|
||||
if SetupDiGetDeviceRegistryProperty(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
SPDRP_LOCATION_PATHS,
|
||||
None,
|
||||
ctypes.byref(loc_path_str),
|
||||
ctypes.sizeof(loc_path_str) - 1,
|
||||
None):
|
||||
m = re.finditer(r'USBROOT\((\w+)\)|#USB\((\w+)\)', loc_path_str.value)
|
||||
location = []
|
||||
for g in m:
|
||||
if g.group(1):
|
||||
location.append('{:d}'.format(int(g.group(1)) + 1))
|
||||
else:
|
||||
if len(location) > 1:
|
||||
location.append('.')
|
||||
else:
|
||||
location.append('-')
|
||||
location.append(g.group(2))
|
||||
if bInterfaceNumber is not None:
|
||||
location.append(':{}.{}'.format(
|
||||
'x', # XXX how to determine correct bConfigurationValue?
|
||||
bInterfaceNumber))
|
||||
if location:
|
||||
info.location = ''.join(location)
|
||||
info.hwid = info.usb_info()
|
||||
elif szHardwareID_str.startswith('FTDIBUS'):
|
||||
m = re.search(r'VID_([0-9a-f]{4})\+PID_([0-9a-f]{4})(\+(\w+))?', szHardwareID_str, re.I)
|
||||
if m:
|
||||
info.vid = int(m.group(1), 16)
|
||||
info.pid = int(m.group(2), 16)
|
||||
if m.group(4):
|
||||
info.serial_number = m.group(4)
|
||||
# USB location is hidden by FDTI driver :(
|
||||
info.hwid = info.usb_info()
|
||||
else:
|
||||
info.hwid = szHardwareID_str
|
||||
|
||||
# friendly name
|
||||
szFriendlyName = ctypes.create_unicode_buffer(250)
|
||||
if SetupDiGetDeviceRegistryProperty(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
SPDRP_FRIENDLYNAME,
|
||||
#~ SPDRP_DEVICEDESC,
|
||||
None,
|
||||
ctypes.byref(szFriendlyName),
|
||||
ctypes.sizeof(szFriendlyName) - 1,
|
||||
None):
|
||||
info.description = szFriendlyName.value
|
||||
#~ else:
|
||||
# Ignore ERROR_INSUFFICIENT_BUFFER
|
||||
#~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
|
||||
#~ raise IOError("failed to get details for %s (%s)" % (devinfo, szHardwareID.value))
|
||||
# ignore errors and still include the port in the list, friendly name will be same as port name
|
||||
|
||||
# manufacturer
|
||||
szManufacturer = ctypes.create_unicode_buffer(250)
|
||||
if SetupDiGetDeviceRegistryProperty(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
SPDRP_MFG,
|
||||
#~ SPDRP_DEVICEDESC,
|
||||
None,
|
||||
ctypes.byref(szManufacturer),
|
||||
ctypes.sizeof(szManufacturer) - 1,
|
||||
None):
|
||||
info.manufacturer = szManufacturer.value
|
||||
yield info
|
||||
SetupDiDestroyDeviceInfoList(g_hdi)
|
||||
|
||||
|
||||
def comports(include_links=False):
|
||||
"""Return a list of info objects about serial ports"""
|
||||
return list(iterate_comports())
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
for port, desc, hwid in sorted(comports()):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,57 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a special URL handler that allows selecting an
|
||||
# alternate implementation provided by some backends.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: alt://port[?option[=value][&option[=value]]]
|
||||
# options:
|
||||
# - class=X used class named X instead of Serial
|
||||
#
|
||||
# example:
|
||||
# use poll based implementation on Posix (Linux):
|
||||
# python -m serial.tools.miniterm alt:///dev/ttyUSB0?class=PosixPollSerial
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
import serial
|
||||
|
||||
|
||||
def serial_class_for_url(url):
|
||||
"""extract host and port from an URL string"""
|
||||
parts = urlparse.urlsplit(url)
|
||||
if parts.scheme != 'alt':
|
||||
raise serial.SerialException(
|
||||
'expected a string in the form "alt://port[?option[=value][&option[=value]]]": '
|
||||
'not starting with alt:// ({!r})'.format(parts.scheme))
|
||||
class_name = 'Serial'
|
||||
try:
|
||||
for option, values in urlparse.parse_qs(parts.query, True).items():
|
||||
if option == 'class':
|
||||
class_name = values[0]
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
except ValueError as e:
|
||||
raise serial.SerialException(
|
||||
'expected a string in the form '
|
||||
'"alt://port[?option[=value][&option[=value]]]": {!r}'.format(e))
|
||||
if not hasattr(serial, class_name):
|
||||
raise ValueError('unknown class: {!r}'.format(class_name))
|
||||
cls = getattr(serial, class_name)
|
||||
if not issubclass(cls, serial.Serial):
|
||||
raise ValueError('class {!r} is not an instance of Serial'.format(class_name))
|
||||
return (''.join([parts.netloc, parts.path]), cls)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
s = serial.serial_for_url('alt:///dev/ttyS0?class=PosixPollSerial')
|
||||
print(s)
|
||||
@@ -1,258 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Backend for Silicon Labs CP2110/4 HID-to-UART devices.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
# (C) 2019 Google LLC
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# This backend implements support for HID-to-UART devices manufactured
|
||||
# by Silicon Labs and marketed as CP2110 and CP2114. The
|
||||
# implementation is (mostly) OS-independent and in userland. It relies
|
||||
# on cython-hidapi (https://github.com/trezor/cython-hidapi).
|
||||
|
||||
# The HID-to-UART protocol implemented by CP2110/4 is described in the
|
||||
# AN434 document from Silicon Labs:
|
||||
# https://www.silabs.com/documents/public/application-notes/AN434-CP2110-4-Interface-Specification.pdf
|
||||
|
||||
# TODO items:
|
||||
|
||||
# - rtscts support is configured for hardware flow control, but the
|
||||
# signaling is missing (AN434 suggests this is done through GPIO).
|
||||
# - Cancelling reads and writes is not supported.
|
||||
# - Baudrate validation is not implemented, as it depends on model and configuration.
|
||||
|
||||
import struct
|
||||
import threading
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
try:
|
||||
import Queue
|
||||
except ImportError:
|
||||
import queue as Queue
|
||||
|
||||
import hid # hidapi
|
||||
|
||||
import serial
|
||||
from serial.serialutil import SerialBase, SerialException, PortNotOpenError, to_bytes, Timeout
|
||||
|
||||
|
||||
# Report IDs and related constant
|
||||
_REPORT_GETSET_UART_ENABLE = 0x41
|
||||
_DISABLE_UART = 0x00
|
||||
_ENABLE_UART = 0x01
|
||||
|
||||
_REPORT_SET_PURGE_FIFOS = 0x43
|
||||
_PURGE_TX_FIFO = 0x01
|
||||
_PURGE_RX_FIFO = 0x02
|
||||
|
||||
_REPORT_GETSET_UART_CONFIG = 0x50
|
||||
|
||||
_REPORT_SET_TRANSMIT_LINE_BREAK = 0x51
|
||||
_REPORT_SET_STOP_LINE_BREAK = 0x52
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
# This is not quite correct. AN343 specifies that the minimum
|
||||
# baudrate is different between CP2110 and CP2114, and it's halved
|
||||
# when using non-8-bit symbols.
|
||||
BAUDRATES = (300, 375, 600, 1200, 1800, 2400, 4800, 9600, 19200,
|
||||
38400, 57600, 115200, 230400, 460800, 500000, 576000,
|
||||
921600, 1000000)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._hid_handle = None
|
||||
self._read_buffer = None
|
||||
self._thread = None
|
||||
super(Serial, self).__init__(*args, **kwargs)
|
||||
|
||||
def open(self):
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
|
||||
self._read_buffer = Queue.Queue()
|
||||
|
||||
self._hid_handle = hid.device()
|
||||
try:
|
||||
portpath = self.from_url(self.portstr)
|
||||
self._hid_handle.open_path(portpath)
|
||||
except OSError as msg:
|
||||
raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
|
||||
|
||||
try:
|
||||
self._reconfigure_port()
|
||||
except:
|
||||
try:
|
||||
self._hid_handle.close()
|
||||
except:
|
||||
pass
|
||||
self._hid_handle = None
|
||||
raise
|
||||
else:
|
||||
self.is_open = True
|
||||
self._thread = threading.Thread(target=self._hid_read_loop)
|
||||
self._thread.daemon = True
|
||||
self._thread.setName('pySerial CP2110 reader thread for {}'.format(self._port))
|
||||
self._thread.start()
|
||||
|
||||
def from_url(self, url):
|
||||
parts = urlparse.urlsplit(url)
|
||||
if parts.scheme != "cp2110":
|
||||
raise SerialException(
|
||||
'expected a string in the forms '
|
||||
'"cp2110:///dev/hidraw9" or "cp2110://0001:0023:00": '
|
||||
'not starting with cp2110:// {{!r}}'.format(parts.scheme))
|
||||
if parts.netloc: # cp2100://BUS:DEVICE:ENDPOINT, for libusb
|
||||
return parts.netloc.encode('utf-8')
|
||||
return parts.path.encode('utf-8')
|
||||
|
||||
def close(self):
|
||||
self.is_open = False
|
||||
if self._thread:
|
||||
self._thread.join(1) # read timeout is 0.1
|
||||
self._thread = None
|
||||
self._hid_handle.close()
|
||||
self._hid_handle = None
|
||||
|
||||
def _reconfigure_port(self):
|
||||
parity_value = None
|
||||
if self._parity == serial.PARITY_NONE:
|
||||
parity_value = 0x00
|
||||
elif self._parity == serial.PARITY_ODD:
|
||||
parity_value = 0x01
|
||||
elif self._parity == serial.PARITY_EVEN:
|
||||
parity_value = 0x02
|
||||
elif self._parity == serial.PARITY_MARK:
|
||||
parity_value = 0x03
|
||||
elif self._parity == serial.PARITY_SPACE:
|
||||
parity_value = 0x04
|
||||
else:
|
||||
raise ValueError('Invalid parity: {!r}'.format(self._parity))
|
||||
|
||||
if self.rtscts:
|
||||
flow_control_value = 0x01
|
||||
else:
|
||||
flow_control_value = 0x00
|
||||
|
||||
data_bits_value = None
|
||||
if self._bytesize == 5:
|
||||
data_bits_value = 0x00
|
||||
elif self._bytesize == 6:
|
||||
data_bits_value = 0x01
|
||||
elif self._bytesize == 7:
|
||||
data_bits_value = 0x02
|
||||
elif self._bytesize == 8:
|
||||
data_bits_value = 0x03
|
||||
else:
|
||||
raise ValueError('Invalid char len: {!r}'.format(self._bytesize))
|
||||
|
||||
stop_bits_value = None
|
||||
if self._stopbits == serial.STOPBITS_ONE:
|
||||
stop_bits_value = 0x00
|
||||
elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
|
||||
stop_bits_value = 0x01
|
||||
elif self._stopbits == serial.STOPBITS_TWO:
|
||||
stop_bits_value = 0x01
|
||||
else:
|
||||
raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits))
|
||||
|
||||
configuration_report = struct.pack(
|
||||
'>BLBBBB',
|
||||
_REPORT_GETSET_UART_CONFIG,
|
||||
self._baudrate,
|
||||
parity_value,
|
||||
flow_control_value,
|
||||
data_bits_value,
|
||||
stop_bits_value)
|
||||
|
||||
self._hid_handle.send_feature_report(configuration_report)
|
||||
|
||||
self._hid_handle.send_feature_report(
|
||||
bytes((_REPORT_GETSET_UART_ENABLE, _ENABLE_UART)))
|
||||
self._update_break_state()
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
return self._read_buffer.qsize()
|
||||
|
||||
def reset_input_buffer(self):
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._hid_handle.send_feature_report(
|
||||
bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_RX_FIFO)))
|
||||
# empty read buffer
|
||||
while self._read_buffer.qsize():
|
||||
self._read_buffer.get(False)
|
||||
|
||||
def reset_output_buffer(self):
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._hid_handle.send_feature_report(
|
||||
bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_TX_FIFO)))
|
||||
|
||||
def _update_break_state(self):
|
||||
if not self._hid_handle:
|
||||
raise PortNotOpenError()
|
||||
|
||||
if self._break_state:
|
||||
self._hid_handle.send_feature_report(
|
||||
bytes((_REPORT_SET_TRANSMIT_LINE_BREAK, 0)))
|
||||
else:
|
||||
# Note that while AN434 states "There are no data bytes in
|
||||
# the payload other than the Report ID", either hidapi or
|
||||
# Linux does not seem to send the report otherwise.
|
||||
self._hid_handle.send_feature_report(
|
||||
bytes((_REPORT_SET_STOP_LINE_BREAK, 0)))
|
||||
|
||||
def read(self, size=1):
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
|
||||
data = bytearray()
|
||||
try:
|
||||
timeout = Timeout(self._timeout)
|
||||
while len(data) < size:
|
||||
if self._thread is None:
|
||||
raise SerialException('connection failed (reader thread died)')
|
||||
buf = self._read_buffer.get(True, timeout.time_left())
|
||||
if buf is None:
|
||||
return bytes(data)
|
||||
data += buf
|
||||
if timeout.expired():
|
||||
break
|
||||
except Queue.Empty: # -> timeout
|
||||
pass
|
||||
return bytes(data)
|
||||
|
||||
def write(self, data):
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
data = to_bytes(data)
|
||||
tx_len = len(data)
|
||||
while tx_len > 0:
|
||||
to_be_sent = min(tx_len, 0x3F)
|
||||
report = to_bytes([to_be_sent]) + data[:to_be_sent]
|
||||
self._hid_handle.write(report)
|
||||
|
||||
data = data[to_be_sent:]
|
||||
tx_len = len(data)
|
||||
|
||||
def _hid_read_loop(self):
|
||||
try:
|
||||
while self.is_open:
|
||||
data = self._hid_handle.read(64, timeout_ms=100)
|
||||
if not data:
|
||||
continue
|
||||
data_len = data.pop(0)
|
||||
assert data_len == len(data)
|
||||
self._read_buffer.put(bytearray(data))
|
||||
finally:
|
||||
self._thread = None
|
||||
@@ -1,91 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a special URL handler that uses the port listing to
|
||||
# find ports by searching the string descriptions.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: hwgrep://<regexp>&<option>
|
||||
#
|
||||
# where <regexp> is a Python regexp according to the re module
|
||||
#
|
||||
# violating the normal definition for URLs, the character `&` is used to
|
||||
# separate parameters from the arguments (instead of `?`, but the question mark
|
||||
# is heavily used in regexp'es)
|
||||
#
|
||||
# options:
|
||||
# n=<N> pick the N'th entry instead of the first one (numbering starts at 1)
|
||||
# skip_busy tries to open port to check if it is busy, fails on posix as ports are not locked!
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
basestring = str # python 3 pylint: disable=redefined-builtin
|
||||
|
||||
|
||||
class Serial(serial.Serial):
|
||||
"""Just inherit the native Serial port implementation and patch the port property."""
|
||||
# pylint: disable=no-member
|
||||
|
||||
@serial.Serial.port.setter
|
||||
def port(self, value):
|
||||
"""translate port name before storing it"""
|
||||
if isinstance(value, basestring) and value.startswith('hwgrep://'):
|
||||
serial.Serial.port.__set__(self, self.from_url(value))
|
||||
else:
|
||||
serial.Serial.port.__set__(self, value)
|
||||
|
||||
def from_url(self, url):
|
||||
"""extract host and port from an URL string"""
|
||||
if url.lower().startswith("hwgrep://"):
|
||||
url = url[9:]
|
||||
n = 0
|
||||
test_open = False
|
||||
args = url.split('&')
|
||||
regexp = args.pop(0)
|
||||
for arg in args:
|
||||
if '=' in arg:
|
||||
option, value = arg.split('=', 1)
|
||||
else:
|
||||
option = arg
|
||||
value = None
|
||||
if option == 'n':
|
||||
# pick n'th element
|
||||
n = int(value) - 1
|
||||
if n < 1:
|
||||
raise ValueError('option "n" expects a positive integer larger than 1: {!r}'.format(value))
|
||||
elif option == 'skip_busy':
|
||||
# open to test if port is available. not the nicest way..
|
||||
test_open = True
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
# use a for loop to get the 1st element from the generator
|
||||
for port, desc, hwid in sorted(serial.tools.list_ports.grep(regexp)):
|
||||
if test_open:
|
||||
try:
|
||||
s = serial.Serial(port)
|
||||
except serial.SerialException:
|
||||
# it has some error, skip this one
|
||||
continue
|
||||
else:
|
||||
s.close()
|
||||
if n:
|
||||
n -= 1
|
||||
continue
|
||||
return port
|
||||
else:
|
||||
raise serial.SerialException('no ports found matching regexp {!r}'.format(url))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
s = Serial(None)
|
||||
s.port = 'hwgrep://ttyS0'
|
||||
print(s)
|
||||
@@ -1,308 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a loop back connection receiving itself what it sent.
|
||||
#
|
||||
# The purpose of this module is.. well... You can run the unit tests with it.
|
||||
# and it was so easy to implement ;-)
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: loop://[option[/option...]]
|
||||
# options:
|
||||
# - "debug" print diagnostic messages
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import numbers
|
||||
import time
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
try:
|
||||
import queue
|
||||
except ImportError:
|
||||
import Queue as queue
|
||||
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, iterbytes, SerialTimeoutException, PortNotOpenError
|
||||
|
||||
# map log level names to constants. used in from_url()
|
||||
LOGGER_LEVELS = {
|
||||
'debug': logging.DEBUG,
|
||||
'info': logging.INFO,
|
||||
'warning': logging.WARNING,
|
||||
'error': logging.ERROR,
|
||||
}
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation that simulates a loop back connection in plain software."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.buffer_size = 4096
|
||||
self.queue = None
|
||||
self.logger = None
|
||||
self._cancel_write = False
|
||||
super(Serial, self).__init__(*args, **kwargs)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
self.logger = None
|
||||
self.queue = queue.Queue(self.buffer_size)
|
||||
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
# not that there is anything to open, but the function applies the
|
||||
# options found in the URL
|
||||
self.from_url(self.port)
|
||||
|
||||
# not that there anything to configure...
|
||||
self._reconfigure_port()
|
||||
# all things set up get, now a clean start
|
||||
self.is_open = True
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
self.reset_input_buffer()
|
||||
self.reset_output_buffer()
|
||||
|
||||
def close(self):
|
||||
if self.is_open:
|
||||
self.is_open = False
|
||||
try:
|
||||
self.queue.put_nowait(None)
|
||||
except queue.Full:
|
||||
pass
|
||||
super(Serial, self).close()
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""\
|
||||
Set communication parameters on opened port. For the loop://
|
||||
protocol all settings are ignored!
|
||||
"""
|
||||
# not that's it of any real use, but it helps in the unit tests
|
||||
if not isinstance(self._baudrate, numbers.Integral) or not 0 < self._baudrate < 2 ** 32:
|
||||
raise ValueError("invalid baudrate: {!r}".format(self._baudrate))
|
||||
if self.logger:
|
||||
self.logger.info('_reconfigure_port()')
|
||||
|
||||
def from_url(self, url):
|
||||
"""extract host and port from an URL string"""
|
||||
parts = urlparse.urlsplit(url)
|
||||
if parts.scheme != "loop":
|
||||
raise SerialException(
|
||||
'expected a string in the form '
|
||||
'"loop://[?logging={debug|info|warning|error}]": not starting '
|
||||
'with loop:// ({!r})'.format(parts.scheme))
|
||||
try:
|
||||
# process options now, directly altering self
|
||||
for option, values in urlparse.parse_qs(parts.query, True).items():
|
||||
if option == 'logging':
|
||||
logging.basicConfig() # XXX is that good to call it here?
|
||||
self.logger = logging.getLogger('pySerial.loop')
|
||||
self.logger.setLevel(LOGGER_LEVELS[values[0]])
|
||||
self.logger.debug('enabled logging')
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
except ValueError as e:
|
||||
raise SerialException(
|
||||
'expected a string in the form '
|
||||
'"loop://[?logging={debug|info|warning|error}]": {}'.format(e))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
# attention the logged value can differ from return value in
|
||||
# threaded environments...
|
||||
self.logger.debug('in_waiting -> {:d}'.format(self.queue.qsize()))
|
||||
return self.queue.qsize()
|
||||
|
||||
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.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self._timeout is not None and self._timeout != 0:
|
||||
timeout = time.time() + self._timeout
|
||||
else:
|
||||
timeout = None
|
||||
data = bytearray()
|
||||
while size > 0 and self.is_open:
|
||||
try:
|
||||
b = self.queue.get(timeout=self._timeout) # XXX inter char timeout
|
||||
except queue.Empty:
|
||||
if self._timeout == 0:
|
||||
break
|
||||
else:
|
||||
if b is not None:
|
||||
data += b
|
||||
size -= 1
|
||||
else:
|
||||
break
|
||||
# check for timeout now, after data has been read.
|
||||
# useful for timeout = 0 (non blocking) read
|
||||
if timeout and time.time() > timeout:
|
||||
if self.logger:
|
||||
self.logger.info('read timeout')
|
||||
break
|
||||
return bytes(data)
|
||||
|
||||
def cancel_read(self):
|
||||
self.queue.put_nowait(None)
|
||||
|
||||
def cancel_write(self):
|
||||
self._cancel_write = True
|
||||
|
||||
def write(self, data):
|
||||
"""\
|
||||
Output the given byte string over the serial port. Can block if the
|
||||
connection is blocked. May raise SerialException if the connection is
|
||||
closed.
|
||||
"""
|
||||
self._cancel_write = False
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
data = to_bytes(data)
|
||||
# calculate aprox time that would be used to send the data
|
||||
time_used_to_send = 10.0 * len(data) / self._baudrate
|
||||
# when a write timeout is configured check if we would be successful
|
||||
# (not sending anything, not even the part that would have time)
|
||||
if self._write_timeout is not None and time_used_to_send > self._write_timeout:
|
||||
# must wait so that unit test succeeds
|
||||
time_left = self._write_timeout
|
||||
while time_left > 0 and not self._cancel_write:
|
||||
time.sleep(min(time_left, 0.5))
|
||||
time_left -= 0.5
|
||||
if self._cancel_write:
|
||||
return 0 # XXX
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
for byte in iterbytes(data):
|
||||
self.queue.put(byte, timeout=self._write_timeout)
|
||||
return len(data)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('reset_input_buffer()')
|
||||
try:
|
||||
while self.queue.qsize():
|
||||
self.queue.get_nowait()
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('reset_output_buffer()')
|
||||
try:
|
||||
while self.queue.qsize():
|
||||
self.queue.get_nowait()
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
@property
|
||||
def out_waiting(self):
|
||||
"""Return how many bytes the in the outgoing buffer"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
# attention the logged value can differ from return value in
|
||||
# threaded environments...
|
||||
self.logger.debug('out_waiting -> {:d}'.format(self.queue.qsize()))
|
||||
return self.queue.qsize()
|
||||
|
||||
def _update_break_state(self):
|
||||
"""\
|
||||
Set break: Controls TXD. When active, to transmitting is
|
||||
possible.
|
||||
"""
|
||||
if self.logger:
|
||||
self.logger.info('_update_break_state({!r})'.format(self._break_state))
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self.logger:
|
||||
self.logger.info('_update_rts_state({!r}) -> state of CTS'.format(self._rts_state))
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self.logger:
|
||||
self.logger.info('_update_dtr_state({!r}) -> state of DSR'.format(self._dtr_state))
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('CTS -> state of RTS ({!r})'.format(self._rts_state))
|
||||
return self._rts_state
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if self.logger:
|
||||
self.logger.info('DSR -> state of DTR ({!r})'.format(self._dtr_state))
|
||||
return self._dtr_state
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for RI')
|
||||
return False
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for CD')
|
||||
return True
|
||||
|
||||
# - - - platform specific - - -
|
||||
# None so far
|
||||
|
||||
|
||||
# simple client test
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
s = Serial('loop://')
|
||||
sys.stdout.write('{}\n'.format(s))
|
||||
|
||||
sys.stdout.write("write...\n")
|
||||
s.write("hello\n")
|
||||
s.flush()
|
||||
sys.stdout.write("read: {!r}\n".format(s.read(5)))
|
||||
|
||||
s.close()
|
||||
@@ -1,12 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This is a thin wrapper to load the rfc2217 implementation.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from serial.rfc2217 import Serial # noqa
|
||||
@@ -1,359 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a simple socket based client.
|
||||
# It does not support changing any port parameters and will silently ignore any
|
||||
# requests to do so.
|
||||
#
|
||||
# The purpose of this module is that applications using pySerial can connect to
|
||||
# TCP/IP to serial port converters that do not support RFC 2217.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: socket://<host>:<port>[/option[/option...]]
|
||||
# options:
|
||||
# - "debug" print diagnostic messages
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, \
|
||||
PortNotOpenError, SerialTimeoutException, Timeout
|
||||
|
||||
# map log level names to constants. used in from_url()
|
||||
LOGGER_LEVELS = {
|
||||
'debug': logging.DEBUG,
|
||||
'info': logging.INFO,
|
||||
'warning': logging.WARNING,
|
||||
'error': logging.ERROR,
|
||||
}
|
||||
|
||||
POLL_TIMEOUT = 5
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation for plain sockets."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
self.logger = None
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
try:
|
||||
# timeout is used for write timeout support :/ and to get an initial connection timeout
|
||||
self._socket = socket.create_connection(self.from_url(self.portstr), timeout=POLL_TIMEOUT)
|
||||
except Exception as msg:
|
||||
self._socket = None
|
||||
raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
|
||||
# after connecting, switch to non-blocking, we're using select
|
||||
self._socket.setblocking(False)
|
||||
|
||||
# not that there is anything to configure...
|
||||
self._reconfigure_port()
|
||||
# all things set up get, now a clean start
|
||||
self.is_open = True
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
self.reset_input_buffer()
|
||||
self.reset_output_buffer()
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""\
|
||||
Set communication parameters on opened port. For the socket://
|
||||
protocol all settings are ignored!
|
||||
"""
|
||||
if self._socket is None:
|
||||
raise SerialException("Can only operate on open ports")
|
||||
if self.logger:
|
||||
self.logger.info('ignored port configuration change')
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self._socket:
|
||||
try:
|
||||
self._socket.shutdown(socket.SHUT_RDWR)
|
||||
self._socket.close()
|
||||
except:
|
||||
# ignore errors.
|
||||
pass
|
||||
self._socket = None
|
||||
self.is_open = False
|
||||
# in case of quick reconnects, give the server some time
|
||||
time.sleep(0.3)
|
||||
|
||||
def from_url(self, url):
|
||||
"""extract host and port from an URL string"""
|
||||
parts = urlparse.urlsplit(url)
|
||||
if parts.scheme != "socket":
|
||||
raise SerialException(
|
||||
'expected a string in the form '
|
||||
'"socket://<host>:<port>[?logging={debug|info|warning|error}]": '
|
||||
'not starting with socket:// ({!r})'.format(parts.scheme))
|
||||
try:
|
||||
# process options now, directly altering self
|
||||
for option, values in urlparse.parse_qs(parts.query, True).items():
|
||||
if option == 'logging':
|
||||
logging.basicConfig() # XXX is that good to call it here?
|
||||
self.logger = logging.getLogger('pySerial.socket')
|
||||
self.logger.setLevel(LOGGER_LEVELS[values[0]])
|
||||
self.logger.debug('enabled logging')
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
if not 0 <= parts.port < 65536:
|
||||
raise ValueError("port not in range 0...65535")
|
||||
except ValueError as e:
|
||||
raise SerialException(
|
||||
'expected a string in the form '
|
||||
'"socket://<host>:<port>[?logging={debug|info|warning|error}]": {}'.format(e))
|
||||
|
||||
return (parts.hostname, parts.port)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
# Poll the socket to see if it is ready for reading.
|
||||
# If ready, at least one byte will be to read.
|
||||
lr, lw, lx = select.select([self._socket], [], [], 0)
|
||||
return len(lr)
|
||||
|
||||
# select based implementation, similar to posix, but only using socket API
|
||||
# to be portable, additionally handle socket timeout which is used to
|
||||
# emulate write timeouts
|
||||
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.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
read = bytearray()
|
||||
timeout = Timeout(self._timeout)
|
||||
while len(read) < size:
|
||||
try:
|
||||
ready, _, _ = select.select([self._socket], [], [], timeout.time_left())
|
||||
# If select was used with a timeout, and the timeout occurs, it
|
||||
# returns with empty lists -> thus abort read operation.
|
||||
# For timeout == 0 (non-blocking operation) also abort when
|
||||
# there is nothing to read.
|
||||
if not ready:
|
||||
break # timeout
|
||||
buf = self._socket.recv(size - len(read))
|
||||
# read should always return some data as select reported it was
|
||||
# ready to read when we get to this point, unless it is EOF
|
||||
if not buf:
|
||||
raise SerialException('socket disconnected')
|
||||
read.extend(buf)
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
except (select.error, socket.error) as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
if timeout.expired():
|
||||
break
|
||||
return bytes(read)
|
||||
|
||||
def write(self, data):
|
||||
"""\
|
||||
Output the given byte string over the serial port. Can block if the
|
||||
connection is blocked. May raise SerialException if the connection is
|
||||
closed.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
|
||||
d = to_bytes(data)
|
||||
tx_len = length = len(d)
|
||||
timeout = Timeout(self._write_timeout)
|
||||
while tx_len > 0:
|
||||
try:
|
||||
n = self._socket.send(d)
|
||||
if timeout.is_non_blocking:
|
||||
# Zero timeout indicates non-blocking - simply return the
|
||||
# number of bytes of data actually written
|
||||
return n
|
||||
elif not timeout.is_infinite:
|
||||
# when timeout is set, use select to wait for being ready
|
||||
# with the time left as timeout
|
||||
if timeout.expired():
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
_, ready, _ = select.select([], [self._socket], [], timeout.time_left())
|
||||
if not ready:
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
else:
|
||||
assert timeout.time_left() is None
|
||||
# wait for write operation
|
||||
_, ready, _ = select.select([], [self._socket], [], None)
|
||||
if not ready:
|
||||
raise SerialException('write failed (select)')
|
||||
d = d[n:]
|
||||
tx_len -= n
|
||||
except SerialException:
|
||||
raise
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('write failed: {}'.format(e))
|
||||
except select.error as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('write failed: {}'.format(e))
|
||||
if not timeout.is_non_blocking and timeout.expired():
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
return length - len(d)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
|
||||
# just use recv to remove input, while there is some
|
||||
ready = True
|
||||
while ready:
|
||||
ready, _, _ = select.select([self._socket], [], [], 0)
|
||||
try:
|
||||
if ready:
|
||||
ready = self._socket.recv(4096)
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
except (select.error, socket.error) as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored reset_output_buffer')
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""\
|
||||
Send break condition. Timed, returns to idle state after given
|
||||
duration.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored send_break({!r})'.format(duration))
|
||||
|
||||
def _update_break_state(self):
|
||||
"""Set break: Controls TXD. When active, to transmitting is
|
||||
possible."""
|
||||
if self.logger:
|
||||
self.logger.info('ignored _update_break_state({!r})'.format(self._break_state))
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self.logger:
|
||||
self.logger.info('ignored _update_rts_state({!r})'.format(self._rts_state))
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self.logger:
|
||||
self.logger.info('ignored _update_dtr_state({!r})'.format(self._dtr_state))
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for cts')
|
||||
return True
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for dsr')
|
||||
return True
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for ri')
|
||||
return False
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for cd)')
|
||||
return True
|
||||
|
||||
# - - - platform specific - - -
|
||||
|
||||
# works on Linux and probably all the other POSIX systems
|
||||
def fileno(self):
|
||||
"""Get the file handle of the underlying socket for use with select"""
|
||||
return self._socket.fileno()
|
||||
|
||||
|
||||
#
|
||||
# simple client test
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
s = Serial('socket://localhost:7000')
|
||||
sys.stdout.write('{}\n'.format(s))
|
||||
|
||||
sys.stdout.write("write...\n")
|
||||
s.write(b"hello\n")
|
||||
s.flush()
|
||||
sys.stdout.write("read: {}\n".format(s.read(5)))
|
||||
|
||||
s.close()
|
||||
@@ -1,337 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a special URL handler that wraps an other port,
|
||||
# print the traffic for debugging purposes. With this, it is possible
|
||||
# to debug the serial port traffic on every application that uses
|
||||
# serial_for_url.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: spy://port[?option[=value][&option[=value]]]
|
||||
# options:
|
||||
# - dev=X a file or device to write to
|
||||
# - color use escape code to colorize output
|
||||
# - raw forward raw bytes instead of hexdump
|
||||
#
|
||||
# example:
|
||||
# redirect output to an other terminal window on Posix (Linux):
|
||||
# python -m serial.tools.miniterm spy:///dev/ttyUSB0?dev=/dev/pts/14\&color
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
|
||||
import serial
|
||||
from serial.serialutil import to_bytes
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
|
||||
def sixteen(data):
|
||||
"""\
|
||||
yield tuples of hex and ASCII display in multiples of 16. Includes a
|
||||
space after 8 bytes and (None, None) after 16 bytes and at the end.
|
||||
"""
|
||||
n = 0
|
||||
for b in serial.iterbytes(data):
|
||||
yield ('{:02X} '.format(ord(b)), b.decode('ascii') if b' ' <= b < b'\x7f' else '.')
|
||||
n += 1
|
||||
if n == 8:
|
||||
yield (' ', '')
|
||||
elif n >= 16:
|
||||
yield (None, None)
|
||||
n = 0
|
||||
if n > 0:
|
||||
while n < 16:
|
||||
n += 1
|
||||
if n == 8:
|
||||
yield (' ', '')
|
||||
yield (' ', ' ')
|
||||
yield (None, None)
|
||||
|
||||
|
||||
def hexdump(data):
|
||||
"""yield lines with hexdump of data"""
|
||||
values = []
|
||||
ascii = []
|
||||
offset = 0
|
||||
for h, a in sixteen(data):
|
||||
if h is None:
|
||||
yield (offset, ' '.join([''.join(values), ''.join(ascii)]))
|
||||
del values[:]
|
||||
del ascii[:]
|
||||
offset += 0x10
|
||||
else:
|
||||
values.append(h)
|
||||
ascii.append(a)
|
||||
|
||||
|
||||
class FormatRaw(object):
|
||||
"""Forward only RX and TX data to output."""
|
||||
|
||||
def __init__(self, output, color):
|
||||
self.output = output
|
||||
self.color = color
|
||||
self.rx_color = '\x1b[32m'
|
||||
self.tx_color = '\x1b[31m'
|
||||
|
||||
def rx(self, data):
|
||||
"""show received data"""
|
||||
if self.color:
|
||||
self.output.write(self.rx_color)
|
||||
self.output.write(data)
|
||||
self.output.flush()
|
||||
|
||||
def tx(self, data):
|
||||
"""show transmitted data"""
|
||||
if self.color:
|
||||
self.output.write(self.tx_color)
|
||||
self.output.write(data)
|
||||
self.output.flush()
|
||||
|
||||
def control(self, name, value):
|
||||
"""(do not) show control calls"""
|
||||
pass
|
||||
|
||||
|
||||
class FormatHexdump(object):
|
||||
"""\
|
||||
Create a hex dump of RX ad TX data, show when control lines are read or
|
||||
written.
|
||||
|
||||
output example::
|
||||
|
||||
000000.000 Q-RX flushInput
|
||||
000002.469 RTS inactive
|
||||
000002.773 RTS active
|
||||
000003.001 TX 48 45 4C 4C 4F HELLO
|
||||
000003.102 RX 48 45 4C 4C 4F HELLO
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, output, color):
|
||||
self.start_time = time.time()
|
||||
self.output = output
|
||||
self.color = color
|
||||
self.rx_color = '\x1b[32m'
|
||||
self.tx_color = '\x1b[31m'
|
||||
self.control_color = '\x1b[37m'
|
||||
|
||||
def write_line(self, timestamp, label, value, value2=''):
|
||||
self.output.write('{:010.3f} {:4} {}{}\n'.format(timestamp, label, value, value2))
|
||||
self.output.flush()
|
||||
|
||||
def rx(self, data):
|
||||
"""show received data as hex dump"""
|
||||
if self.color:
|
||||
self.output.write(self.rx_color)
|
||||
if data:
|
||||
for offset, row in hexdump(data):
|
||||
self.write_line(time.time() - self.start_time, 'RX', '{:04X} '.format(offset), row)
|
||||
else:
|
||||
self.write_line(time.time() - self.start_time, 'RX', '<empty>')
|
||||
|
||||
def tx(self, data):
|
||||
"""show transmitted data as hex dump"""
|
||||
if self.color:
|
||||
self.output.write(self.tx_color)
|
||||
for offset, row in hexdump(data):
|
||||
self.write_line(time.time() - self.start_time, 'TX', '{:04X} '.format(offset), row)
|
||||
|
||||
def control(self, name, value):
|
||||
"""show control calls"""
|
||||
if self.color:
|
||||
self.output.write(self.control_color)
|
||||
self.write_line(time.time() - self.start_time, name, value)
|
||||
|
||||
|
||||
class FormatLog(object):
|
||||
"""\
|
||||
Write data to logging module.
|
||||
"""
|
||||
|
||||
def __init__(self, output, color):
|
||||
# output and color is ignored
|
||||
self.log = logging.getLogger(output)
|
||||
|
||||
def rx(self, data):
|
||||
"""show received data"""
|
||||
if data:
|
||||
self.log.info('RX {!r}'.format(data))
|
||||
|
||||
def tx(self, data):
|
||||
"""show transmitted data"""
|
||||
self.log.info('TX {!r}'.format(data))
|
||||
|
||||
def control(self, name, value):
|
||||
"""show control calls"""
|
||||
self.log.info('{}: {}'.format(name, value))
|
||||
|
||||
|
||||
class FormatLogHex(FormatLog):
|
||||
"""\
|
||||
Write data to logging module.
|
||||
"""
|
||||
|
||||
def rx(self, data):
|
||||
"""show received data"""
|
||||
if data:
|
||||
for offset, row in hexdump(data):
|
||||
self.log.info('RX {}{}'.format('{:04X} '.format(offset), row))
|
||||
|
||||
def tx(self, data):
|
||||
"""show transmitted data"""
|
||||
for offset, row in hexdump(data):
|
||||
self.log.info('TX {}{}'.format('{:04X} '.format(offset), row))
|
||||
|
||||
|
||||
class Serial(serial.Serial):
|
||||
"""\
|
||||
Inherit the native Serial port implementation and wrap all the methods and
|
||||
attributes.
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Serial, self).__init__(*args, **kwargs)
|
||||
self.formatter = None
|
||||
self.show_all = False
|
||||
|
||||
@serial.Serial.port.setter
|
||||
def port(self, value):
|
||||
if value is not None:
|
||||
serial.Serial.port.__set__(self, self.from_url(value))
|
||||
|
||||
def from_url(self, url):
|
||||
"""extract host and port from an URL string"""
|
||||
parts = urlparse.urlsplit(url)
|
||||
if parts.scheme != 'spy':
|
||||
raise serial.SerialException(
|
||||
'expected a string in the form '
|
||||
'"spy://port[?option[=value][&option[=value]]]": '
|
||||
'not starting with spy:// ({!r})'.format(parts.scheme))
|
||||
# process options now, directly altering self
|
||||
formatter = FormatHexdump
|
||||
color = False
|
||||
output = sys.stderr
|
||||
try:
|
||||
for option, values in urlparse.parse_qs(parts.query, True).items():
|
||||
if option == 'file':
|
||||
output = open(values[0], 'w')
|
||||
elif option == 'color':
|
||||
color = True
|
||||
elif option == 'raw':
|
||||
formatter = FormatRaw
|
||||
elif option == 'rawlog':
|
||||
formatter = FormatLog
|
||||
output = values[0] if values[0] else 'serial'
|
||||
elif option == 'log':
|
||||
formatter = FormatLogHex
|
||||
output = values[0] if values[0] else 'serial'
|
||||
elif option == 'all':
|
||||
self.show_all = True
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
except ValueError as e:
|
||||
raise serial.SerialException(
|
||||
'expected a string in the form '
|
||||
'"spy://port[?option[=value][&option[=value]]]": {}'.format(e))
|
||||
self.formatter = formatter(output, color)
|
||||
return ''.join([parts.netloc, parts.path])
|
||||
|
||||
def write(self, tx):
|
||||
tx = to_bytes(tx)
|
||||
self.formatter.tx(tx)
|
||||
return super(Serial, self).write(tx)
|
||||
|
||||
def read(self, size=1):
|
||||
rx = super(Serial, self).read(size)
|
||||
if rx or self.show_all:
|
||||
self.formatter.rx(rx)
|
||||
return rx
|
||||
|
||||
if hasattr(serial.Serial, 'cancel_read'):
|
||||
def cancel_read(self):
|
||||
self.formatter.control('Q-RX', 'cancel_read')
|
||||
super(Serial, self).cancel_read()
|
||||
|
||||
if hasattr(serial.Serial, 'cancel_write'):
|
||||
def cancel_write(self):
|
||||
self.formatter.control('Q-TX', 'cancel_write')
|
||||
super(Serial, self).cancel_write()
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
n = super(Serial, self).in_waiting
|
||||
if self.show_all:
|
||||
self.formatter.control('Q-RX', 'in_waiting -> {}'.format(n))
|
||||
return n
|
||||
|
||||
def flush(self):
|
||||
self.formatter.control('Q-TX', 'flush')
|
||||
super(Serial, self).flush()
|
||||
|
||||
def reset_input_buffer(self):
|
||||
self.formatter.control('Q-RX', 'reset_input_buffer')
|
||||
super(Serial, self).reset_input_buffer()
|
||||
|
||||
def reset_output_buffer(self):
|
||||
self.formatter.control('Q-TX', 'reset_output_buffer')
|
||||
super(Serial, self).reset_output_buffer()
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
self.formatter.control('BRK', 'send_break {}s'.format(duration))
|
||||
super(Serial, self).send_break(duration)
|
||||
|
||||
@serial.Serial.break_condition.setter
|
||||
def break_condition(self, level):
|
||||
self.formatter.control('BRK', 'active' if level else 'inactive')
|
||||
serial.Serial.break_condition.__set__(self, level)
|
||||
|
||||
@serial.Serial.rts.setter
|
||||
def rts(self, level):
|
||||
self.formatter.control('RTS', 'active' if level else 'inactive')
|
||||
serial.Serial.rts.__set__(self, level)
|
||||
|
||||
@serial.Serial.dtr.setter
|
||||
def dtr(self, level):
|
||||
self.formatter.control('DTR', 'active' if level else 'inactive')
|
||||
serial.Serial.dtr.__set__(self, level)
|
||||
|
||||
@serial.Serial.cts.getter
|
||||
def cts(self):
|
||||
level = super(Serial, self).cts
|
||||
self.formatter.control('CTS', 'active' if level else 'inactive')
|
||||
return level
|
||||
|
||||
@serial.Serial.dsr.getter
|
||||
def dsr(self):
|
||||
level = super(Serial, self).dsr
|
||||
self.formatter.control('DSR', 'active' if level else 'inactive')
|
||||
return level
|
||||
|
||||
@serial.Serial.ri.getter
|
||||
def ri(self):
|
||||
level = super(Serial, self).ri
|
||||
self.formatter.control('RI', 'active' if level else 'inactive')
|
||||
return level
|
||||
|
||||
@serial.Serial.cd.getter
|
||||
def cd(self):
|
||||
level = super(Serial, self).cd
|
||||
self.formatter.control('CD', 'active' if level else 'inactive')
|
||||
return level
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
ser = Serial(None)
|
||||
ser.port = 'spy:///dev/ttyS0'
|
||||
print(ser)
|
||||
-366
@@ -1,366 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Constants and types for use with Windows API, used by serialwin32.py
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# pylint: disable=invalid-name,too-few-public-methods,protected-access,too-many-instance-attributes
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from ctypes import c_ulong, c_void_p, c_int64, c_char, \
|
||||
WinDLL, sizeof, Structure, Union, POINTER
|
||||
from ctypes.wintypes import HANDLE
|
||||
from ctypes.wintypes import BOOL
|
||||
from ctypes.wintypes import LPCWSTR
|
||||
from ctypes.wintypes import DWORD
|
||||
from ctypes.wintypes import WORD
|
||||
from ctypes.wintypes import BYTE
|
||||
_stdcall_libraries = {}
|
||||
_stdcall_libraries['kernel32'] = WinDLL('kernel32')
|
||||
|
||||
INVALID_HANDLE_VALUE = HANDLE(-1).value
|
||||
|
||||
|
||||
# some details of the windows API differ between 32 and 64 bit systems..
|
||||
def is_64bit():
|
||||
"""Returns true when running on a 64 bit system"""
|
||||
return sizeof(c_ulong) != sizeof(c_void_p)
|
||||
|
||||
# ULONG_PTR is a an ordinary number, not a pointer and contrary to the name it
|
||||
# is either 32 or 64 bits, depending on the type of windows...
|
||||
# so test if this a 32 bit windows...
|
||||
if is_64bit():
|
||||
ULONG_PTR = c_int64
|
||||
else:
|
||||
ULONG_PTR = c_ulong
|
||||
|
||||
|
||||
class _SECURITY_ATTRIBUTES(Structure):
|
||||
pass
|
||||
LPSECURITY_ATTRIBUTES = POINTER(_SECURITY_ATTRIBUTES)
|
||||
|
||||
|
||||
try:
|
||||
CreateEventW = _stdcall_libraries['kernel32'].CreateEventW
|
||||
except AttributeError:
|
||||
# Fallback to non wide char version for old OS...
|
||||
from ctypes.wintypes import LPCSTR
|
||||
CreateEventA = _stdcall_libraries['kernel32'].CreateEventA
|
||||
CreateEventA.restype = HANDLE
|
||||
CreateEventA.argtypes = [LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCSTR]
|
||||
CreateEvent = CreateEventA
|
||||
|
||||
CreateFileA = _stdcall_libraries['kernel32'].CreateFileA
|
||||
CreateFileA.restype = HANDLE
|
||||
CreateFileA.argtypes = [LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE]
|
||||
CreateFile = CreateFileA
|
||||
else:
|
||||
CreateEventW.restype = HANDLE
|
||||
CreateEventW.argtypes = [LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCWSTR]
|
||||
CreateEvent = CreateEventW # alias
|
||||
|
||||
CreateFileW = _stdcall_libraries['kernel32'].CreateFileW
|
||||
CreateFileW.restype = HANDLE
|
||||
CreateFileW.argtypes = [LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE]
|
||||
CreateFile = CreateFileW # alias
|
||||
|
||||
|
||||
class _OVERLAPPED(Structure):
|
||||
pass
|
||||
|
||||
OVERLAPPED = _OVERLAPPED
|
||||
|
||||
|
||||
class _COMSTAT(Structure):
|
||||
pass
|
||||
|
||||
COMSTAT = _COMSTAT
|
||||
|
||||
|
||||
class _DCB(Structure):
|
||||
pass
|
||||
|
||||
DCB = _DCB
|
||||
|
||||
|
||||
class _COMMTIMEOUTS(Structure):
|
||||
pass
|
||||
|
||||
COMMTIMEOUTS = _COMMTIMEOUTS
|
||||
|
||||
GetLastError = _stdcall_libraries['kernel32'].GetLastError
|
||||
GetLastError.restype = DWORD
|
||||
GetLastError.argtypes = []
|
||||
|
||||
LPOVERLAPPED = POINTER(_OVERLAPPED)
|
||||
LPDWORD = POINTER(DWORD)
|
||||
|
||||
GetOverlappedResult = _stdcall_libraries['kernel32'].GetOverlappedResult
|
||||
GetOverlappedResult.restype = BOOL
|
||||
GetOverlappedResult.argtypes = [HANDLE, LPOVERLAPPED, LPDWORD, BOOL]
|
||||
|
||||
ResetEvent = _stdcall_libraries['kernel32'].ResetEvent
|
||||
ResetEvent.restype = BOOL
|
||||
ResetEvent.argtypes = [HANDLE]
|
||||
|
||||
LPCVOID = c_void_p
|
||||
|
||||
WriteFile = _stdcall_libraries['kernel32'].WriteFile
|
||||
WriteFile.restype = BOOL
|
||||
WriteFile.argtypes = [HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED]
|
||||
|
||||
LPVOID = c_void_p
|
||||
|
||||
ReadFile = _stdcall_libraries['kernel32'].ReadFile
|
||||
ReadFile.restype = BOOL
|
||||
ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED]
|
||||
|
||||
CloseHandle = _stdcall_libraries['kernel32'].CloseHandle
|
||||
CloseHandle.restype = BOOL
|
||||
CloseHandle.argtypes = [HANDLE]
|
||||
|
||||
ClearCommBreak = _stdcall_libraries['kernel32'].ClearCommBreak
|
||||
ClearCommBreak.restype = BOOL
|
||||
ClearCommBreak.argtypes = [HANDLE]
|
||||
|
||||
LPCOMSTAT = POINTER(_COMSTAT)
|
||||
|
||||
ClearCommError = _stdcall_libraries['kernel32'].ClearCommError
|
||||
ClearCommError.restype = BOOL
|
||||
ClearCommError.argtypes = [HANDLE, LPDWORD, LPCOMSTAT]
|
||||
|
||||
SetupComm = _stdcall_libraries['kernel32'].SetupComm
|
||||
SetupComm.restype = BOOL
|
||||
SetupComm.argtypes = [HANDLE, DWORD, DWORD]
|
||||
|
||||
EscapeCommFunction = _stdcall_libraries['kernel32'].EscapeCommFunction
|
||||
EscapeCommFunction.restype = BOOL
|
||||
EscapeCommFunction.argtypes = [HANDLE, DWORD]
|
||||
|
||||
GetCommModemStatus = _stdcall_libraries['kernel32'].GetCommModemStatus
|
||||
GetCommModemStatus.restype = BOOL
|
||||
GetCommModemStatus.argtypes = [HANDLE, LPDWORD]
|
||||
|
||||
LPDCB = POINTER(_DCB)
|
||||
|
||||
GetCommState = _stdcall_libraries['kernel32'].GetCommState
|
||||
GetCommState.restype = BOOL
|
||||
GetCommState.argtypes = [HANDLE, LPDCB]
|
||||
|
||||
LPCOMMTIMEOUTS = POINTER(_COMMTIMEOUTS)
|
||||
|
||||
GetCommTimeouts = _stdcall_libraries['kernel32'].GetCommTimeouts
|
||||
GetCommTimeouts.restype = BOOL
|
||||
GetCommTimeouts.argtypes = [HANDLE, LPCOMMTIMEOUTS]
|
||||
|
||||
PurgeComm = _stdcall_libraries['kernel32'].PurgeComm
|
||||
PurgeComm.restype = BOOL
|
||||
PurgeComm.argtypes = [HANDLE, DWORD]
|
||||
|
||||
SetCommBreak = _stdcall_libraries['kernel32'].SetCommBreak
|
||||
SetCommBreak.restype = BOOL
|
||||
SetCommBreak.argtypes = [HANDLE]
|
||||
|
||||
SetCommMask = _stdcall_libraries['kernel32'].SetCommMask
|
||||
SetCommMask.restype = BOOL
|
||||
SetCommMask.argtypes = [HANDLE, DWORD]
|
||||
|
||||
SetCommState = _stdcall_libraries['kernel32'].SetCommState
|
||||
SetCommState.restype = BOOL
|
||||
SetCommState.argtypes = [HANDLE, LPDCB]
|
||||
|
||||
SetCommTimeouts = _stdcall_libraries['kernel32'].SetCommTimeouts
|
||||
SetCommTimeouts.restype = BOOL
|
||||
SetCommTimeouts.argtypes = [HANDLE, LPCOMMTIMEOUTS]
|
||||
|
||||
WaitForSingleObject = _stdcall_libraries['kernel32'].WaitForSingleObject
|
||||
WaitForSingleObject.restype = DWORD
|
||||
WaitForSingleObject.argtypes = [HANDLE, DWORD]
|
||||
|
||||
WaitCommEvent = _stdcall_libraries['kernel32'].WaitCommEvent
|
||||
WaitCommEvent.restype = BOOL
|
||||
WaitCommEvent.argtypes = [HANDLE, LPDWORD, LPOVERLAPPED]
|
||||
|
||||
CancelIoEx = _stdcall_libraries['kernel32'].CancelIoEx
|
||||
CancelIoEx.restype = BOOL
|
||||
CancelIoEx.argtypes = [HANDLE, LPOVERLAPPED]
|
||||
|
||||
ONESTOPBIT = 0 # Variable c_int
|
||||
TWOSTOPBITS = 2 # Variable c_int
|
||||
ONE5STOPBITS = 1
|
||||
|
||||
NOPARITY = 0 # Variable c_int
|
||||
ODDPARITY = 1 # Variable c_int
|
||||
EVENPARITY = 2 # Variable c_int
|
||||
MARKPARITY = 3
|
||||
SPACEPARITY = 4
|
||||
|
||||
RTS_CONTROL_HANDSHAKE = 2 # Variable c_int
|
||||
RTS_CONTROL_DISABLE = 0 # Variable c_int
|
||||
RTS_CONTROL_ENABLE = 1 # Variable c_int
|
||||
RTS_CONTROL_TOGGLE = 3 # Variable c_int
|
||||
SETRTS = 3
|
||||
CLRRTS = 4
|
||||
|
||||
DTR_CONTROL_HANDSHAKE = 2 # Variable c_int
|
||||
DTR_CONTROL_DISABLE = 0 # Variable c_int
|
||||
DTR_CONTROL_ENABLE = 1 # Variable c_int
|
||||
SETDTR = 5
|
||||
CLRDTR = 6
|
||||
|
||||
MS_DSR_ON = 32 # Variable c_ulong
|
||||
EV_RING = 256 # Variable c_int
|
||||
EV_PERR = 512 # Variable c_int
|
||||
EV_ERR = 128 # Variable c_int
|
||||
SETXOFF = 1 # Variable c_int
|
||||
EV_RXCHAR = 1 # Variable c_int
|
||||
GENERIC_WRITE = 1073741824 # Variable c_long
|
||||
PURGE_TXCLEAR = 4 # Variable c_int
|
||||
FILE_FLAG_OVERLAPPED = 1073741824 # Variable c_int
|
||||
EV_DSR = 16 # Variable c_int
|
||||
MAXDWORD = 4294967295 # Variable c_uint
|
||||
EV_RLSD = 32 # Variable c_int
|
||||
|
||||
ERROR_SUCCESS = 0
|
||||
ERROR_NOT_ENOUGH_MEMORY = 8
|
||||
ERROR_OPERATION_ABORTED = 995
|
||||
ERROR_IO_INCOMPLETE = 996
|
||||
ERROR_IO_PENDING = 997 # Variable c_long
|
||||
ERROR_INVALID_USER_BUFFER = 1784
|
||||
|
||||
MS_CTS_ON = 16 # Variable c_ulong
|
||||
EV_EVENT1 = 2048 # Variable c_int
|
||||
EV_RX80FULL = 1024 # Variable c_int
|
||||
PURGE_RXABORT = 2 # Variable c_int
|
||||
FILE_ATTRIBUTE_NORMAL = 128 # Variable c_int
|
||||
PURGE_TXABORT = 1 # Variable c_int
|
||||
SETXON = 2 # Variable c_int
|
||||
OPEN_EXISTING = 3 # Variable c_int
|
||||
MS_RING_ON = 64 # Variable c_ulong
|
||||
EV_TXEMPTY = 4 # Variable c_int
|
||||
EV_RXFLAG = 2 # Variable c_int
|
||||
MS_RLSD_ON = 128 # Variable c_ulong
|
||||
GENERIC_READ = 2147483648 # Variable c_ulong
|
||||
EV_EVENT2 = 4096 # Variable c_int
|
||||
EV_CTS = 8 # Variable c_int
|
||||
EV_BREAK = 64 # Variable c_int
|
||||
PURGE_RXCLEAR = 8 # Variable c_int
|
||||
INFINITE = 0xFFFFFFFF
|
||||
|
||||
CE_RXOVER = 0x0001
|
||||
CE_OVERRUN = 0x0002
|
||||
CE_RXPARITY = 0x0004
|
||||
CE_FRAME = 0x0008
|
||||
CE_BREAK = 0x0010
|
||||
|
||||
|
||||
class N11_OVERLAPPED4DOLLAR_48E(Union):
|
||||
pass
|
||||
|
||||
|
||||
class N11_OVERLAPPED4DOLLAR_484DOLLAR_49E(Structure):
|
||||
pass
|
||||
|
||||
|
||||
N11_OVERLAPPED4DOLLAR_484DOLLAR_49E._fields_ = [
|
||||
('Offset', DWORD),
|
||||
('OffsetHigh', DWORD),
|
||||
]
|
||||
|
||||
PVOID = c_void_p
|
||||
|
||||
N11_OVERLAPPED4DOLLAR_48E._anonymous_ = ['_0']
|
||||
N11_OVERLAPPED4DOLLAR_48E._fields_ = [
|
||||
('_0', N11_OVERLAPPED4DOLLAR_484DOLLAR_49E),
|
||||
('Pointer', PVOID),
|
||||
]
|
||||
_OVERLAPPED._anonymous_ = ['_0']
|
||||
_OVERLAPPED._fields_ = [
|
||||
('Internal', ULONG_PTR),
|
||||
('InternalHigh', ULONG_PTR),
|
||||
('_0', N11_OVERLAPPED4DOLLAR_48E),
|
||||
('hEvent', HANDLE),
|
||||
]
|
||||
_SECURITY_ATTRIBUTES._fields_ = [
|
||||
('nLength', DWORD),
|
||||
('lpSecurityDescriptor', LPVOID),
|
||||
('bInheritHandle', BOOL),
|
||||
]
|
||||
_COMSTAT._fields_ = [
|
||||
('fCtsHold', DWORD, 1),
|
||||
('fDsrHold', DWORD, 1),
|
||||
('fRlsdHold', DWORD, 1),
|
||||
('fXoffHold', DWORD, 1),
|
||||
('fXoffSent', DWORD, 1),
|
||||
('fEof', DWORD, 1),
|
||||
('fTxim', DWORD, 1),
|
||||
('fReserved', DWORD, 25),
|
||||
('cbInQue', DWORD),
|
||||
('cbOutQue', DWORD),
|
||||
]
|
||||
_DCB._fields_ = [
|
||||
('DCBlength', DWORD),
|
||||
('BaudRate', DWORD),
|
||||
('fBinary', DWORD, 1),
|
||||
('fParity', DWORD, 1),
|
||||
('fOutxCtsFlow', DWORD, 1),
|
||||
('fOutxDsrFlow', DWORD, 1),
|
||||
('fDtrControl', DWORD, 2),
|
||||
('fDsrSensitivity', DWORD, 1),
|
||||
('fTXContinueOnXoff', DWORD, 1),
|
||||
('fOutX', DWORD, 1),
|
||||
('fInX', DWORD, 1),
|
||||
('fErrorChar', DWORD, 1),
|
||||
('fNull', DWORD, 1),
|
||||
('fRtsControl', DWORD, 2),
|
||||
('fAbortOnError', DWORD, 1),
|
||||
('fDummy2', DWORD, 17),
|
||||
('wReserved', WORD),
|
||||
('XonLim', WORD),
|
||||
('XoffLim', WORD),
|
||||
('ByteSize', BYTE),
|
||||
('Parity', BYTE),
|
||||
('StopBits', BYTE),
|
||||
('XonChar', c_char),
|
||||
('XoffChar', c_char),
|
||||
('ErrorChar', c_char),
|
||||
('EofChar', c_char),
|
||||
('EvtChar', c_char),
|
||||
('wReserved1', WORD),
|
||||
]
|
||||
_COMMTIMEOUTS._fields_ = [
|
||||
('ReadIntervalTimeout', DWORD),
|
||||
('ReadTotalTimeoutMultiplier', DWORD),
|
||||
('ReadTotalTimeoutConstant', DWORD),
|
||||
('WriteTotalTimeoutMultiplier', DWORD),
|
||||
('WriteTotalTimeoutConstant', DWORD),
|
||||
]
|
||||
__all__ = ['GetLastError', 'MS_CTS_ON', 'FILE_ATTRIBUTE_NORMAL',
|
||||
'DTR_CONTROL_ENABLE', '_COMSTAT', 'MS_RLSD_ON',
|
||||
'GetOverlappedResult', 'SETXON', 'PURGE_TXABORT',
|
||||
'PurgeComm', 'N11_OVERLAPPED4DOLLAR_48E', 'EV_RING',
|
||||
'ONESTOPBIT', 'SETXOFF', 'PURGE_RXABORT', 'GetCommState',
|
||||
'RTS_CONTROL_ENABLE', '_DCB', 'CreateEvent',
|
||||
'_COMMTIMEOUTS', '_SECURITY_ATTRIBUTES', 'EV_DSR',
|
||||
'EV_PERR', 'EV_RXFLAG', 'OPEN_EXISTING', 'DCB',
|
||||
'FILE_FLAG_OVERLAPPED', 'EV_CTS', 'SetupComm',
|
||||
'LPOVERLAPPED', 'EV_TXEMPTY', 'ClearCommBreak',
|
||||
'LPSECURITY_ATTRIBUTES', 'SetCommBreak', 'SetCommTimeouts',
|
||||
'COMMTIMEOUTS', 'ODDPARITY', 'EV_RLSD',
|
||||
'GetCommModemStatus', 'EV_EVENT2', 'PURGE_TXCLEAR',
|
||||
'EV_BREAK', 'EVENPARITY', 'LPCVOID', 'COMSTAT', 'ReadFile',
|
||||
'PVOID', '_OVERLAPPED', 'WriteFile', 'GetCommTimeouts',
|
||||
'ResetEvent', 'EV_RXCHAR', 'LPCOMSTAT', 'ClearCommError',
|
||||
'ERROR_IO_PENDING', 'EscapeCommFunction', 'GENERIC_READ',
|
||||
'RTS_CONTROL_HANDSHAKE', 'OVERLAPPED',
|
||||
'DTR_CONTROL_HANDSHAKE', 'PURGE_RXCLEAR', 'GENERIC_WRITE',
|
||||
'LPDCB', 'CreateEventW', 'SetCommMask', 'EV_EVENT1',
|
||||
'SetCommState', 'LPVOID', 'CreateFileW', 'LPDWORD',
|
||||
'EV_RX80FULL', 'TWOSTOPBITS', 'LPCOMMTIMEOUTS', 'MAXDWORD',
|
||||
'MS_DSR_ON', 'MS_RING_ON',
|
||||
'N11_OVERLAPPED4DOLLAR_484DOLLAR_49E', 'EV_ERR',
|
||||
'ULONG_PTR', 'CreateFile', 'NOPARITY', 'CloseHandle']
|
||||
@@ -1,98 +0,0 @@
|
||||
import sys
|
||||
import serial
|
||||
import logging
|
||||
import konfig
|
||||
import time
|
||||
from mysql.connector import connect, Error
|
||||
from time import sleep
|
||||
|
||||
Log_Format = "%(levelname)s %(module)s:%(lineno)d %(asctime)s - %(message)s"
|
||||
logging.basicConfig(stream=sys.stdout, format = Log_Format, level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
comeTimeID = 0
|
||||
def checksum(data):
|
||||
checksum = 0;
|
||||
len = int(data[1:3])
|
||||
if(len < 99):
|
||||
for i in range(1, len+3):
|
||||
checksum = checksum ^ ord(data[i:i+1])
|
||||
#print(str(i)+":"+str(data[i:i+1])+" "+hex(ord(data[i:i+1])))
|
||||
return checksum & 0xFF;
|
||||
|
||||
|
||||
def writeCmd(cmd,data):
|
||||
length = str(len(data)+1).zfill(2)
|
||||
msg = '#'+ length + cmd + data
|
||||
cs = str(checksum(str.encode(msg))).zfill(3)
|
||||
#print("Len: "+str(len(data)+1))
|
||||
#print("MEssage: "+msg+" CS: "+cs)
|
||||
con.write(str.encode(msg+cs+"\n"))
|
||||
|
||||
def getMaID(id):
|
||||
comeTimeID = 0
|
||||
try:
|
||||
with connect(**konfig.datenbank()) as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT id FROM user WHERE rfid = '"+str(id[0:8])+"';")
|
||||
maid = cursor.fetchone()
|
||||
connection.commit()
|
||||
if maid == None:
|
||||
writeCmd('M',"NeuerTag "+str(id[0:8]))
|
||||
logger.warning("Employee not found rfID: %s",str(id[0:8]))
|
||||
else:
|
||||
cursor.execute("SELECT id FROM zeiten WHERE ma_id = "+maid[0]+" AND DATE(kommen) = DATE(NOW()) AND gehen = '1990-01-01 00:00:00';")
|
||||
cometime = cursor.fetchone()
|
||||
connection.commit()
|
||||
if cometime == None:
|
||||
writeCmd('C',"")
|
||||
cursor.execute("INSERT INTO zeiten (kommen, gehen, ma_id) VALUES (NOW(), '1990-01-01 00:00:00', "+maid[0]+")")
|
||||
connection.commit()
|
||||
else:
|
||||
writeCmd('G',"")
|
||||
return str(cometime[0])
|
||||
|
||||
except:
|
||||
writeCmd('M',"Datenbankverbindung\n\rfehlgeschlagen")
|
||||
logger.warning("DB connection failed!")
|
||||
|
||||
|
||||
#con = serial.Serial("COM7",115200,timeout=2,parity=serial.PARITY_NONE, dsrdtr=False)
|
||||
#con.write('#')
|
||||
#con.write('\n')
|
||||
#sleep(2)
|
||||
#writeCmd("T",str(int(time.time())))
|
||||
#print(con.read_all())
|
||||
con = None
|
||||
while True:
|
||||
try:
|
||||
if(con == None):
|
||||
con = serial.Serial("COM7",115200,timeout=2,parity=serial.PARITY_NONE, dsrdtr=False)
|
||||
print("Reconnecting")
|
||||
sleep(2)
|
||||
writeCmd("T",str(int(time.time())))
|
||||
line=con.readline()
|
||||
if len(line) > 10:
|
||||
logger.info("MSG: "+str(line)+" "+str(line[3:4]))
|
||||
if(line[3:4] == b'I'):
|
||||
logger.info("TAG!")
|
||||
cometimeID = getMaID(line[4:4+8])
|
||||
elif(line[3:4] == b'P'):
|
||||
logger.info("PAUSE!")
|
||||
pause = str(line(4,4+3))
|
||||
if(cometimeID != None):
|
||||
try:
|
||||
with connect(**konfig.datenbank()) as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("UPDATE zeiten SET gehen = NOW(), pause_mins="+pause+" WHERE id = "+cometimeID)
|
||||
maid = cursor.fetchone()
|
||||
connection.commit()
|
||||
writeCmd('P',"")
|
||||
except:
|
||||
writeCmd('M',"Eintragen\n\rfehlgeschlagen")
|
||||
logger.warning("Could not update entry with Go time")
|
||||
except:
|
||||
if(not(con == None)):
|
||||
con.close()
|
||||
con = None
|
||||
logger.warning("Disconnecting")
|
||||
time.sleep(2)
|
||||
Reference in New Issue
Block a user