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:
@@ -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://
|
||||
@@ -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>&OK</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxButton" name="button_cancel" base="EditButton">
|
||||
<stockitem>CANCEL</stockitem>
|
||||
<label>&Cancel</label>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</application>
|
||||
@@ -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="&File">
|
||||
<item>
|
||||
<label>&Clear</label>
|
||||
<id>ID_CLEAR</id>
|
||||
<handler>OnClear</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>&Save Text As...</label>
|
||||
<id>ID_SAVEAS</id>
|
||||
<handler>OnSaveAs</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>---</label>
|
||||
<id>---</id>
|
||||
<name>---</name>
|
||||
</item>
|
||||
<item>
|
||||
<label>&Terminal Settings...</label>
|
||||
<id>ID_TERM</id>
|
||||
<handler>OnTermSettings</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>---</label>
|
||||
<name>---</name>
|
||||
</item>
|
||||
<item>
|
||||
<label>&Exit</label>
|
||||
<id>ID_EXIT</id>
|
||||
<handler>OnExit</handler>
|
||||
</item>
|
||||
</menu>
|
||||
<menu name="" label="Serial Port">
|
||||
<item>
|
||||
<label>RTS</label>
|
||||
<id>ID_RTS</id>
|
||||
<checkable>1</checkable>
|
||||
<handler>OnRTS</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>&DTR</label>
|
||||
<id>ID_DTR</id>
|
||||
<checkable>1</checkable>
|
||||
<handler>OnDTR</handler>
|
||||
</item>
|
||||
<item>
|
||||
<label>&Port Settings...</label>
|
||||
<id>ID_SETTINGS</id>
|
||||
<handler>OnPortSettings</handler>
|
||||
</item>
|
||||
</menu>
|
||||
</menus>
|
||||
</object>
|
||||
<object class="wxBoxSizer" name="sizer_1" base="EditBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>1</option>
|
||||
<object class="wxTextCtrl" name="text_ctrl_output" base="EditTextCtrl">
|
||||
<style>wxTE_MULTILINE|wxTE_READONLY</style>
|
||||
<font>
|
||||
<size>9</size>
|
||||
<family>modern</family>
|
||||
<style>normal</style>
|
||||
<weight>normal</weight>
|
||||
<underlined>0</underlined>
|
||||
<face></face>
|
||||
</font>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="TerminalSettingsDialog" name="dialog_terminal_Settings" base="EditDialog">
|
||||
<style>wxDEFAULT_DIALOG_STYLE</style>
|
||||
<title>Terminal Settings</title>
|
||||
<object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<object class="sizeritem">
|
||||
<flag>wxEXPAND</flag>
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxStaticBoxSizer" name="sizer_4" base="EditStaticBoxSizer">
|
||||
<orient>wxVERTICAL</orient>
|
||||
<label>Input/Output</label>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxCheckBox" name="checkbox_echo" base="EditCheckBox">
|
||||
<label>Local Echo</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxCheckBox" name="checkbox_unprintable" base="EditCheckBox">
|
||||
<label>Show unprintable characters</label>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxRadioBox" name="radio_box_newline" base="EditRadioBox">
|
||||
<style>wxRA_SPECIFY_ROWS</style>
|
||||
<selection>0</selection>
|
||||
<dimension>0</dimension>
|
||||
<label>Newline Handling</label>
|
||||
<choices>
|
||||
<choice>CR only</choice>
|
||||
<choice>LF only</choice>
|
||||
<choice>CR+LF</choice>
|
||||
</choices>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<flag>wxALL|wxALIGN_RIGHT</flag>
|
||||
<border>4</border>
|
||||
<option>0</option>
|
||||
<object class="wxBoxSizer" name="sizer_3" base="EditBoxSizer">
|
||||
<orient>wxHORIZONTAL</orient>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxButton" name="button_ok" base="EditButton">
|
||||
<stockitem>OK</stockitem>
|
||||
<default>1</default>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem">
|
||||
<border>0</border>
|
||||
<option>0</option>
|
||||
<object class="wxButton" name="button_cancel" base="EditButton">
|
||||
<stockitem>CANCEL</stockitem>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</application>
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user