SolarManager unter Versionsverwaltung

Erster Stand der Hintergrundprozesse, die auf der Synology unter
/volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler
je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der
AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins
Web-Verzeichnis.

Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die
nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie
von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot),
zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das
Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat.

Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py,
gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso
gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte.

Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein
pip, sie muessen neben den Skripten liegen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 20:46:59 +02:00
co-authored by Claude Opus 5
commit 79843aa2ae
968 changed files with 261182 additions and 0 deletions
@@ -0,0 +1,13 @@
**/__pycache__
*.pyc
*.pyo
documentation/_build
build
dist
*.egg-info
/MANIFEST
.idea
venv
xxx*
@@ -0,0 +1,16 @@
# 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://
+825
View File
@@ -0,0 +1,825 @@
========================
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
@@ -0,0 +1,39 @@
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/
@@ -0,0 +1,39 @@
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
@@ -0,0 +1,56 @@
=================================
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
@@ -0,0 +1,88 @@
# 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."
@@ -0,0 +1,148 @@
==========
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.
@@ -0,0 +1,200 @@
# -*- 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),
}
@@ -0,0 +1,274 @@
.. _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
@@ -0,0 +1,44 @@
.. 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.

After

Width:  |  Height:  |  Size: 6.9 KiB

@@ -0,0 +1,145 @@
==========
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
@@ -0,0 +1,123 @@
====================
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).
@@ -0,0 +1,291 @@
=======
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.
@@ -0,0 +1,274 @@
.. _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``
@@ -0,0 +1,154 @@
#! /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())
@@ -0,0 +1,578 @@
#! /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()
@@ -0,0 +1,44 @@
#! /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
@@ -0,0 +1,188 @@
#!/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 ---')
@@ -0,0 +1,33 @@
# 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__
],
)
@@ -0,0 +1,31 @@
# 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",
],
)
@@ -0,0 +1,41 @@
# 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/",
)
@@ -0,0 +1,230 @@
#!/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()
@@ -0,0 +1,293 @@
#!/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()
@@ -0,0 +1,252 @@
<?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>&amp;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>&amp;Cancel</label>
</object>
</object>
</object>
</object>
</object>
</object>
</application>
@@ -0,0 +1,367 @@
#!/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()
@@ -0,0 +1,154 @@
<?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="&amp;File">
<item>
<label>&amp;Clear</label>
<id>ID_CLEAR</id>
<handler>OnClear</handler>
</item>
<item>
<label>&amp;Save Text As...</label>
<id>ID_SAVEAS</id>
<handler>OnSaveAs</handler>
</item>
<item>
<label>---</label>
<id>---</id>
<name>---</name>
</item>
<item>
<label>&amp;Terminal Settings...</label>
<id>ID_TERM</id>
<handler>OnTermSettings</handler>
</item>
<item>
<label>---</label>
<name>---</name>
</item>
<item>
<label>&amp;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>&amp;DTR</label>
<id>ID_DTR</id>
<checkable>1</checkable>
<handler>OnDTR</handler>
</item>
<item>
<label>&amp;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>
+378
View File
@@ -0,0 +1,378 @@
[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
@@ -0,0 +1,7 @@
[bdist_wheel]
universal=1
[flake8]
max-line-length = 120
ignore = E265, E126, E241
+108
View File
@@ -0,0 +1,108 @@
# 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'],
},
)
@@ -0,0 +1,202 @@
#! 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()
@@ -0,0 +1,55 @@
#! /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())
@@ -0,0 +1,233 @@
#! /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()
@@ -0,0 +1,161 @@
#!/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()
@@ -0,0 +1,82 @@
#!/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()
@@ -0,0 +1,109 @@
#!/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()
@@ -0,0 +1,58 @@
#! /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()
@@ -0,0 +1,49 @@
#! /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()
@@ -0,0 +1,59 @@
#!/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()
@@ -0,0 +1,76 @@
#!/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()
@@ -0,0 +1,61 @@
#! /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()
@@ -0,0 +1,54 @@
#!/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()
@@ -0,0 +1,104 @@
#! /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()
@@ -0,0 +1,42 @@
#! /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()
@@ -0,0 +1,67 @@
#!/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()
@@ -0,0 +1,80 @@
#!/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()
@@ -0,0 +1,75 @@
#!/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()
@@ -0,0 +1,66 @@
#!/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()
@@ -0,0 +1,51 @@
#! /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()
@@ -0,0 +1,36 @@
#!/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()