pyserial-3.5/0000775000175000017500000000000013756631132013464 5ustar chrischris00000000000000pyserial-3.5/PKG-INFO0000664000175000017500000000322513756631132014563 0ustar chrischris00000000000000Metadata-Version: 2.1 Name: pyserial Version: 3.5 Summary: Python Serial Port Extension Home-page: https://github.com/pyserial/pyserial Author: Chris Liechti Author-email: cliechti@gmx.net License: BSD 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 Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Topic :: Communications Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Terminals :: Serial Provides-Extra: cp2110 pyserial-3.5/setup.cfg0000644000175000017500000000017513756631132015306 0ustar chrischris00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 120 ignore = E265, E126, E241 [egg_info] tag_build = tag_date = 0 pyserial-3.5/MANIFEST.in0000644000175000017500000000206213641767344015230 0ustar chrischris00000000000000include 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 pyserial-3.5/LICENSE.txt0000644000175000017500000000353513730355274015315 0ustar chrischris00000000000000Copyright (c) 2001-2020 Chris Liechti 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/ pyserial-3.5/examples/0000775000175000017500000000000013756631132015302 5ustar chrischris00000000000000pyserial-3.5/examples/wxSerialConfigDialog.py0000755000175000017500000003161413727760300021724 0ustar chrischris00000000000000#!/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 # # 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() pyserial-3.5/examples/setup-wxTerminal-py2exe.py0000644000175000017500000000200313641767344022357 0ustar chrischris00000000000000# This is a setup.py example script for the use with py2exe # # (C) 2001-2015 Chris Liechti # # 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/", ) pyserial-3.5/examples/setup-miniterm-py2exe.py0000644000175000017500000000143213641767344022056 0ustar chrischris00000000000000# setup script for py2exe to create the miniterm.exe # # (C) 2001-2015 Chris Liechti # # 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__ ], ) pyserial-3.5/examples/port_publisher.py0000755000175000017500000004727213641767344020742 0ustar chrischris00000000000000#! /usr/bin/env python # # (C) 2001-2015 Chris Liechti # # 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() pyserial-3.5/examples/rfc2217_server.py0000755000175000017500000001400413730355007020324 0ustar chrischris00000000000000#!/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 # # 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 ---') pyserial-3.5/examples/at_protocol.py0000644000175000017500000001151013641767344020205 0ustar chrischris00000000000000#! /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('') 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()) pyserial-3.5/examples/setup-rfc2217_server-py2exe.py0000644000175000017500000000141413641767344022706 0ustar chrischris00000000000000# setup script for py2exe to create the rfc2217_server.exe # # (C) 2001-2015 Chris Liechti # # 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", ], ) pyserial-3.5/examples/wxSerialConfigDialog.wxg0000644000175000017500000003245613641767344022115 0ustar chrischris00000000000000 Serial Port Configuration wxVERTICAL wxEXPAND 0 wxVERTICAL wxEXPAND 0 0 3 1 2 0 wxALL|wxALIGN_CENTER_VERTICAL 4 1 wxEXPAND 0 0 wxALL|wxALIGN_CENTER_VERTICAL 4 1 wxEXPAND 0 -1 wxEXPAND 0 wxVERTICAL wxEXPAND 0 0 3 2 0 wxALL|wxALIGN_CENTER_VERTICAL 4 1 wxEXPAND|wxALIGN_RIGHT 0 0 choice 1 wxALL|wxALIGN_CENTER_VERTICAL 4 1 wxEXPAND|wxALIGN_RIGHT 0 0 choice 1 wxALL|wxALIGN_CENTER_VERTICAL 4 1 wxEXPAND|wxALIGN_RIGHT 0 0 choice 1 wxEXPAND 0 wxHORIZONTAL wxALL|wxALIGN_CENTER_VERTICAL 4 0 1 wxALL|wxALIGN_CENTER_VERTICAL 4 1 wxEXPAND 0 wxHORIZONTAL wxALL|wxALIGN_CENTER_VERTICAL 4 wxALL|wxALIGN_CENTER_VERTICAL 4 wxEXPAND 0 10 10 wxALL|wxALIGN_RIGHT 4 wxHORIZONTAL 0 OK 1 0 CANCEL pyserial-3.5/examples/port_publisher.sh0000755000175000017500000000203613641767344020711 0ustar chrischris00000000000000#! /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 pyserial-3.5/examples/wxTerminal.py0000755000175000017500000003427413730355023020013 0ustar chrischris00000000000000#!/usr/bin/env python # # A simple terminal application with wxPython. # # (C) 2001-2020 Chris Liechti # # 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 wil 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.isSet(): self.Close() def StartThread(self): """Start the receiver thread""" self.thread = threading.Thread(target=self.ComPortThread) self.thread.setDaemon(1) 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. """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. """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. """Clear contents of output window.""" self.text_ctrl_output.Clear() def OnPortSettings(self, event): # wxGlade: TerminalFrame. """ 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. """\ 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.isSet(): 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. self.serial.rts = event.IsChecked() def OnDTR(self, event): # wxGlade: TerminalFrame. 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() pyserial-3.5/examples/tcp_serial_redirect.py0000755000175000017500000001652513730355016021670 0ustar chrischris00000000000000#!/usr/bin/env python # # Redirect data from a TCP/IP connection to a serial port and vice versa. # # (C) 2002-2020 Chris Liechti # # 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() pyserial-3.5/examples/wxTerminal.wxg0000644000175000017500000001537013641767344020177 0ustar chrischris00000000000000 Serial Terminal 1 546, 383 ID_CLEAR OnClear ID_SAVEAS OnSaveAs --- --- ID_TERM OnTermSettings --- ID_EXIT OnExit ID_RTS 1 OnRTS ID_DTR 1 OnDTR ID_SETTINGS OnPortSettings wxVERTICAL wxEXPAND 0 9 modern normal 0 Terminal Settings wxVERTICAL wxEXPAND 0 wxVERTICAL wxALL 4 wxALL 4 0 0 0 CR only LF only CR+LF wxALL|wxALIGN_RIGHT 4 wxHORIZONTAL 0 OK 1 0 CANCEL pyserial-3.5/serial/0000775000175000017500000000000013756631132014743 5ustar chrischris00000000000000pyserial-3.5/serial/serialutil.py0000644000175000017500000005244513730355046017501 0ustar chrischris00000000000000#! python # # Base class and support functions used by various backends. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2020 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import import io import time # ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)`` # isn't returning the contents (very unfortunate). Therefore we need special # cases and test for it. Ensure that there is a ``memoryview`` object for older # Python versions. This is easier than making every test dependent on its # existence. try: memoryview except (NameError, AttributeError): # implementation does not matter as we do not really use it. # it just must not inherit from something else we might care for. class memoryview(object): # pylint: disable=redefined-builtin,invalid-name pass try: unicode except (NameError, AttributeError): unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name try: basestring except (NameError, AttributeError): basestring = (str,) # for Python 3, pylint: disable=redefined-builtin,invalid-name # "for byte in data" fails for python3 as it returns ints instead of bytes def iterbytes(b): """Iterate over bytes, returning bytes instead of ints (python3)""" if isinstance(b, memoryview): b = b.tobytes() i = 0 while True: a = b[i:i + 1] i += 1 if a: yield a else: break # all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11' # so a simple ``bytes(sequence)`` doesn't work for all versions def to_bytes(seq): """convert a sequence to a bytes type""" if isinstance(seq, bytes): return seq elif isinstance(seq, bytearray): return bytes(seq) elif isinstance(seq, memoryview): return seq.tobytes() elif isinstance(seq, unicode): raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq)) else: # handle list of integers and bytes (one or more items) for Python 2 and 3 return bytes(bytearray(seq)) # create control bytes XON = to_bytes([17]) XOFF = to_bytes([19]) CR = to_bytes([13]) LF = to_bytes([10]) PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S' STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2) FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8) PARITY_NAMES = { PARITY_NONE: 'None', PARITY_EVEN: 'Even', PARITY_ODD: 'Odd', PARITY_MARK: 'Mark', PARITY_SPACE: 'Space', } class SerialException(IOError): """Base class for serial port related exceptions.""" class SerialTimeoutException(SerialException): """Write timeouts give an exception""" class PortNotOpenError(SerialException): """Port is not open""" def __init__(self): super(PortNotOpenError, self).__init__('Attempting to use a port that is not open') class Timeout(object): """\ Abstraction for timeout operations. Using time.monotonic() if available or time.time() in all other cases. The class can also be initialized with 0 or None, in order to support non-blocking and fully blocking I/O operations. The attributes is_non_blocking and is_infinite are set accordingly. """ if hasattr(time, 'monotonic'): # Timeout implementation with time.monotonic(). This function is only # supported by Python 3.3 and above. It returns a time in seconds # (float) just as time.time(), but is not affected by system clock # adjustments. TIME = time.monotonic else: # Timeout implementation with time.time(). This is compatible with all # Python versions but has issues if the clock is adjusted while the # timeout is running. TIME = time.time def __init__(self, duration): """Initialize a timeout with given duration""" self.is_infinite = (duration is None) self.is_non_blocking = (duration == 0) self.duration = duration if duration is not None: self.target_time = self.TIME() + duration else: self.target_time = None def expired(self): """Return a boolean, telling if the timeout has expired""" return self.target_time is not None and self.time_left() <= 0 def time_left(self): """Return how many seconds are left until the timeout expires""" if self.is_non_blocking: return 0 elif self.is_infinite: return None else: delta = self.target_time - self.TIME() if delta > self.duration: # clock jumped, recalculate self.target_time = self.TIME() + self.duration return self.duration else: return max(0, delta) def restart(self, duration): """\ Restart a timeout, only supported if a timeout was already set up before. """ self.duration = duration self.target_time = self.TIME() + duration class SerialBase(io.RawIOBase): """\ Serial port base class. Provides __init__ function and properties to get/set port settings. """ # default values, may be overridden in subclasses that do not support all values BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000) BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS) PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE) STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO) def __init__(self, port=None, baudrate=9600, bytesize=EIGHTBITS, parity=PARITY_NONE, stopbits=STOPBITS_ONE, timeout=None, xonxoff=False, rtscts=False, write_timeout=None, dsrdtr=False, inter_byte_timeout=None, exclusive=None, **kwargs): """\ Initialize comm port object. If a "port" is given, then the port will be opened immediately. Otherwise a Serial port object in closed state is returned. """ self.is_open = False self.portstr = None self.name = None # correct values are assigned below through properties self._port = None self._baudrate = None self._bytesize = None self._parity = None self._stopbits = None self._timeout = None self._write_timeout = None self._xonxoff = None self._rtscts = None self._dsrdtr = None self._inter_byte_timeout = None self._rs485_mode = None # disabled by default self._rts_state = True self._dtr_state = True self._break_state = False self._exclusive = None # assign values using get/set methods using the properties feature self.port = port self.baudrate = baudrate self.bytesize = bytesize self.parity = parity self.stopbits = stopbits self.timeout = timeout self.write_timeout = write_timeout self.xonxoff = xonxoff self.rtscts = rtscts self.dsrdtr = dsrdtr self.inter_byte_timeout = inter_byte_timeout self.exclusive = exclusive # watch for backward compatible kwargs if 'writeTimeout' in kwargs: self.write_timeout = kwargs.pop('writeTimeout') if 'interCharTimeout' in kwargs: self.inter_byte_timeout = kwargs.pop('interCharTimeout') if kwargs: raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs)) if port is not None: self.open() # - - - - - - - - - - - - - - - - - - - - - - - - # to be implemented by subclasses: # def open(self): # def close(self): # - - - - - - - - - - - - - - - - - - - - - - - - @property def port(self): """\ Get the current port setting. The value that was passed on init or using setPort() is passed back. """ return self._port @port.setter def port(self, port): """\ Change the port. """ if port is not None and not isinstance(port, basestring): raise ValueError('"port" must be None or a string, not {}'.format(type(port))) was_open = self.is_open if was_open: self.close() self.portstr = port self._port = port self.name = self.portstr if was_open: self.open() @property def baudrate(self): """Get the current baud rate setting.""" return self._baudrate @baudrate.setter def baudrate(self, baudrate): """\ Change baud rate. It raises a ValueError if the port is open and the baud rate is not possible. If the port is closed, then the value is accepted and the exception is raised when the port is opened. """ try: b = int(baudrate) except TypeError: raise ValueError("Not a valid baudrate: {!r}".format(baudrate)) else: if b < 0: raise ValueError("Not a valid baudrate: {!r}".format(baudrate)) self._baudrate = b if self.is_open: self._reconfigure_port() @property def bytesize(self): """Get the current byte size setting.""" return self._bytesize @bytesize.setter def bytesize(self, bytesize): """Change byte size.""" if bytesize not in self.BYTESIZES: raise ValueError("Not a valid byte size: {!r}".format(bytesize)) self._bytesize = bytesize if self.is_open: self._reconfigure_port() @property def exclusive(self): """Get the current exclusive access setting.""" return self._exclusive @exclusive.setter def exclusive(self, exclusive): """Change the exclusive access setting.""" self._exclusive = exclusive if self.is_open: self._reconfigure_port() @property def parity(self): """Get the current parity setting.""" return self._parity @parity.setter def parity(self, parity): """Change parity setting.""" if parity not in self.PARITIES: raise ValueError("Not a valid parity: {!r}".format(parity)) self._parity = parity if self.is_open: self._reconfigure_port() @property def stopbits(self): """Get the current stop bits setting.""" return self._stopbits @stopbits.setter def stopbits(self, stopbits): """Change stop bits size.""" if stopbits not in self.STOPBITS: raise ValueError("Not a valid stop bit size: {!r}".format(stopbits)) self._stopbits = stopbits if self.is_open: self._reconfigure_port() @property def timeout(self): """Get the current timeout setting.""" return self._timeout @timeout.setter def timeout(self, timeout): """Change timeout setting.""" if timeout is not None: try: timeout + 1 # test if it's a number, will throw a TypeError if not... except TypeError: raise ValueError("Not a valid timeout: {!r}".format(timeout)) if timeout < 0: raise ValueError("Not a valid timeout: {!r}".format(timeout)) self._timeout = timeout if self.is_open: self._reconfigure_port() @property def write_timeout(self): """Get the current timeout setting.""" return self._write_timeout @write_timeout.setter def write_timeout(self, timeout): """Change timeout setting.""" if timeout is not None: if timeout < 0: raise ValueError("Not a valid timeout: {!r}".format(timeout)) try: timeout + 1 # test if it's a number, will throw a TypeError if not... except TypeError: raise ValueError("Not a valid timeout: {!r}".format(timeout)) self._write_timeout = timeout if self.is_open: self._reconfigure_port() @property def inter_byte_timeout(self): """Get the current inter-character timeout setting.""" return self._inter_byte_timeout @inter_byte_timeout.setter def inter_byte_timeout(self, ic_timeout): """Change inter-byte timeout setting.""" if ic_timeout is not None: if ic_timeout < 0: raise ValueError("Not a valid timeout: {!r}".format(ic_timeout)) try: ic_timeout + 1 # test if it's a number, will throw a TypeError if not... except TypeError: raise ValueError("Not a valid timeout: {!r}".format(ic_timeout)) self._inter_byte_timeout = ic_timeout if self.is_open: self._reconfigure_port() @property def xonxoff(self): """Get the current XON/XOFF setting.""" return self._xonxoff @xonxoff.setter def xonxoff(self, xonxoff): """Change XON/XOFF setting.""" self._xonxoff = xonxoff if self.is_open: self._reconfigure_port() @property def rtscts(self): """Get the current RTS/CTS flow control setting.""" return self._rtscts @rtscts.setter def rtscts(self, rtscts): """Change RTS/CTS flow control setting.""" self._rtscts = rtscts if self.is_open: self._reconfigure_port() @property def dsrdtr(self): """Get the current DSR/DTR flow control setting.""" return self._dsrdtr @dsrdtr.setter def dsrdtr(self, dsrdtr=None): """Change DsrDtr flow control setting.""" if dsrdtr is None: # if not set, keep backwards compatibility and follow rtscts setting self._dsrdtr = self._rtscts else: # if defined independently, follow its value self._dsrdtr = dsrdtr if self.is_open: self._reconfigure_port() @property def rts(self): return self._rts_state @rts.setter def rts(self, value): self._rts_state = value if self.is_open: self._update_rts_state() @property def dtr(self): return self._dtr_state @dtr.setter def dtr(self, value): self._dtr_state = value if self.is_open: self._update_dtr_state() @property def break_condition(self): return self._break_state @break_condition.setter def break_condition(self, value): self._break_state = value if self.is_open: self._update_break_state() # - - - - - - - - - - - - - - - - - - - - - - - - # functions useful for RS-485 adapters @property def rs485_mode(self): """\ Enable RS485 mode and apply new settings, set to None to disable. See serial.rs485.RS485Settings for more info about the value. """ return self._rs485_mode @rs485_mode.setter def rs485_mode(self, rs485_settings): self._rs485_mode = rs485_settings if self.is_open: self._reconfigure_port() # - - - - - - - - - - - - - - - - - - - - - - - - _SAVED_SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff', 'dsrdtr', 'rtscts', 'timeout', 'write_timeout', 'inter_byte_timeout') def get_settings(self): """\ Get current port settings as a dictionary. For use with apply_settings(). """ return dict([(key, getattr(self, '_' + key)) for key in self._SAVED_SETTINGS]) def apply_settings(self, d): """\ Apply stored settings from a dictionary returned from get_settings(). It's allowed to delete keys from the dictionary. These values will simply left unchanged. """ for key in self._SAVED_SETTINGS: if key in d and d[key] != getattr(self, '_' + key): # check against internal "_" value setattr(self, key, d[key]) # set non "_" value to use properties write function # - - - - - - - - - - - - - - - - - - - - - - - - def __repr__(self): """String representation of the current port settings and its state.""" return '{name}(port={p.portstr!r}, ' \ 'baudrate={p.baudrate!r}, bytesize={p.bytesize!r}, parity={p.parity!r}, ' \ 'stopbits={p.stopbits!r}, timeout={p.timeout!r}, xonxoff={p.xonxoff!r}, ' \ 'rtscts={p.rtscts!r}, dsrdtr={p.dsrdtr!r})'.format( name=self.__class__.__name__, id=id(self), p=self) # - - - - - - - - - - - - - - - - - - - - - - - - # compatibility with io library # pylint: disable=invalid-name,missing-docstring def readable(self): return True def writable(self): return True def seekable(self): return False def readinto(self, b): data = self.read(len(b)) n = len(data) try: b[:n] = data except TypeError as err: import array if not isinstance(b, array.array): raise err b[:n] = array.array('b', data) return n # - - - - - - - - - - - - - - - - - - - - - - - - # context manager def __enter__(self): if self._port is not None and not self.is_open: self.open() return self def __exit__(self, *args, **kwargs): self.close() # - - - - - - - - - - - - - - - - - - - - - - - - def send_break(self, duration=0.25): """\ Send break condition. Timed, returns to idle state after given duration. """ if not self.is_open: raise PortNotOpenError() self.break_condition = True time.sleep(duration) self.break_condition = False # - - - - - - - - - - - - - - - - - - - - - - - - # backwards compatibility / deprecated functions def flushInput(self): self.reset_input_buffer() def flushOutput(self): self.reset_output_buffer() def inWaiting(self): return self.in_waiting def sendBreak(self, duration=0.25): self.send_break(duration) def setRTS(self, value=1): self.rts = value def setDTR(self, value=1): self.dtr = value def getCTS(self): return self.cts def getDSR(self): return self.dsr def getRI(self): return self.ri def getCD(self): return self.cd def setPort(self, port): self.port = port @property def writeTimeout(self): return self.write_timeout @writeTimeout.setter def writeTimeout(self, timeout): self.write_timeout = timeout @property def interCharTimeout(self): return self.inter_byte_timeout @interCharTimeout.setter def interCharTimeout(self, interCharTimeout): self.inter_byte_timeout = interCharTimeout def getSettingsDict(self): return self.get_settings() def applySettingsDict(self, d): self.apply_settings(d) def isOpen(self): return self.is_open # - - - - - - - - - - - - - - - - - - - - - - - - # additional functionality def read_all(self): """\ Read all bytes currently available in the buffer of the OS. """ return self.read(self.in_waiting) def read_until(self, expected=LF, size=None): """\ Read until an expected sequence is found ('\n' by default), the size is exceeded or until timeout occurs. """ lenterm = len(expected) line = bytearray() timeout = Timeout(self._timeout) while True: c = self.read(1) if c: line += c if line[-lenterm:] == expected: break if size is not None and len(line) >= size: break else: break if timeout.expired(): break return bytes(line) def iread_until(self, *args, **kwargs): """\ Read lines, implemented as generator. It will raise StopIteration on timeout (empty read). """ while True: line = self.read_until(*args, **kwargs) if not line: break yield line # - - - - - - - - - - - - - - - - - - - - - - - - - if __name__ == '__main__': import sys s = SerialBase() sys.stdout.write('port name: {}\n'.format(s.name)) sys.stdout.write('baud rates: {}\n'.format(s.BAUDRATES)) sys.stdout.write('byte sizes: {}\n'.format(s.BYTESIZES)) sys.stdout.write('parities: {}\n'.format(s.PARITIES)) sys.stdout.write('stop bits: {}\n'.format(s.STOPBITS)) sys.stdout.write('{}\n'.format(s)) pyserial-3.5/serial/serialcli.py0000644000175000017500000002174613727523210017267 0ustar chrischris00000000000000#! python # # Backend for .NET/Mono (IronPython), .NET >= 2 # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2008-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import import System import System.IO.Ports from serial.serialutil import * # must invoke function with byte array, make a helper to convert strings # to byte arrays sab = System.Array[System.Byte] def as_byte_array(string): return sab([ord(x) for x in string]) # XXX will require adaption when run with a 3.x compatible IronPython class Serial(SerialBase): """Serial port implementation for .NET/Mono.""" BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200) def open(self): """\ Open port with current settings. This may throw a SerialException if the port cannot be opened. """ if self._port is None: raise SerialException("Port must be configured before it can be used.") if self.is_open: raise SerialException("Port is already open.") try: self._port_handle = System.IO.Ports.SerialPort(self.portstr) except Exception as msg: self._port_handle = None raise SerialException("could not open port %s: %s" % (self.portstr, msg)) # if RTS and/or DTR are not set before open, they default to True if self._rts_state is None: self._rts_state = True if self._dtr_state is None: self._dtr_state = True self._reconfigure_port() self._port_handle.Open() self.is_open = True if not self._dsrdtr: self._update_dtr_state() if not self._rtscts: self._update_rts_state() self.reset_input_buffer() def _reconfigure_port(self): """Set communication parameters on opened port.""" if not self._port_handle: raise SerialException("Can only operate on a valid port handle") #~ self._port_handle.ReceivedBytesThreshold = 1 if self._timeout is None: self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout else: self._port_handle.ReadTimeout = int(self._timeout * 1000) # if self._timeout != 0 and self._interCharTimeout is not None: # timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:] if self._write_timeout is None: self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout else: self._port_handle.WriteTimeout = int(self._write_timeout * 1000) # Setup the connection info. try: self._port_handle.BaudRate = self._baudrate except IOError as e: # catch errors from illegal baudrate settings raise ValueError(str(e)) if self._bytesize == FIVEBITS: self._port_handle.DataBits = 5 elif self._bytesize == SIXBITS: self._port_handle.DataBits = 6 elif self._bytesize == SEVENBITS: self._port_handle.DataBits = 7 elif self._bytesize == EIGHTBITS: self._port_handle.DataBits = 8 else: raise ValueError("Unsupported number of data bits: %r" % self._bytesize) if self._parity == PARITY_NONE: self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k elif self._parity == PARITY_EVEN: self._port_handle.Parity = System.IO.Ports.Parity.Even elif self._parity == PARITY_ODD: self._port_handle.Parity = System.IO.Ports.Parity.Odd elif self._parity == PARITY_MARK: self._port_handle.Parity = System.IO.Ports.Parity.Mark elif self._parity == PARITY_SPACE: self._port_handle.Parity = System.IO.Ports.Parity.Space else: raise ValueError("Unsupported parity mode: %r" % self._parity) if self._stopbits == STOPBITS_ONE: self._port_handle.StopBits = System.IO.Ports.StopBits.One elif self._stopbits == STOPBITS_ONE_POINT_FIVE: self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive elif self._stopbits == STOPBITS_TWO: self._port_handle.StopBits = System.IO.Ports.StopBits.Two else: raise ValueError("Unsupported number of stop bits: %r" % self._stopbits) if self._rtscts and self._xonxoff: self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff elif self._rtscts: self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend elif self._xonxoff: self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff else: self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None') # reserved keyword in Py3k #~ def __del__(self): #~ self.close() def close(self): """Close port""" if self.is_open: if self._port_handle: try: self._port_handle.Close() except System.IO.Ports.InvalidOperationException: # ignore errors. can happen for unplugged USB serial devices pass self._port_handle = None self.is_open = False # - - - - - - - - - - - - - - - - - - - - - - - - @property def in_waiting(self): """Return the number of characters currently in the input buffer.""" if not self.is_open: raise PortNotOpenError() return self._port_handle.BytesToRead def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.is_open: raise PortNotOpenError() # must use single byte reads as this is the only way to read # without applying encodings data = bytearray() while size: try: data.append(self._port_handle.ReadByte()) except System.TimeoutException: break else: size -= 1 return bytes(data) def write(self, data): """Output the given string over the serial port.""" if not self.is_open: raise PortNotOpenError() #~ if not isinstance(data, (bytes, bytearray)): #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) try: # must call overloaded method with byte array argument # as this is the only one not applying encodings self._port_handle.Write(as_byte_array(data), 0, len(data)) except System.TimeoutException: raise SerialTimeoutException('Write timeout') return len(data) def reset_input_buffer(self): """Clear input buffer, discarding all that is in the buffer.""" if not self.is_open: raise PortNotOpenError() self._port_handle.DiscardInBuffer() def reset_output_buffer(self): """\ Clear output buffer, aborting the current output and discarding all that is in the buffer. """ if not self.is_open: raise PortNotOpenError() self._port_handle.DiscardOutBuffer() def _update_break_state(self): """ Set break: Controls TXD. When active, to transmitting is possible. """ if not self.is_open: raise PortNotOpenError() self._port_handle.BreakState = bool(self._break_state) def _update_rts_state(self): """Set terminal status line: Request To Send""" if not self.is_open: raise PortNotOpenError() self._port_handle.RtsEnable = bool(self._rts_state) def _update_dtr_state(self): """Set terminal status line: Data Terminal Ready""" if not self.is_open: raise PortNotOpenError() self._port_handle.DtrEnable = bool(self._dtr_state) @property def cts(self): """Read terminal status line: Clear To Send""" if not self.is_open: raise PortNotOpenError() return self._port_handle.CtsHolding @property def dsr(self): """Read terminal status line: Data Set Ready""" if not self.is_open: raise PortNotOpenError() return self._port_handle.DsrHolding @property def ri(self): """Read terminal status line: Ring Indicator""" if not self.is_open: raise PortNotOpenError() #~ return self._port_handle.XXX return False # XXX an error would be better @property def cd(self): """Read terminal status line: Carrier Detect""" if not self.is_open: raise PortNotOpenError() return self._port_handle.CDHolding # - - platform specific - - - - # none pyserial-3.5/serial/win32.py0000644000175000017500000002560213641767344016272 0ustar chrischris00000000000000#! python # # Constants and types for use with Windows API, used by serialwin32.py # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause # pylint: disable=invalid-name,too-few-public-methods,protected-access,too-many-instance-attributes from __future__ import absolute_import from ctypes import c_ulong, c_void_p, c_int64, c_char, \ WinDLL, sizeof, Structure, Union, POINTER from ctypes.wintypes import HANDLE from ctypes.wintypes import BOOL from ctypes.wintypes import LPCWSTR from ctypes.wintypes import DWORD from ctypes.wintypes import WORD from ctypes.wintypes import BYTE _stdcall_libraries = {} _stdcall_libraries['kernel32'] = WinDLL('kernel32') INVALID_HANDLE_VALUE = HANDLE(-1).value # some details of the windows API differ between 32 and 64 bit systems.. def is_64bit(): """Returns true when running on a 64 bit system""" return sizeof(c_ulong) != sizeof(c_void_p) # ULONG_PTR is a an ordinary number, not a pointer and contrary to the name it # is either 32 or 64 bits, depending on the type of windows... # so test if this a 32 bit windows... if is_64bit(): ULONG_PTR = c_int64 else: ULONG_PTR = c_ulong class _SECURITY_ATTRIBUTES(Structure): pass LPSECURITY_ATTRIBUTES = POINTER(_SECURITY_ATTRIBUTES) try: CreateEventW = _stdcall_libraries['kernel32'].CreateEventW except AttributeError: # Fallback to non wide char version for old OS... from ctypes.wintypes import LPCSTR CreateEventA = _stdcall_libraries['kernel32'].CreateEventA CreateEventA.restype = HANDLE CreateEventA.argtypes = [LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCSTR] CreateEvent = CreateEventA CreateFileA = _stdcall_libraries['kernel32'].CreateFileA CreateFileA.restype = HANDLE CreateFileA.argtypes = [LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE] CreateFile = CreateFileA else: CreateEventW.restype = HANDLE CreateEventW.argtypes = [LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCWSTR] CreateEvent = CreateEventW # alias CreateFileW = _stdcall_libraries['kernel32'].CreateFileW CreateFileW.restype = HANDLE CreateFileW.argtypes = [LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE] CreateFile = CreateFileW # alias class _OVERLAPPED(Structure): pass OVERLAPPED = _OVERLAPPED class _COMSTAT(Structure): pass COMSTAT = _COMSTAT class _DCB(Structure): pass DCB = _DCB class _COMMTIMEOUTS(Structure): pass COMMTIMEOUTS = _COMMTIMEOUTS GetLastError = _stdcall_libraries['kernel32'].GetLastError GetLastError.restype = DWORD GetLastError.argtypes = [] LPOVERLAPPED = POINTER(_OVERLAPPED) LPDWORD = POINTER(DWORD) GetOverlappedResult = _stdcall_libraries['kernel32'].GetOverlappedResult GetOverlappedResult.restype = BOOL GetOverlappedResult.argtypes = [HANDLE, LPOVERLAPPED, LPDWORD, BOOL] ResetEvent = _stdcall_libraries['kernel32'].ResetEvent ResetEvent.restype = BOOL ResetEvent.argtypes = [HANDLE] LPCVOID = c_void_p WriteFile = _stdcall_libraries['kernel32'].WriteFile WriteFile.restype = BOOL WriteFile.argtypes = [HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED] LPVOID = c_void_p ReadFile = _stdcall_libraries['kernel32'].ReadFile ReadFile.restype = BOOL ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED] CloseHandle = _stdcall_libraries['kernel32'].CloseHandle CloseHandle.restype = BOOL CloseHandle.argtypes = [HANDLE] ClearCommBreak = _stdcall_libraries['kernel32'].ClearCommBreak ClearCommBreak.restype = BOOL ClearCommBreak.argtypes = [HANDLE] LPCOMSTAT = POINTER(_COMSTAT) ClearCommError = _stdcall_libraries['kernel32'].ClearCommError ClearCommError.restype = BOOL ClearCommError.argtypes = [HANDLE, LPDWORD, LPCOMSTAT] SetupComm = _stdcall_libraries['kernel32'].SetupComm SetupComm.restype = BOOL SetupComm.argtypes = [HANDLE, DWORD, DWORD] EscapeCommFunction = _stdcall_libraries['kernel32'].EscapeCommFunction EscapeCommFunction.restype = BOOL EscapeCommFunction.argtypes = [HANDLE, DWORD] GetCommModemStatus = _stdcall_libraries['kernel32'].GetCommModemStatus GetCommModemStatus.restype = BOOL GetCommModemStatus.argtypes = [HANDLE, LPDWORD] LPDCB = POINTER(_DCB) GetCommState = _stdcall_libraries['kernel32'].GetCommState GetCommState.restype = BOOL GetCommState.argtypes = [HANDLE, LPDCB] LPCOMMTIMEOUTS = POINTER(_COMMTIMEOUTS) GetCommTimeouts = _stdcall_libraries['kernel32'].GetCommTimeouts GetCommTimeouts.restype = BOOL GetCommTimeouts.argtypes = [HANDLE, LPCOMMTIMEOUTS] PurgeComm = _stdcall_libraries['kernel32'].PurgeComm PurgeComm.restype = BOOL PurgeComm.argtypes = [HANDLE, DWORD] SetCommBreak = _stdcall_libraries['kernel32'].SetCommBreak SetCommBreak.restype = BOOL SetCommBreak.argtypes = [HANDLE] SetCommMask = _stdcall_libraries['kernel32'].SetCommMask SetCommMask.restype = BOOL SetCommMask.argtypes = [HANDLE, DWORD] SetCommState = _stdcall_libraries['kernel32'].SetCommState SetCommState.restype = BOOL SetCommState.argtypes = [HANDLE, LPDCB] SetCommTimeouts = _stdcall_libraries['kernel32'].SetCommTimeouts SetCommTimeouts.restype = BOOL SetCommTimeouts.argtypes = [HANDLE, LPCOMMTIMEOUTS] WaitForSingleObject = _stdcall_libraries['kernel32'].WaitForSingleObject WaitForSingleObject.restype = DWORD WaitForSingleObject.argtypes = [HANDLE, DWORD] WaitCommEvent = _stdcall_libraries['kernel32'].WaitCommEvent WaitCommEvent.restype = BOOL WaitCommEvent.argtypes = [HANDLE, LPDWORD, LPOVERLAPPED] CancelIoEx = _stdcall_libraries['kernel32'].CancelIoEx CancelIoEx.restype = BOOL CancelIoEx.argtypes = [HANDLE, LPOVERLAPPED] ONESTOPBIT = 0 # Variable c_int TWOSTOPBITS = 2 # Variable c_int ONE5STOPBITS = 1 NOPARITY = 0 # Variable c_int ODDPARITY = 1 # Variable c_int EVENPARITY = 2 # Variable c_int MARKPARITY = 3 SPACEPARITY = 4 RTS_CONTROL_HANDSHAKE = 2 # Variable c_int RTS_CONTROL_DISABLE = 0 # Variable c_int RTS_CONTROL_ENABLE = 1 # Variable c_int RTS_CONTROL_TOGGLE = 3 # Variable c_int SETRTS = 3 CLRRTS = 4 DTR_CONTROL_HANDSHAKE = 2 # Variable c_int DTR_CONTROL_DISABLE = 0 # Variable c_int DTR_CONTROL_ENABLE = 1 # Variable c_int SETDTR = 5 CLRDTR = 6 MS_DSR_ON = 32 # Variable c_ulong EV_RING = 256 # Variable c_int EV_PERR = 512 # Variable c_int EV_ERR = 128 # Variable c_int SETXOFF = 1 # Variable c_int EV_RXCHAR = 1 # Variable c_int GENERIC_WRITE = 1073741824 # Variable c_long PURGE_TXCLEAR = 4 # Variable c_int FILE_FLAG_OVERLAPPED = 1073741824 # Variable c_int EV_DSR = 16 # Variable c_int MAXDWORD = 4294967295 # Variable c_uint EV_RLSD = 32 # Variable c_int ERROR_SUCCESS = 0 ERROR_NOT_ENOUGH_MEMORY = 8 ERROR_OPERATION_ABORTED = 995 ERROR_IO_INCOMPLETE = 996 ERROR_IO_PENDING = 997 # Variable c_long ERROR_INVALID_USER_BUFFER = 1784 MS_CTS_ON = 16 # Variable c_ulong EV_EVENT1 = 2048 # Variable c_int EV_RX80FULL = 1024 # Variable c_int PURGE_RXABORT = 2 # Variable c_int FILE_ATTRIBUTE_NORMAL = 128 # Variable c_int PURGE_TXABORT = 1 # Variable c_int SETXON = 2 # Variable c_int OPEN_EXISTING = 3 # Variable c_int MS_RING_ON = 64 # Variable c_ulong EV_TXEMPTY = 4 # Variable c_int EV_RXFLAG = 2 # Variable c_int MS_RLSD_ON = 128 # Variable c_ulong GENERIC_READ = 2147483648 # Variable c_ulong EV_EVENT2 = 4096 # Variable c_int EV_CTS = 8 # Variable c_int EV_BREAK = 64 # Variable c_int PURGE_RXCLEAR = 8 # Variable c_int INFINITE = 0xFFFFFFFF CE_RXOVER = 0x0001 CE_OVERRUN = 0x0002 CE_RXPARITY = 0x0004 CE_FRAME = 0x0008 CE_BREAK = 0x0010 class N11_OVERLAPPED4DOLLAR_48E(Union): pass class N11_OVERLAPPED4DOLLAR_484DOLLAR_49E(Structure): pass N11_OVERLAPPED4DOLLAR_484DOLLAR_49E._fields_ = [ ('Offset', DWORD), ('OffsetHigh', DWORD), ] PVOID = c_void_p N11_OVERLAPPED4DOLLAR_48E._anonymous_ = ['_0'] N11_OVERLAPPED4DOLLAR_48E._fields_ = [ ('_0', N11_OVERLAPPED4DOLLAR_484DOLLAR_49E), ('Pointer', PVOID), ] _OVERLAPPED._anonymous_ = ['_0'] _OVERLAPPED._fields_ = [ ('Internal', ULONG_PTR), ('InternalHigh', ULONG_PTR), ('_0', N11_OVERLAPPED4DOLLAR_48E), ('hEvent', HANDLE), ] _SECURITY_ATTRIBUTES._fields_ = [ ('nLength', DWORD), ('lpSecurityDescriptor', LPVOID), ('bInheritHandle', BOOL), ] _COMSTAT._fields_ = [ ('fCtsHold', DWORD, 1), ('fDsrHold', DWORD, 1), ('fRlsdHold', DWORD, 1), ('fXoffHold', DWORD, 1), ('fXoffSent', DWORD, 1), ('fEof', DWORD, 1), ('fTxim', DWORD, 1), ('fReserved', DWORD, 25), ('cbInQue', DWORD), ('cbOutQue', DWORD), ] _DCB._fields_ = [ ('DCBlength', DWORD), ('BaudRate', DWORD), ('fBinary', DWORD, 1), ('fParity', DWORD, 1), ('fOutxCtsFlow', DWORD, 1), ('fOutxDsrFlow', DWORD, 1), ('fDtrControl', DWORD, 2), ('fDsrSensitivity', DWORD, 1), ('fTXContinueOnXoff', DWORD, 1), ('fOutX', DWORD, 1), ('fInX', DWORD, 1), ('fErrorChar', DWORD, 1), ('fNull', DWORD, 1), ('fRtsControl', DWORD, 2), ('fAbortOnError', DWORD, 1), ('fDummy2', DWORD, 17), ('wReserved', WORD), ('XonLim', WORD), ('XoffLim', WORD), ('ByteSize', BYTE), ('Parity', BYTE), ('StopBits', BYTE), ('XonChar', c_char), ('XoffChar', c_char), ('ErrorChar', c_char), ('EofChar', c_char), ('EvtChar', c_char), ('wReserved1', WORD), ] _COMMTIMEOUTS._fields_ = [ ('ReadIntervalTimeout', DWORD), ('ReadTotalTimeoutMultiplier', DWORD), ('ReadTotalTimeoutConstant', DWORD), ('WriteTotalTimeoutMultiplier', DWORD), ('WriteTotalTimeoutConstant', DWORD), ] __all__ = ['GetLastError', 'MS_CTS_ON', 'FILE_ATTRIBUTE_NORMAL', 'DTR_CONTROL_ENABLE', '_COMSTAT', 'MS_RLSD_ON', 'GetOverlappedResult', 'SETXON', 'PURGE_TXABORT', 'PurgeComm', 'N11_OVERLAPPED4DOLLAR_48E', 'EV_RING', 'ONESTOPBIT', 'SETXOFF', 'PURGE_RXABORT', 'GetCommState', 'RTS_CONTROL_ENABLE', '_DCB', 'CreateEvent', '_COMMTIMEOUTS', '_SECURITY_ATTRIBUTES', 'EV_DSR', 'EV_PERR', 'EV_RXFLAG', 'OPEN_EXISTING', 'DCB', 'FILE_FLAG_OVERLAPPED', 'EV_CTS', 'SetupComm', 'LPOVERLAPPED', 'EV_TXEMPTY', 'ClearCommBreak', 'LPSECURITY_ATTRIBUTES', 'SetCommBreak', 'SetCommTimeouts', 'COMMTIMEOUTS', 'ODDPARITY', 'EV_RLSD', 'GetCommModemStatus', 'EV_EVENT2', 'PURGE_TXCLEAR', 'EV_BREAK', 'EVENPARITY', 'LPCVOID', 'COMSTAT', 'ReadFile', 'PVOID', '_OVERLAPPED', 'WriteFile', 'GetCommTimeouts', 'ResetEvent', 'EV_RXCHAR', 'LPCOMSTAT', 'ClearCommError', 'ERROR_IO_PENDING', 'EscapeCommFunction', 'GENERIC_READ', 'RTS_CONTROL_HANDSHAKE', 'OVERLAPPED', 'DTR_CONTROL_HANDSHAKE', 'PURGE_RXCLEAR', 'GENERIC_WRITE', 'LPDCB', 'CreateEventW', 'SetCommMask', 'EV_EVENT1', 'SetCommState', 'LPVOID', 'CreateFileW', 'LPDWORD', 'EV_RX80FULL', 'TWOSTOPBITS', 'LPCOMMTIMEOUTS', 'MAXDWORD', 'MS_DSR_ON', 'MS_RING_ON', 'N11_OVERLAPPED4DOLLAR_484DOLLAR_49E', 'EV_ERR', 'ULONG_PTR', 'CreateFile', 'NOPARITY', 'CloseHandle'] pyserial-3.5/serial/serialjava.py0000644000175000017500000002044013727523211017430 0ustar chrischris00000000000000#!jython # # Backend Jython with JavaComm # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2002-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import from serial.serialutil import * def my_import(name): mod = __import__(name) components = name.split('.') for comp in components[1:]: mod = getattr(mod, comp) return mod def detect_java_comm(names): """try given list of modules and return that imports""" for name in names: try: mod = my_import(name) mod.SerialPort return mod except (ImportError, AttributeError): pass raise ImportError("No Java Communications API implementation found") # Java Communications API implementations # http://mho.republika.pl/java/comm/ comm = detect_java_comm([ 'javax.comm', # Sun/IBM 'gnu.io', # RXTX ]) def device(portnumber): """Turn a port number into a device name""" enum = comm.CommPortIdentifier.getPortIdentifiers() ports = [] while enum.hasMoreElements(): el = enum.nextElement() if el.getPortType() == comm.CommPortIdentifier.PORT_SERIAL: ports.append(el) return ports[portnumber].getName() class Serial(SerialBase): """\ Serial port class, implemented with Java Communications API and thus usable with jython and the appropriate java extension. """ def open(self): """\ Open port with current settings. This may throw a SerialException if the port cannot be opened. """ if self._port is None: raise SerialException("Port must be configured before it can be used.") if self.is_open: raise SerialException("Port is already open.") if type(self._port) == type(''): # strings are taken directly portId = comm.CommPortIdentifier.getPortIdentifier(self._port) else: portId = comm.CommPortIdentifier.getPortIdentifier(device(self._port)) # numbers are transformed to a comport id obj try: self.sPort = portId.open("python serial module", 10) except Exception as msg: self.sPort = None raise SerialException("Could not open port: %s" % msg) self._reconfigurePort() self._instream = self.sPort.getInputStream() self._outstream = self.sPort.getOutputStream() self.is_open = True def _reconfigurePort(self): """Set communication parameters on opened port.""" if not self.sPort: raise SerialException("Can only operate on a valid port handle") self.sPort.enableReceiveTimeout(30) if self._bytesize == FIVEBITS: jdatabits = comm.SerialPort.DATABITS_5 elif self._bytesize == SIXBITS: jdatabits = comm.SerialPort.DATABITS_6 elif self._bytesize == SEVENBITS: jdatabits = comm.SerialPort.DATABITS_7 elif self._bytesize == EIGHTBITS: jdatabits = comm.SerialPort.DATABITS_8 else: raise ValueError("unsupported bytesize: %r" % self._bytesize) if self._stopbits == STOPBITS_ONE: jstopbits = comm.SerialPort.STOPBITS_1 elif self._stopbits == STOPBITS_ONE_POINT_FIVE: jstopbits = comm.SerialPort.STOPBITS_1_5 elif self._stopbits == STOPBITS_TWO: jstopbits = comm.SerialPort.STOPBITS_2 else: raise ValueError("unsupported number of stopbits: %r" % self._stopbits) if self._parity == PARITY_NONE: jparity = comm.SerialPort.PARITY_NONE elif self._parity == PARITY_EVEN: jparity = comm.SerialPort.PARITY_EVEN elif self._parity == PARITY_ODD: jparity = comm.SerialPort.PARITY_ODD elif self._parity == PARITY_MARK: jparity = comm.SerialPort.PARITY_MARK elif self._parity == PARITY_SPACE: jparity = comm.SerialPort.PARITY_SPACE else: raise ValueError("unsupported parity type: %r" % self._parity) jflowin = jflowout = 0 if self._rtscts: jflowin |= comm.SerialPort.FLOWCONTROL_RTSCTS_IN jflowout |= comm.SerialPort.FLOWCONTROL_RTSCTS_OUT if self._xonxoff: jflowin |= comm.SerialPort.FLOWCONTROL_XONXOFF_IN jflowout |= comm.SerialPort.FLOWCONTROL_XONXOFF_OUT self.sPort.setSerialPortParams(self._baudrate, jdatabits, jstopbits, jparity) self.sPort.setFlowControlMode(jflowin | jflowout) if self._timeout >= 0: self.sPort.enableReceiveTimeout(int(self._timeout*1000)) else: self.sPort.disableReceiveTimeout() def close(self): """Close port""" if self.is_open: if self.sPort: self._instream.close() self._outstream.close() self.sPort.close() self.sPort = None self.is_open = False # - - - - - - - - - - - - - - - - - - - - - - - - @property def in_waiting(self): """Return the number of characters currently in the input buffer.""" if not self.sPort: raise PortNotOpenError() return self._instream.available() def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.sPort: raise PortNotOpenError() read = bytearray() if size > 0: while len(read) < size: x = self._instream.read() if x == -1: if self.timeout >= 0: break else: read.append(x) return bytes(read) def write(self, data): """Output the given string over the serial port.""" if not self.sPort: raise PortNotOpenError() if not isinstance(data, (bytes, bytearray)): raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) self._outstream.write(data) return len(data) def reset_input_buffer(self): """Clear input buffer, discarding all that is in the buffer.""" if not self.sPort: raise PortNotOpenError() self._instream.skip(self._instream.available()) def reset_output_buffer(self): """\ Clear output buffer, aborting the current output and discarding all that is in the buffer. """ if not self.sPort: raise PortNotOpenError() self._outstream.flush() def send_break(self, duration=0.25): """Send break condition. Timed, returns to idle state after given duration.""" if not self.sPort: raise PortNotOpenError() self.sPort.sendBreak(duration*1000.0) def _update_break_state(self): """Set break: Controls TXD. When active, to transmitting is possible.""" if self.fd is None: raise PortNotOpenError() raise SerialException("The _update_break_state function is not implemented in java.") def _update_rts_state(self): """Set terminal status line: Request To Send""" if not self.sPort: raise PortNotOpenError() self.sPort.setRTS(self._rts_state) def _update_dtr_state(self): """Set terminal status line: Data Terminal Ready""" if not self.sPort: raise PortNotOpenError() self.sPort.setDTR(self._dtr_state) @property def cts(self): """Read terminal status line: Clear To Send""" if not self.sPort: raise PortNotOpenError() self.sPort.isCTS() @property def dsr(self): """Read terminal status line: Data Set Ready""" if not self.sPort: raise PortNotOpenError() self.sPort.isDSR() @property def ri(self): """Read terminal status line: Ring Indicator""" if not self.sPort: raise PortNotOpenError() self.sPort.isRI() @property def cd(self): """Read terminal status line: Carrier Detect""" if not self.sPort: raise PortNotOpenError() self.sPort.isCD() pyserial-3.5/serial/threaded/0000775000175000017500000000000013756631132016523 5ustar chrischris00000000000000pyserial-3.5/serial/threaded/__init__.py0000644000175000017500000002214713641767344020650 0ustar chrischris00000000000000#!/usr/bin/env python3 # # Working with threading and pySerial # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2015-2016 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause """\ Support threading with serial ports. """ from __future__ import absolute_import import serial import threading class Protocol(object): """\ Protocol as used by the ReaderThread. This base class provides empty implementations of all methods. """ def connection_made(self, transport): """Called when reader thread is started""" def data_received(self, data): """Called with snippets received from the serial port""" def connection_lost(self, exc): """\ Called when the serial port is closed or the reader loop terminated otherwise. """ if isinstance(exc, Exception): raise exc class Packetizer(Protocol): """ Read binary packets from serial port. Packets are expected to be terminated with a TERMINATOR byte (null byte by default). The class also keeps track of the transport. """ TERMINATOR = b'\0' def __init__(self): self.buffer = bytearray() self.transport = None def connection_made(self, transport): """Store transport""" self.transport = transport def connection_lost(self, exc): """Forget transport""" self.transport = None super(Packetizer, self).connection_lost(exc) def data_received(self, data): """Buffer received data, find TERMINATOR, call handle_packet""" self.buffer.extend(data) while self.TERMINATOR in self.buffer: packet, self.buffer = self.buffer.split(self.TERMINATOR, 1) self.handle_packet(packet) def handle_packet(self, packet): """Process packets - to be overridden by subclassing""" raise NotImplementedError('please implement functionality in handle_packet') class FramedPacket(Protocol): """ Read binary packets. Packets are expected to have a start and stop marker. The class also keeps track of the transport. """ START = b'(' STOP = b')' def __init__(self): self.packet = bytearray() self.in_packet = False self.transport = None def connection_made(self, transport): """Store transport""" self.transport = transport def connection_lost(self, exc): """Forget transport""" self.transport = None self.in_packet = False del self.packet[:] super(FramedPacket, self).connection_lost(exc) def data_received(self, data): """Find data enclosed in START/STOP, call handle_packet""" for byte in serial.iterbytes(data): if byte == self.START: self.in_packet = True elif byte == self.STOP: self.in_packet = False self.handle_packet(bytes(self.packet)) # make read-only copy del self.packet[:] elif self.in_packet: self.packet.extend(byte) else: self.handle_out_of_packet_data(byte) def handle_packet(self, packet): """Process packets - to be overridden by subclassing""" raise NotImplementedError('please implement functionality in handle_packet') def handle_out_of_packet_data(self, data): """Process data that is received outside of packets""" pass class LineReader(Packetizer): """ Read and write (Unicode) lines from/to serial port. The encoding is applied. """ TERMINATOR = b'\r\n' ENCODING = 'utf-8' UNICODE_HANDLING = 'replace' def handle_packet(self, packet): self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING)) def handle_line(self, line): """Process one line - to be overridden by subclassing""" raise NotImplementedError('please implement functionality in handle_line') def write_line(self, text): """ Write text to the transport. ``text`` is a Unicode string and the encoding is applied before sending ans also the newline is append. """ # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR) class ReaderThread(threading.Thread): """\ Implement a serial port read loop and dispatch to a Protocol instance (like the asyncio.Protocol) but do it with threads. Calls to close() will close the serial port but it is also possible to just stop() this thread and continue the serial port instance otherwise. """ def __init__(self, serial_instance, protocol_factory): """\ Initialize thread. Note that the serial_instance' timeout is set to one second! Other settings are not changed. """ super(ReaderThread, self).__init__() self.daemon = True self.serial = serial_instance self.protocol_factory = protocol_factory self.alive = True self._lock = threading.Lock() self._connection_made = threading.Event() self.protocol = None def stop(self): """Stop the reader thread""" self.alive = False if hasattr(self.serial, 'cancel_read'): self.serial.cancel_read() self.join(2) def run(self): """Reader loop""" if not hasattr(self.serial, 'cancel_read'): self.serial.timeout = 1 self.protocol = self.protocol_factory() try: self.protocol.connection_made(self) except Exception as e: self.alive = False self.protocol.connection_lost(e) self._connection_made.set() return error = None self._connection_made.set() while self.alive and self.serial.is_open: try: # read all that is there or wait for one byte (blocking) data = self.serial.read(self.serial.in_waiting or 1) except serial.SerialException as e: # probably some I/O problem such as disconnected USB serial # adapters -> exit error = e break else: if data: # make a separated try-except for called user code try: self.protocol.data_received(data) except Exception as e: error = e break self.alive = False self.protocol.connection_lost(error) self.protocol = None def write(self, data): """Thread safe writing (uses lock)""" with self._lock: return self.serial.write(data) def close(self): """Close the serial port and exit reader thread (uses lock)""" # use the lock to let other threads finish writing with self._lock: # first stop reading, so that closing can be done on idle port self.stop() self.serial.close() def connect(self): """ Wait until connection is set up and return the transport and protocol instances. """ if self.alive: self._connection_made.wait() if not self.alive: raise RuntimeError('connection_lost already called') return (self, self.protocol) else: raise RuntimeError('already stopped') # - - context manager, returns protocol def __enter__(self): """\ Enter context handler. May raise RuntimeError in case the connection could not be created. """ self.start() self._connection_made.wait() if not self.alive: raise RuntimeError('connection_lost already called') return self.protocol def __exit__(self, exc_type, exc_val, exc_tb): """Leave context: close port""" self.close() # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # test if __name__ == '__main__': # pylint: disable=wrong-import-position import sys import time import traceback #~ PORT = 'spy:///dev/ttyUSB0' PORT = 'loop://' class PrintLines(LineReader): def connection_made(self, transport): super(PrintLines, self).connection_made(transport) sys.stdout.write('port opened\n') self.write_line('hello world') def handle_line(self, data): sys.stdout.write('line received: {!r}\n'.format(data)) def connection_lost(self, exc): if exc: traceback.print_exc(exc) sys.stdout.write('port closed\n') ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1) with ReaderThread(ser, PrintLines) as protocol: protocol.write_line('hello') time.sleep(2) # alternative usage ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1) t = ReaderThread(ser, PrintLines) t.start() transport, protocol = t.connect() protocol.write_line('hello') time.sleep(2) t.close() pyserial-3.5/serial/tools/0000775000175000017500000000000013756631132016103 5ustar chrischris00000000000000pyserial-3.5/serial/tools/list_ports_windows.py0000664000175000017500000003722513755350224022441 0ustar chrischris00000000000000#! python # # Enumerate serial ports on Windows including a human readable description # and hardware information. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2016 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import # pylint: disable=invalid-name,too-few-public-methods import re import ctypes from ctypes.wintypes import BOOL from ctypes.wintypes import HWND from ctypes.wintypes import DWORD from ctypes.wintypes import WORD from ctypes.wintypes import LONG from ctypes.wintypes import ULONG from ctypes.wintypes import HKEY from ctypes.wintypes import BYTE import serial from serial.win32 import ULONG_PTR from serial.tools import list_ports_common def ValidHandle(value, func, arguments): if value == 0: raise ctypes.WinError() return value NULL = 0 HDEVINFO = ctypes.c_void_p LPCTSTR = ctypes.c_wchar_p PCTSTR = ctypes.c_wchar_p PTSTR = ctypes.c_wchar_p LPDWORD = PDWORD = ctypes.POINTER(DWORD) #~ LPBYTE = PBYTE = ctypes.POINTER(BYTE) LPBYTE = PBYTE = ctypes.c_void_p # XXX avoids error about types ACCESS_MASK = DWORD REGSAM = ACCESS_MASK class GUID(ctypes.Structure): _fields_ = [ ('Data1', DWORD), ('Data2', WORD), ('Data3', WORD), ('Data4', BYTE * 8), ] def __str__(self): return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format( self.Data1, self.Data2, self.Data3, ''.join(["{:02x}".format(d) for d in self.Data4[:2]]), ''.join(["{:02x}".format(d) for d in self.Data4[2:]]), ) class SP_DEVINFO_DATA(ctypes.Structure): _fields_ = [ ('cbSize', DWORD), ('ClassGuid', GUID), ('DevInst', DWORD), ('Reserved', ULONG_PTR), ] def __str__(self): return "ClassGuid:{} DevInst:{}".format(self.ClassGuid, self.DevInst) PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA) PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p setupapi = ctypes.windll.LoadLibrary("setupapi") SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO] SetupDiDestroyDeviceInfoList.restype = BOOL SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameW SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD] SetupDiClassGuidsFromName.restype = BOOL SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA] SetupDiEnumDeviceInfo.restype = BOOL SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD] SetupDiGetClassDevs.restype = HDEVINFO SetupDiGetClassDevs.errcheck = ValidHandle SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD] SetupDiGetDeviceRegistryProperty.restype = BOOL SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdW SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD] SetupDiGetDeviceInstanceId.restype = BOOL SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM] SetupDiOpenDevRegKey.restype = HKEY advapi32 = ctypes.windll.LoadLibrary("Advapi32") RegCloseKey = advapi32.RegCloseKey RegCloseKey.argtypes = [HKEY] RegCloseKey.restype = LONG RegQueryValueEx = advapi32.RegQueryValueExW RegQueryValueEx.argtypes = [HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD] RegQueryValueEx.restype = LONG cfgmgr32 = ctypes.windll.LoadLibrary("Cfgmgr32") CM_Get_Parent = cfgmgr32.CM_Get_Parent CM_Get_Parent.argtypes = [PDWORD, DWORD, ULONG] CM_Get_Parent.restype = LONG CM_Get_Device_IDW = cfgmgr32.CM_Get_Device_IDW CM_Get_Device_IDW.argtypes = [DWORD, PTSTR, ULONG, ULONG] CM_Get_Device_IDW.restype = LONG CM_MapCrToWin32Err = cfgmgr32.CM_MapCrToWin32Err CM_MapCrToWin32Err.argtypes = [DWORD, DWORD] CM_MapCrToWin32Err.restype = DWORD DIGCF_PRESENT = 2 DIGCF_DEVICEINTERFACE = 16 INVALID_HANDLE_VALUE = 0 ERROR_INSUFFICIENT_BUFFER = 122 ERROR_NOT_FOUND = 1168 SPDRP_HARDWAREID = 1 SPDRP_FRIENDLYNAME = 12 SPDRP_LOCATION_PATHS = 35 SPDRP_MFG = 11 DICS_FLAG_GLOBAL = 1 DIREG_DEV = 0x00000001 KEY_READ = 0x20019 MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH = 5 def get_parent_serial_number(child_devinst, child_vid, child_pid, depth=0, last_serial_number=None): """ Get the serial number of the parent of a device. Args: child_devinst: The device instance handle to get the parent serial number of. child_vid: The vendor ID of the child device. child_pid: The product ID of the child device. depth: The current iteration depth of the USB device tree. """ # If the traversal depth is beyond the max, abandon attempting to find the serial number. if depth > MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH: return '' if not last_serial_number else last_serial_number # Get the parent device instance. devinst = DWORD() ret = CM_Get_Parent(ctypes.byref(devinst), child_devinst, 0) if ret: win_error = CM_MapCrToWin32Err(DWORD(ret), DWORD(0)) # If there is no parent available, the child was the root device. We cannot traverse # further. if win_error == ERROR_NOT_FOUND: return '' if not last_serial_number else last_serial_number raise ctypes.WinError(win_error) # Get the ID of the parent device and parse it for vendor ID, product ID, and serial number. parentHardwareID = ctypes.create_unicode_buffer(250) ret = CM_Get_Device_IDW( devinst, parentHardwareID, ctypes.sizeof(parentHardwareID) - 1, 0) if ret: raise ctypes.WinError(CM_MapCrToWin32Err(DWORD(ret), DWORD(0))) parentHardwareID_str = parentHardwareID.value m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?', parentHardwareID_str, re.I) # return early if we have no matches (likely malformed serial, traversed too far) if not m: return '' if not last_serial_number else last_serial_number vid = None pid = None serial_number = None if m.group(1): vid = int(m.group(1), 16) if m.group(3): pid = int(m.group(3), 16) if m.group(7): serial_number = m.group(7) # store what we found as a fallback for malformed serial values up the chain found_serial_number = serial_number # Check that the USB serial number only contains alpha-numeric characters. It may be a windows # device ID (ephemeral ID). if serial_number and not re.match(r'^\w+$', serial_number): serial_number = None if not vid or not pid: # If pid and vid are not available at this device level, continue to the parent. return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number) if pid != child_pid or vid != child_vid: # If the VID or PID has changed, we are no longer looking at the same physical device. The # serial number is unknown. return '' if not last_serial_number else last_serial_number # In this case, the vid and pid of the parent device are identical to the child. However, if # there still isn't a serial number available, continue to the next parent. if not serial_number: return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number) # Finally, the VID and PID are identical to the child and a serial number is present, so return # it. return serial_number def iterate_comports(): """Return a generator that yields descriptions for serial ports""" PortsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough... ports_guids_size = DWORD() if not SetupDiClassGuidsFromName( "Ports", PortsGUIDs, ctypes.sizeof(PortsGUIDs), ctypes.byref(ports_guids_size)): raise ctypes.WinError() ModemsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough... modems_guids_size = DWORD() if not SetupDiClassGuidsFromName( "Modem", ModemsGUIDs, ctypes.sizeof(ModemsGUIDs), ctypes.byref(modems_guids_size)): raise ctypes.WinError() GUIDs = PortsGUIDs[:ports_guids_size.value] + ModemsGUIDs[:modems_guids_size.value] # repeat for all possible GUIDs for index in range(len(GUIDs)): bInterfaceNumber = None g_hdi = SetupDiGetClassDevs( ctypes.byref(GUIDs[index]), None, NULL, DIGCF_PRESENT) # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports devinfo = SP_DEVINFO_DATA() devinfo.cbSize = ctypes.sizeof(devinfo) index = 0 while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)): index += 1 # get the real com port name hkey = SetupDiOpenDevRegKey( g_hdi, ctypes.byref(devinfo), DICS_FLAG_GLOBAL, 0, DIREG_DEV, # DIREG_DRV for SW info KEY_READ) port_name_buffer = ctypes.create_unicode_buffer(250) port_name_length = ULONG(ctypes.sizeof(port_name_buffer)) RegQueryValueEx( hkey, "PortName", None, None, ctypes.byref(port_name_buffer), ctypes.byref(port_name_length)) RegCloseKey(hkey) # unfortunately does this method also include parallel ports. # we could check for names starting with COM or just exclude LPT # and hope that other "unknown" names are serial ports... if port_name_buffer.value.startswith('LPT'): continue # hardware ID szHardwareID = ctypes.create_unicode_buffer(250) # try to get ID that includes serial number if not SetupDiGetDeviceInstanceId( g_hdi, ctypes.byref(devinfo), #~ ctypes.byref(szHardwareID), szHardwareID, ctypes.sizeof(szHardwareID) - 1, None): # fall back to more generic hardware ID if that would fail if not SetupDiGetDeviceRegistryProperty( g_hdi, ctypes.byref(devinfo), SPDRP_HARDWAREID, None, ctypes.byref(szHardwareID), ctypes.sizeof(szHardwareID) - 1, None): # Ignore ERROR_INSUFFICIENT_BUFFER if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: raise ctypes.WinError() # stringify szHardwareID_str = szHardwareID.value info = list_ports_common.ListPortInfo(port_name_buffer.value, skip_link_detection=True) # in case of USB, make a more readable string, similar to that form # that we also generate on other platforms if szHardwareID_str.startswith('USB'): m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?', szHardwareID_str, re.I) if m: info.vid = int(m.group(1), 16) if m.group(3): info.pid = int(m.group(3), 16) if m.group(5): bInterfaceNumber = int(m.group(5)) # Check that the USB serial number only contains alpha-numeric characters. It # may be a windows device ID (ephemeral ID) for composite devices. if m.group(7) and re.match(r'^\w+$', m.group(7)): info.serial_number = m.group(7) else: info.serial_number = get_parent_serial_number(devinfo.DevInst, info.vid, info.pid) # calculate a location string loc_path_str = ctypes.create_unicode_buffer(250) if SetupDiGetDeviceRegistryProperty( g_hdi, ctypes.byref(devinfo), SPDRP_LOCATION_PATHS, None, ctypes.byref(loc_path_str), ctypes.sizeof(loc_path_str) - 1, None): m = re.finditer(r'USBROOT\((\w+)\)|#USB\((\w+)\)', loc_path_str.value) location = [] for g in m: if g.group(1): location.append('{:d}'.format(int(g.group(1)) + 1)) else: if len(location) > 1: location.append('.') else: location.append('-') location.append(g.group(2)) if bInterfaceNumber is not None: location.append(':{}.{}'.format( 'x', # XXX how to determine correct bConfigurationValue? bInterfaceNumber)) if location: info.location = ''.join(location) info.hwid = info.usb_info() elif szHardwareID_str.startswith('FTDIBUS'): m = re.search(r'VID_([0-9a-f]{4})\+PID_([0-9a-f]{4})(\+(\w+))?', szHardwareID_str, re.I) if m: info.vid = int(m.group(1), 16) info.pid = int(m.group(2), 16) if m.group(4): info.serial_number = m.group(4) # USB location is hidden by FDTI driver :( info.hwid = info.usb_info() else: info.hwid = szHardwareID_str # friendly name szFriendlyName = ctypes.create_unicode_buffer(250) if SetupDiGetDeviceRegistryProperty( g_hdi, ctypes.byref(devinfo), SPDRP_FRIENDLYNAME, #~ SPDRP_DEVICEDESC, None, ctypes.byref(szFriendlyName), ctypes.sizeof(szFriendlyName) - 1, None): info.description = szFriendlyName.value #~ else: # Ignore ERROR_INSUFFICIENT_BUFFER #~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: #~ raise IOError("failed to get details for %s (%s)" % (devinfo, szHardwareID.value)) # ignore errors and still include the port in the list, friendly name will be same as port name # manufacturer szManufacturer = ctypes.create_unicode_buffer(250) if SetupDiGetDeviceRegistryProperty( g_hdi, ctypes.byref(devinfo), SPDRP_MFG, #~ SPDRP_DEVICEDESC, None, ctypes.byref(szManufacturer), ctypes.sizeof(szManufacturer) - 1, None): info.manufacturer = szManufacturer.value yield info SetupDiDestroyDeviceInfoList(g_hdi) def comports(include_links=False): """Return a list of info objects about serial ports""" return list(iterate_comports()) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # test if __name__ == '__main__': for port, desc, hwid in sorted(comports()): print("{}: {} [{}]".format(port, desc, hwid)) pyserial-3.5/serial/tools/hexlify_codec.py0000644000175000017500000000713513641767344021276 0ustar chrischris00000000000000#! python # # This is a codec to create and decode hexdumps with spaces between characters. used by miniterm. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2015-2016 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause """\ Python 'hex' Codec - 2-digit hex with spaces content transfer encoding. Encode and decode may be a bit missleading at first sight... The textual representation is a hex dump: e.g. "40 41" The "encoded" data of this is the binary form, e.g. b"@A" Therefore decoding is binary to text and thus converting binary data to hex dump. """ from __future__ import absolute_import import codecs import serial try: unicode except (NameError, AttributeError): unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name HEXDIGITS = '0123456789ABCDEF' # Codec APIs def hex_encode(data, errors='strict'): """'40 41 42' -> b'@ab'""" return (serial.to_bytes([int(h, 16) for h in data.split()]), len(data)) def hex_decode(data, errors='strict'): """b'@ab' -> '40 41 42'""" return (unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))), len(data)) class Codec(codecs.Codec): def encode(self, data, errors='strict'): """'40 41 42' -> b'@ab'""" return serial.to_bytes([int(h, 16) for h in data.split()]) def decode(self, data, errors='strict'): """b'@ab' -> '40 41 42'""" return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) class IncrementalEncoder(codecs.IncrementalEncoder): """Incremental hex encoder""" def __init__(self, errors='strict'): self.errors = errors self.state = 0 def reset(self): self.state = 0 def getstate(self): return self.state def setstate(self, state): self.state = state def encode(self, data, final=False): """\ Incremental encode, keep track of digits and emit a byte when a pair of hex digits is found. The space is optional unless the error handling is defined to be 'strict'. """ state = self.state encoded = [] for c in data.upper(): if c in HEXDIGITS: z = HEXDIGITS.index(c) if state: encoded.append(z + (state & 0xf0)) state = 0 else: state = 0x100 + (z << 4) elif c == ' ': # allow spaces to separate values if state and self.errors == 'strict': raise UnicodeError('odd number of hex digits') state = 0 else: if self.errors == 'strict': raise UnicodeError('non-hex digit found: {!r}'.format(c)) self.state = state return serial.to_bytes(encoded) class IncrementalDecoder(codecs.IncrementalDecoder): """Incremental decoder""" def decode(self, data, final=False): return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) class StreamWriter(Codec, codecs.StreamWriter): """Combination of hexlify codec and StreamWriter""" class StreamReader(Codec, codecs.StreamReader): """Combination of hexlify codec and StreamReader""" def getregentry(): """encodings module API""" return codecs.CodecInfo( name='hexlify', encode=hex_encode, decode=hex_decode, incrementalencoder=IncrementalEncoder, incrementaldecoder=IncrementalDecoder, streamwriter=StreamWriter, streamreader=StreamReader, #~ _is_text_encoding=True, ) pyserial-3.5/serial/tools/list_ports.py0000644000175000017500000000647513641767344020701 0ustar chrischris00000000000000#!/usr/bin/env python # # Serial port enumeration. Console tool and backend selection. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2011-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause """\ This module will provide a function called comports that returns an iterable (generator or list) that will enumerate available com ports. Note that on some systems non-existent ports may be listed. Additionally a grep function is supplied that can be used to search for ports based on their descriptions or hardware ID. """ from __future__ import absolute_import import sys import os import re # chose an implementation, depending on os #~ if sys.platform == 'cli': #~ else: if os.name == 'nt': # sys.platform == 'win32': from serial.tools.list_ports_windows import comports elif os.name == 'posix': from serial.tools.list_ports_posix import comports #~ elif os.name == 'java': else: raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def grep(regexp, include_links=False): """\ Search for ports using a regular expression. Port name, description and hardware ID are searched. The function returns an iterable that returns the same tuples as comport() would do. """ r = re.compile(regexp, re.I) for info in comports(include_links): port, desc, hwid = info if r.search(port) or r.search(desc) or r.search(hwid): yield info # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def main(): import argparse parser = argparse.ArgumentParser(description='Serial port enumeration') parser.add_argument( 'regexp', nargs='?', help='only show ports that match this regex') parser.add_argument( '-v', '--verbose', action='store_true', help='show more messages') parser.add_argument( '-q', '--quiet', action='store_true', help='suppress all messages') parser.add_argument( '-n', type=int, help='only output the N-th entry') parser.add_argument( '-s', '--include-links', action='store_true', help='include entries that are symlinks to real devices') args = parser.parse_args() hits = 0 # get iteraror w/ or w/o filter if args.regexp: if not args.quiet: sys.stderr.write("Filtered list with regexp: {!r}\n".format(args.regexp)) iterator = sorted(grep(args.regexp, include_links=args.include_links)) else: iterator = sorted(comports(include_links=args.include_links)) # list them for n, (port, desc, hwid) in enumerate(iterator, 1): if args.n is None or args.n == n: sys.stdout.write("{:20}\n".format(port)) if args.verbose: sys.stdout.write(" desc: {}\n".format(desc)) sys.stdout.write(" hwid: {}\n".format(hwid)) hits += 1 if not args.quiet: if hits: sys.stderr.write("{} ports found\n".format(hits)) else: sys.stderr.write("no ports found\n") # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # test if __name__ == '__main__': main() pyserial-3.5/serial/tools/list_ports_linux.py0000664000175000017500000001062713731757326022113 0ustar chrischris00000000000000#!/usr/bin/env python # # This is a module that gathers a list of serial ports including details on # GNU/Linux systems. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2011-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import import glob import os from serial.tools import list_ports_common class SysFS(list_ports_common.ListPortInfo): """Wrapper for easy sysfs access and device info""" def __init__(self, device): super(SysFS, self).__init__(device) # special handling for links if device is not None and os.path.islink(device): device = os.path.realpath(device) is_link = True else: is_link = False self.usb_device_path = None if os.path.exists('/sys/class/tty/{}/device'.format(self.name)): self.device_path = os.path.realpath('/sys/class/tty/{}/device'.format(self.name)) self.subsystem = os.path.basename(os.path.realpath(os.path.join(self.device_path, 'subsystem'))) else: self.device_path = None self.subsystem = None # check device type if self.subsystem == 'usb-serial': self.usb_interface_path = os.path.dirname(self.device_path) elif self.subsystem == 'usb': self.usb_interface_path = self.device_path else: self.usb_interface_path = None # fill-in info for USB devices if self.usb_interface_path is not None: self.usb_device_path = os.path.dirname(self.usb_interface_path) try: num_if = int(self.read_line(self.usb_device_path, 'bNumInterfaces')) except ValueError: num_if = 1 self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16) self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16) self.serial_number = self.read_line(self.usb_device_path, 'serial') if num_if > 1: # multi interface devices like FT4232 self.location = os.path.basename(self.usb_interface_path) else: self.location = os.path.basename(self.usb_device_path) self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer') self.product = self.read_line(self.usb_device_path, 'product') self.interface = self.read_line(self.usb_interface_path, 'interface') if self.subsystem in ('usb', 'usb-serial'): self.apply_usb_info() #~ elif self.subsystem in ('pnp', 'amba'): # PCI based devices, raspi elif self.subsystem == 'pnp': # PCI based devices self.description = self.name self.hwid = self.read_line(self.device_path, 'id') elif self.subsystem == 'amba': # raspi self.description = self.name self.hwid = os.path.basename(self.device_path) if is_link: self.hwid += ' LINK={}'.format(device) def read_line(self, *args): """\ Helper function to read a single line from a file. One or more parameters are allowed, they are joined with os.path.join. Returns None on errors.. """ try: with open(os.path.join(*args)) as f: line = f.readline().strip() return line except IOError: return None def comports(include_links=False): devices = glob.glob('/dev/ttyS*') # built-in serial ports devices.extend(glob.glob('/dev/ttyUSB*')) # usb-serial with own driver devices.extend(glob.glob('/dev/ttyXRUSB*')) # xr-usb-serial port exar (DELL Edge 3001) devices.extend(glob.glob('/dev/ttyACM*')) # usb-serial with CDC-ACM profile devices.extend(glob.glob('/dev/ttyAMA*')) # ARM internal port (raspi) devices.extend(glob.glob('/dev/rfcomm*')) # BT serial devices devices.extend(glob.glob('/dev/ttyAP*')) # Advantech multi-port serial controllers if include_links: devices.extend(list_ports_common.list_links(devices)) return [info for info in [SysFS(d) for d in devices] if info.subsystem != "platform"] # hide non-present internal serial ports # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # test if __name__ == '__main__': for info in sorted(comports()): print("{0}: {0.subsystem}".format(info)) pyserial-3.5/serial/tools/list_ports_common.py0000644000175000017500000000723013641767344022237 0ustar chrischris00000000000000#!/usr/bin/env python # # This is a helper module for the various platform dependent list_port # implementations. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import import re import glob import os import os.path def numsplit(text): """\ Convert string into a list of texts and numbers in order to support a natural sorting. """ result = [] for group in re.split(r'(\d+)', text): if group: try: group = int(group) except ValueError: pass result.append(group) return result class ListPortInfo(object): """Info collection base class for serial ports""" def __init__(self, device, skip_link_detection=False): self.device = device self.name = os.path.basename(device) self.description = 'n/a' self.hwid = 'n/a' # USB specific data self.vid = None self.pid = None self.serial_number = None self.location = None self.manufacturer = None self.product = None self.interface = None # special handling for links if not skip_link_detection and device is not None and os.path.islink(device): self.hwid = 'LINK={}'.format(os.path.realpath(device)) def usb_description(self): """return a short string to name the port based on USB info""" if self.interface is not None: return '{} - {}'.format(self.product, self.interface) elif self.product is not None: return self.product else: return self.name def usb_info(self): """return a string with USB related information about device""" return 'USB VID:PID={:04X}:{:04X}{}{}'.format( self.vid or 0, self.pid or 0, ' SER={}'.format(self.serial_number) if self.serial_number is not None else '', ' LOCATION={}'.format(self.location) if self.location is not None else '') def apply_usb_info(self): """update description and hwid from USB data""" self.description = self.usb_description() self.hwid = self.usb_info() def __eq__(self, other): return isinstance(other, ListPortInfo) and self.device == other.device def __hash__(self): return hash(self.device) def __lt__(self, other): if not isinstance(other, ListPortInfo): raise TypeError('unorderable types: {}() and {}()'.format( type(self).__name__, type(other).__name__)) return numsplit(self.device) < numsplit(other.device) def __str__(self): return '{} - {}'.format(self.device, self.description) def __getitem__(self, index): """Item access: backwards compatible -> (port, desc, hwid)""" if index == 0: return self.device elif index == 1: return self.description elif index == 2: return self.hwid else: raise IndexError('{} > 2'.format(index)) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def list_links(devices): """\ search all /dev devices and look for symlinks to known ports already listed in devices. """ links = [] for device in glob.glob('/dev/*'): if os.path.islink(device) and os.path.realpath(device) in devices: links.append(device) return links # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # test if __name__ == '__main__': print(ListPortInfo('dummy')) pyserial-3.5/serial/tools/__init__.py0000644000175000017500000000000013641767344020210 0ustar chrischris00000000000000pyserial-3.5/serial/tools/list_ports_posix.py0000644000175000017500000001066713641767344022121 0ustar chrischris00000000000000#!/usr/bin/env python # # This is a module that gathers a list of serial ports on POSIXy systems. # For some specific implementations, see also list_ports_linux, list_ports_osx # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2011-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause """\ The ``comports`` function is expected to return an iterable that yields tuples of 3 strings: port name, human readable description and a hardware ID. As currently no method is known to get the second two strings easily, they are currently just identical to the port name. """ from __future__ import absolute_import import glob import sys import os from serial.tools import list_ports_common # try to detect the OS so that a device can be selected... plat = sys.platform.lower() if plat[:5] == 'linux': # Linux (confirmed) # noqa from serial.tools.list_ports_linux import comports elif plat[:6] == 'darwin': # OS X (confirmed) from serial.tools.list_ports_osx import comports elif plat == 'cygwin': # cygwin/win32 # cygwin accepts /dev/com* in many contexts # (such as 'open' call, explicit 'ls'), but 'glob.glob' # and bare 'ls' do not; so use /dev/ttyS* instead def comports(include_links=False): devices = glob.glob('/dev/ttyS*') if include_links: devices.extend(list_ports_common.list_links(devices)) return [list_ports_common.ListPortInfo(d) for d in devices] elif plat[:7] == 'openbsd': # OpenBSD def comports(include_links=False): devices = glob.glob('/dev/cua*') if include_links: devices.extend(list_ports_common.list_links(devices)) return [list_ports_common.ListPortInfo(d) for d in devices] elif plat[:3] == 'bsd' or plat[:7] == 'freebsd': def comports(include_links=False): devices = glob.glob('/dev/cua*[!.init][!.lock]') if include_links: devices.extend(list_ports_common.list_links(devices)) return [list_ports_common.ListPortInfo(d) for d in devices] elif plat[:6] == 'netbsd': # NetBSD def comports(include_links=False): """scan for available ports. return a list of device names.""" devices = glob.glob('/dev/dty*') if include_links: devices.extend(list_ports_common.list_links(devices)) return [list_ports_common.ListPortInfo(d) for d in devices] elif plat[:4] == 'irix': # IRIX def comports(include_links=False): """scan for available ports. return a list of device names.""" devices = glob.glob('/dev/ttyf*') if include_links: devices.extend(list_ports_common.list_links(devices)) return [list_ports_common.ListPortInfo(d) for d in devices] elif plat[:2] == 'hp': # HP-UX (not tested) def comports(include_links=False): """scan for available ports. return a list of device names.""" devices = glob.glob('/dev/tty*p0') if include_links: devices.extend(list_ports_common.list_links(devices)) return [list_ports_common.ListPortInfo(d) for d in devices] elif plat[:5] == 'sunos': # Solaris/SunOS def comports(include_links=False): """scan for available ports. return a list of device names.""" devices = glob.glob('/dev/tty*c') if include_links: devices.extend(list_ports_common.list_links(devices)) return [list_ports_common.ListPortInfo(d) for d in devices] elif plat[:3] == 'aix': # AIX def comports(include_links=False): """scan for available ports. return a list of device names.""" devices = glob.glob('/dev/tty*') if include_links: devices.extend(list_ports_common.list_links(devices)) return [list_ports_common.ListPortInfo(d) for d in devices] else: # platform detection has failed... import serial sys.stderr.write("""\ don't know how to enumerate ttys on this system. ! I you know how the serial ports are named send this information to ! the author of this module: sys.platform = {!r} os.name = {!r} pySerial version = {} also add the naming scheme of the serial ports and with a bit luck you can get this module running... """.format(sys.platform, os.name, serial.VERSION)) raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) # test if __name__ == '__main__': for port, desc, hwid in sorted(comports()): print("{}: {} [{}]".format(port, desc, hwid)) pyserial-3.5/serial/tools/list_ports_osx.py0000664000175000017500000002565213756630436021570 0ustar chrischris00000000000000#!/usr/bin/env python # # This is a module that gathers a list of serial ports including details on OSX # # code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools # with contributions from cibomahto, dgs3, FarMcKon, tedbrandston # and modifications by cliechti, hoihu, hardkrash # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2013-2020 # # SPDX-License-Identifier: BSD-3-Clause # List all of the callout devices in OS/X by querying IOKit. # See the following for a reference of how to do this: # http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD # More help from darwin_hid.py # Also see the 'IORegistryExplorer' for an idea of what we are actually searching from __future__ import absolute_import import ctypes from serial.tools import list_ports_common iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit') cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation') # kIOMasterPortDefault is no longer exported in BigSur but no biggie, using NULL works just the same kIOMasterPortDefault = 0 # WAS: ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault") kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault") kCFStringEncodingMacRoman = 0 kCFStringEncodingUTF8 = 0x08000100 # defined in `IOKit/usb/USBSpec.h` kUSBVendorString = 'USB Vendor Name' kUSBSerialNumberString = 'USB Serial Number' # `io_name_t` defined as `typedef char io_name_t[128];` # in `device/device_types.h` io_name_size = 128 # defined in `mach/kern_return.h` KERN_SUCCESS = 0 # kern_return_t defined as `typedef int kern_return_t;` in `mach/i386/kern_return.h` kern_return_t = ctypes.c_int iokit.IOServiceMatching.restype = ctypes.c_void_p iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] iokit.IOServiceGetMatchingServices.restype = kern_return_t iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] iokit.IOServiceGetMatchingServices.restype = kern_return_t iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32] iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] iokit.IORegistryEntryGetPath.restype = kern_return_t iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p] iokit.IORegistryEntryGetName.restype = kern_return_t iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p] iokit.IOObjectGetClass.restype = kern_return_t iokit.IOObjectRelease.argtypes = [ctypes.c_void_p] cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32] cf.CFStringCreateWithCString.restype = ctypes.c_void_p cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32] cf.CFStringGetCStringPtr.restype = ctypes.c_char_p cf.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_uint32] cf.CFStringGetCString.restype = ctypes.c_bool cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p] cf.CFNumberGetValue.restype = ctypes.c_void_p # void CFRelease ( CFTypeRef cf ); cf.CFRelease.argtypes = [ctypes.c_void_p] cf.CFRelease.restype = None # CFNumber type defines kCFNumberSInt8Type = 1 kCFNumberSInt16Type = 2 kCFNumberSInt32Type = 3 kCFNumberSInt64Type = 4 def get_string_property(device_type, property): """ Search the given device for the specified string property @param device_type Type of Device @param property String to search for @return Python string containing the value, or None if not found. """ key = cf.CFStringCreateWithCString( kCFAllocatorDefault, property.encode("utf-8"), kCFStringEncodingUTF8) CFContainer = iokit.IORegistryEntryCreateCFProperty( device_type, key, kCFAllocatorDefault, 0) output = None if CFContainer: output = cf.CFStringGetCStringPtr(CFContainer, 0) if output is not None: output = output.decode('utf-8') else: buffer = ctypes.create_string_buffer(io_name_size); success = cf.CFStringGetCString(CFContainer, ctypes.byref(buffer), io_name_size, kCFStringEncodingUTF8) if success: output = buffer.value.decode('utf-8') cf.CFRelease(CFContainer) return output def get_int_property(device_type, property, cf_number_type): """ Search the given device for the specified string property @param device_type Device to search @param property String to search for @param cf_number_type CFType number @return Python string containing the value, or None if not found. """ key = cf.CFStringCreateWithCString( kCFAllocatorDefault, property.encode("utf-8"), kCFStringEncodingUTF8) CFContainer = iokit.IORegistryEntryCreateCFProperty( device_type, key, kCFAllocatorDefault, 0) if CFContainer: if (cf_number_type == kCFNumberSInt32Type): number = ctypes.c_uint32() elif (cf_number_type == kCFNumberSInt16Type): number = ctypes.c_uint16() cf.CFNumberGetValue(CFContainer, cf_number_type, ctypes.byref(number)) cf.CFRelease(CFContainer) return number.value return None def IORegistryEntryGetName(device): devicename = ctypes.create_string_buffer(io_name_size); res = iokit.IORegistryEntryGetName(device, ctypes.byref(devicename)) if res != KERN_SUCCESS: return None # this works in python2 but may not be valid. Also I don't know if # this encoding is guaranteed. It may be dependent on system locale. return devicename.value.decode('utf-8') def IOObjectGetClass(device): classname = ctypes.create_string_buffer(io_name_size) iokit.IOObjectGetClass(device, ctypes.byref(classname)) return classname.value def GetParentDeviceByType(device, parent_type): """ Find the first parent of a device that implements the parent_type @param IOService Service to inspect @return Pointer to the parent type, or None if it was not found. """ # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice. parent_type = parent_type.encode('utf-8') while IOObjectGetClass(device) != parent_type: parent = ctypes.c_void_p() response = iokit.IORegistryEntryGetParentEntry( device, "IOService".encode("utf-8"), ctypes.byref(parent)) # If we weren't able to find a parent for the device, we're done. if response != KERN_SUCCESS: return None device = parent return device def GetIOServicesByType(service_type): """ returns iterator over specified service_type """ serial_port_iterator = ctypes.c_void_p() iokit.IOServiceGetMatchingServices( kIOMasterPortDefault, iokit.IOServiceMatching(service_type.encode('utf-8')), ctypes.byref(serial_port_iterator)) services = [] while iokit.IOIteratorIsValid(serial_port_iterator): service = iokit.IOIteratorNext(serial_port_iterator) if not service: break services.append(service) iokit.IOObjectRelease(serial_port_iterator) return services def location_to_string(locationID): """ helper to calculate port and bus number from locationID """ loc = ['{}-'.format(locationID >> 24)] while locationID & 0xf00000: if len(loc) > 1: loc.append('.') loc.append('{}'.format((locationID >> 20) & 0xf)) locationID <<= 4 return ''.join(loc) class SuitableSerialInterface(object): pass def scan_interfaces(): """ helper function to scan USB interfaces returns a list of SuitableSerialInterface objects with name and id attributes """ interfaces = [] for service in GetIOServicesByType('IOSerialBSDClient'): device = get_string_property(service, "IOCalloutDevice") if device: usb_device = GetParentDeviceByType(service, "IOUSBInterface") if usb_device: name = get_string_property(usb_device, "USB Interface Name") or None locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) or '' i = SuitableSerialInterface() i.id = locationID i.name = name interfaces.append(i) return interfaces def search_for_locationID_in_interfaces(serial_interfaces, locationID): for interface in serial_interfaces: if (interface.id == locationID): return interface.name return None def comports(include_links=False): # XXX include_links is currently ignored. are links in /dev even supported here? # Scan for all iokit serial ports services = GetIOServicesByType('IOSerialBSDClient') ports = [] serial_interfaces = scan_interfaces() for service in services: # First, add the callout device file. device = get_string_property(service, "IOCalloutDevice") if device: info = list_ports_common.ListPortInfo(device) # If the serial port is implemented by IOUSBDevice # NOTE IOUSBDevice was deprecated as of 10.11 and finally on Apple Silicon # devices has been completely removed. Thanks to @oskay for this patch. usb_device = GetParentDeviceByType(service, "IOUSBHostDevice") if not usb_device: usb_device = GetParentDeviceByType(service, "IOUSBDevice") if usb_device: # fetch some useful informations from properties info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type) info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type) info.serial_number = get_string_property(usb_device, kUSBSerialNumberString) # We know this is a usb device, so the # IORegistryEntryName should always be aliased to the # usb product name string descriptor. info.product = IORegistryEntryGetName(usb_device) or 'n/a' info.manufacturer = get_string_property(usb_device, kUSBVendorString) locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) info.location = location_to_string(locationID) info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID) info.apply_usb_info() ports.append(info) return ports # test if __name__ == '__main__': for port, desc, hwid in sorted(comports()): print("{}: {} [{}]".format(port, desc, hwid)) pyserial-3.5/serial/tools/miniterm.py0000664000175000017500000011172013756626216020312 0ustar chrischris00000000000000#!/usr/bin/env python # # Very simple serial terminal # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C)2002-2020 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import import codecs import os import sys import threading import serial from serial.tools.list_ports import comports from serial.tools import hexlify_codec # pylint: disable=wrong-import-order,wrong-import-position codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None) try: raw_input except NameError: # pylint: disable=redefined-builtin,invalid-name raw_input = input # in python3 it's "raw" unichr = chr def key_description(character): """generate a readable description for a key""" ascii_code = ord(character) if ascii_code < 32: return 'Ctrl+{:c}'.format(ord('@') + ascii_code) else: return repr(character) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class ConsoleBase(object): """OS abstraction for console (input/output codec, no echo)""" def __init__(self): if sys.version_info >= (3, 0): self.byte_output = sys.stdout.buffer else: self.byte_output = sys.stdout self.output = sys.stdout def setup(self): """Set console to read single characters, no echo""" def cleanup(self): """Restore default console settings""" def getkey(self): """Read a single key from the console""" return None def write_bytes(self, byte_string): """Write bytes (already encoded)""" self.byte_output.write(byte_string) self.byte_output.flush() def write(self, text): """Write string""" self.output.write(text) self.output.flush() def cancel(self): """Cancel getkey operation""" # - - - - - - - - - - - - - - - - - - - - - - - - # context manager: # switch terminal temporary to normal mode (e.g. to get user input) def __enter__(self): self.cleanup() return self def __exit__(self, *args, **kwargs): self.setup() if os.name == 'nt': # noqa import msvcrt import ctypes import platform class Out(object): """file-like wrapper that uses os.write""" def __init__(self, fd): self.fd = fd def flush(self): pass def write(self, s): os.write(self.fd, s) class Console(ConsoleBase): fncodes = { ';': '\1bOP', # F1 '<': '\1bOQ', # F2 '=': '\1bOR', # F3 '>': '\1bOS', # F4 '?': '\1b[15~', # F5 '@': '\1b[17~', # F6 'A': '\1b[18~', # F7 'B': '\1b[19~', # F8 'C': '\1b[20~', # F9 'D': '\1b[21~', # F10 } navcodes = { 'H': '\x1b[A', # UP 'P': '\x1b[B', # DOWN 'K': '\x1b[D', # LEFT 'M': '\x1b[C', # RIGHT 'G': '\x1b[H', # HOME 'O': '\x1b[F', # END 'R': '\x1b[2~', # INSERT 'S': '\x1b[3~', # DELETE 'I': '\x1b[5~', # PGUP 'Q': '\x1b[6~', # PGDN } def __init__(self): super(Console, self).__init__() self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP() self._saved_icp = ctypes.windll.kernel32.GetConsoleCP() ctypes.windll.kernel32.SetConsoleOutputCP(65001) ctypes.windll.kernel32.SetConsoleCP(65001) # ANSI handling available through SetConsoleMode since Windows 10 v1511 # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1 if platform.release() == '10' and int(platform.version().split('.')[2]) > 10586: ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 import ctypes.wintypes as wintypes if not hasattr(wintypes, 'LPDWORD'): # PY2 wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD) SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode GetStdHandle = ctypes.windll.kernel32.GetStdHandle mode = wintypes.DWORD() GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode)) if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: SetConsoleMode(GetStdHandle(-11), mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) self._saved_cm = mode self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace') # the change of the code page is not propagated to Python, manually fix it sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace') sys.stdout = self.output self.output.encoding = 'UTF-8' # needed for input def __del__(self): ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp) ctypes.windll.kernel32.SetConsoleCP(self._saved_icp) try: ctypes.windll.kernel32.SetConsoleMode(ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm) except AttributeError: # in case no _saved_cm pass def getkey(self): while True: z = msvcrt.getwch() if z == unichr(13): return unichr(10) elif z is unichr(0) or z is unichr(0xe0): try: code = msvcrt.getwch() if z is unichr(0): return self.fncodes[code] else: return self.navcodes[code] except KeyError: pass else: return z def cancel(self): # CancelIo, CancelSynchronousIo do not seem to work when using # getwch, so instead, send a key to the window with the console hwnd = ctypes.windll.kernel32.GetConsoleWindow() ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0) elif os.name == 'posix': import atexit import termios import fcntl class Console(ConsoleBase): def __init__(self): super(Console, self).__init__() self.fd = sys.stdin.fileno() self.old = termios.tcgetattr(self.fd) atexit.register(self.cleanup) if sys.version_info < (3, 0): self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin) else: self.enc_stdin = sys.stdin def setup(self): new = termios.tcgetattr(self.fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG new[6][termios.VMIN] = 1 new[6][termios.VTIME] = 0 termios.tcsetattr(self.fd, termios.TCSANOW, new) def getkey(self): c = self.enc_stdin.read(1) if c == unichr(0x7f): c = unichr(8) # map the BS key (which yields DEL) to backspace return c def cancel(self): fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0') def cleanup(self): termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old) else: raise NotImplementedError( 'Sorry no implementation for your platform ({}) available.'.format(sys.platform)) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class Transform(object): """do-nothing: forward all data unchanged""" def rx(self, text): """text received from serial port""" return text def tx(self, text): """text to be sent to serial port""" return text def echo(self, text): """text to be sent but displayed on console""" return text class CRLF(Transform): """ENTER sends CR+LF""" def tx(self, text): return text.replace('\n', '\r\n') class CR(Transform): """ENTER sends CR""" def rx(self, text): return text.replace('\r', '\n') def tx(self, text): return text.replace('\n', '\r') class LF(Transform): """ENTER sends LF""" class NoTerminal(Transform): """remove typical terminal control codes from input""" REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t') REPLACEMENT_MAP.update( { 0x7F: 0x2421, # DEL 0x9B: 0x2425, # CSI }) def rx(self, text): return text.translate(self.REPLACEMENT_MAP) echo = rx class NoControls(NoTerminal): """Remove all control codes, incl. CR+LF""" REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32)) REPLACEMENT_MAP.update( { 0x20: 0x2423, # visual space 0x7F: 0x2421, # DEL 0x9B: 0x2425, # CSI }) class Printable(Transform): """Show decimal code for all non-ASCII characters and replace most control codes""" def rx(self, text): r = [] for c in text: if ' ' <= c < '\x7f' or c in '\r\n\b\t': r.append(c) elif c < ' ': r.append(unichr(0x2400 + ord(c))) else: r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c))) r.append(' ') return ''.join(r) echo = rx class Colorize(Transform): """Apply different colors for received and echo""" def __init__(self): # XXX make it configurable, use colorama? self.input_color = '\x1b[37m' self.echo_color = '\x1b[31m' def rx(self, text): return self.input_color + text def echo(self, text): return self.echo_color + text class DebugIO(Transform): """Print what is sent and received""" def rx(self, text): sys.stderr.write(' [RX:{!r}] '.format(text)) sys.stderr.flush() return text def tx(self, text): sys.stderr.write(' [TX:{!r}] '.format(text)) sys.stderr.flush() return text # other ideas: # - add date/time for each newline # - insert newline after: a) timeout b) packet end character EOL_TRANSFORMATIONS = { 'crlf': CRLF, 'cr': CR, 'lf': LF, } TRANSFORMATIONS = { 'direct': Transform, # no transformation 'default': NoTerminal, 'nocontrol': NoControls, 'printable': Printable, 'colorize': Colorize, 'debug': DebugIO, } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def ask_for_port(): """\ Show a list of ports and ask the user for a choice. To make selection easier on systems with long device names, also allow the input of an index. """ sys.stderr.write('\n--- Available ports:\n') ports = [] for n, (port, desc, hwid) in enumerate(sorted(comports()), 1): sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc)) ports.append(port) while True: port = raw_input('--- Enter port index or full name: ') try: index = int(port) - 1 if not 0 <= index < len(ports): sys.stderr.write('--- Invalid index!\n') continue except ValueError: pass else: port = ports[index] return port class Miniterm(object): """\ Terminal application. Copy data from serial port to console and vice versa. Handle special keys from the console to show menu etc. """ def __init__(self, serial_instance, echo=False, eol='crlf', filters=()): self.console = Console() self.serial = serial_instance self.echo = echo self.raw = False self.input_encoding = 'UTF-8' self.output_encoding = 'UTF-8' self.eol = eol self.filters = filters self.update_transformations() self.exit_character = unichr(0x1d) # GS/CTRL+] self.menu_character = unichr(0x14) # Menu: CTRL+T self.alive = None self._reader_alive = None self.receiver_thread = None self.rx_decoder = None self.tx_decoder = None def _start_reader(self): """Start reader thread""" self._reader_alive = True # start serial->console thread self.receiver_thread = threading.Thread(target=self.reader, name='rx') self.receiver_thread.daemon = True self.receiver_thread.start() def _stop_reader(self): """Stop reader thread only, wait for clean exit of thread""" self._reader_alive = False if hasattr(self.serial, 'cancel_read'): self.serial.cancel_read() self.receiver_thread.join() def start(self): """start worker threads""" self.alive = True self._start_reader() # enter console->serial loop self.transmitter_thread = threading.Thread(target=self.writer, name='tx') self.transmitter_thread.daemon = True self.transmitter_thread.start() self.console.setup() def stop(self): """set flag to stop worker threads""" self.alive = False def join(self, transmit_only=False): """wait for worker threads to terminate""" self.transmitter_thread.join() if not transmit_only: if hasattr(self.serial, 'cancel_read'): self.serial.cancel_read() self.receiver_thread.join() def close(self): self.serial.close() def update_transformations(self): """take list of transformation classes and instantiate them for rx and tx""" transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f] for f in self.filters] self.tx_transformations = [t() for t in transformations] self.rx_transformations = list(reversed(self.tx_transformations)) def set_rx_encoding(self, encoding, errors='replace'): """set encoding for received data""" self.input_encoding = encoding self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors) def set_tx_encoding(self, encoding, errors='replace'): """set encoding for transmitted data""" self.output_encoding = encoding self.tx_encoder = codecs.getincrementalencoder(encoding)(errors) def dump_port_settings(self): """Write current settings to sys.stderr""" sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format( p=self.serial)) sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format( ('active' if self.serial.rts else 'inactive'), ('active' if self.serial.dtr else 'inactive'), ('active' if self.serial.break_condition else 'inactive'))) try: sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format( ('active' if self.serial.cts else 'inactive'), ('active' if self.serial.dsr else 'inactive'), ('active' if self.serial.ri else 'inactive'), ('active' if self.serial.cd else 'inactive'))) except serial.SerialException: # on RFC 2217 ports, it can happen if no modem state notification was # yet received. ignore this error. pass sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive')) sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive')) sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding)) sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding)) sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper())) sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters))) def reader(self): """loop and copy serial->console""" try: while self.alive and self._reader_alive: # read all that is there or wait for one byte data = self.serial.read(self.serial.in_waiting or 1) if data: if self.raw: self.console.write_bytes(data) else: text = self.rx_decoder.decode(data) for transformation in self.rx_transformations: text = transformation.rx(text) self.console.write(text) except serial.SerialException: self.alive = False self.console.cancel() raise # XXX handle instead of re-raise? def writer(self): """\ Loop and copy console->serial until self.exit_character character is found. When self.menu_character is found, interpret the next key locally. """ menu_active = False try: while self.alive: try: c = self.console.getkey() except KeyboardInterrupt: c = '\x03' if not self.alive: break if menu_active: self.handle_menu_key(c) menu_active = False elif c == self.menu_character: menu_active = True # next char will be for menu elif c == self.exit_character: self.stop() # exit app break else: #~ if self.raw: text = c for transformation in self.tx_transformations: text = transformation.tx(text) self.serial.write(self.tx_encoder.encode(text)) if self.echo: echo_text = c for transformation in self.tx_transformations: echo_text = transformation.echo(echo_text) self.console.write(echo_text) except: self.alive = False raise def handle_menu_key(self, c): """Implement a simple menu / settings""" if c == self.menu_character or c == self.exit_character: # Menu/exit character again -> send itself self.serial.write(self.tx_encoder.encode(c)) if self.echo: self.console.write(c) elif c == '\x15': # CTRL+U -> upload file self.upload_file() elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help sys.stderr.write(self.get_help_text()) elif c == '\x12': # CTRL+R -> Toggle RTS self.serial.rts = not self.serial.rts sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive')) elif c == '\x04': # CTRL+D -> Toggle DTR self.serial.dtr = not self.serial.dtr sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive')) elif c == '\x02': # CTRL+B -> toggle BREAK condition self.serial.break_condition = not self.serial.break_condition sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive')) elif c == '\x05': # CTRL+E -> toggle local echo self.echo = not self.echo sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive')) elif c == '\x06': # CTRL+F -> edit filters self.change_filter() elif c == '\x0c': # CTRL+L -> EOL mode modes = list(EOL_TRANSFORMATIONS) # keys eol = modes.index(self.eol) + 1 if eol >= len(modes): eol = 0 self.eol = modes[eol] sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper())) self.update_transformations() elif c == '\x01': # CTRL+A -> set encoding self.change_encoding() elif c == '\x09': # CTRL+I -> info self.dump_port_settings() #~ elif c == '\x01': # CTRL+A -> cycle escape mode #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode elif c in 'pP': # P -> change port self.change_port() elif c in 'zZ': # S -> suspend / open port temporarily self.suspend_port() elif c in 'bB': # B -> change baudrate self.change_baudrate() elif c == '8': # 8 -> change to 8 bits self.serial.bytesize = serial.EIGHTBITS self.dump_port_settings() elif c == '7': # 7 -> change to 8 bits self.serial.bytesize = serial.SEVENBITS self.dump_port_settings() elif c in 'eE': # E -> change to even parity self.serial.parity = serial.PARITY_EVEN self.dump_port_settings() elif c in 'oO': # O -> change to odd parity self.serial.parity = serial.PARITY_ODD self.dump_port_settings() elif c in 'mM': # M -> change to mark parity self.serial.parity = serial.PARITY_MARK self.dump_port_settings() elif c in 'sS': # S -> change to space parity self.serial.parity = serial.PARITY_SPACE self.dump_port_settings() elif c in 'nN': # N -> change to no parity self.serial.parity = serial.PARITY_NONE self.dump_port_settings() elif c == '1': # 1 -> change to 1 stop bits self.serial.stopbits = serial.STOPBITS_ONE self.dump_port_settings() elif c == '2': # 2 -> change to 2 stop bits self.serial.stopbits = serial.STOPBITS_TWO self.dump_port_settings() elif c == '3': # 3 -> change to 1.5 stop bits self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE self.dump_port_settings() elif c in 'xX': # X -> change software flow control self.serial.xonxoff = (c == 'X') self.dump_port_settings() elif c in 'rR': # R -> change hardware flow control self.serial.rtscts = (c == 'R') self.dump_port_settings() elif c in 'qQ': self.stop() # Q -> exit app else: sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c))) def upload_file(self): """Ask user for filenname and send its contents""" sys.stderr.write('\n--- File to upload: ') sys.stderr.flush() with self.console: filename = sys.stdin.readline().rstrip('\r\n') if filename: try: with open(filename, 'rb') as f: sys.stderr.write('--- Sending file {} ---\n'.format(filename)) while True: block = f.read(1024) if not block: break self.serial.write(block) # Wait for output buffer to drain. self.serial.flush() sys.stderr.write('.') # Progress indicator. sys.stderr.write('\n--- File {} sent ---\n'.format(filename)) except IOError as e: sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e)) def change_filter(self): """change the i/o transformations""" sys.stderr.write('\n--- Available Filters:\n') sys.stderr.write('\n'.join( '--- {:<10} = {.__doc__}'.format(k, v) for k, v in sorted(TRANSFORMATIONS.items()))) sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters))) with self.console: new_filters = sys.stdin.readline().lower().split() if new_filters: for f in new_filters: if f not in TRANSFORMATIONS: sys.stderr.write('--- unknown filter: {!r}\n'.format(f)) break else: self.filters = new_filters self.update_transformations() sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters))) def change_encoding(self): """change encoding on the serial port""" sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding)) with self.console: new_encoding = sys.stdin.readline().strip() if new_encoding: try: codecs.lookup(new_encoding) except LookupError: sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding)) else: self.set_rx_encoding(new_encoding) self.set_tx_encoding(new_encoding) sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding)) sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding)) def change_baudrate(self): """change the baudrate""" sys.stderr.write('\n--- Baudrate: ') sys.stderr.flush() with self.console: backup = self.serial.baudrate try: self.serial.baudrate = int(sys.stdin.readline().strip()) except ValueError as e: sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e)) self.serial.baudrate = backup else: self.dump_port_settings() def change_port(self): """Have a conversation with the user to change the serial port""" with self.console: try: port = ask_for_port() except KeyboardInterrupt: port = None if port and port != self.serial.port: # reader thread needs to be shut down self._stop_reader() # save settings settings = self.serial.getSettingsDict() try: new_serial = serial.serial_for_url(port, do_not_open=True) # restore settings and open new_serial.applySettingsDict(settings) new_serial.rts = self.serial.rts new_serial.dtr = self.serial.dtr new_serial.open() new_serial.break_condition = self.serial.break_condition except Exception as e: sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e)) new_serial.close() else: self.serial.close() self.serial = new_serial sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port)) # and restart the reader thread self._start_reader() def suspend_port(self): """\ open port temporarily, allow reconnect, exit and port change to get out of the loop """ # reader thread needs to be shut down self._stop_reader() self.serial.close() sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port)) do_change_port = False while not self.serial.is_open: sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format( exit=key_description(self.exit_character))) k = self.console.getkey() if k == self.exit_character: self.stop() # exit app break elif k in 'pP': do_change_port = True break try: self.serial.open() except Exception as e: sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e)) if do_change_port: self.change_port() else: # and restart the reader thread self._start_reader() sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port)) def get_help_text(self): """return the help text""" # help text, starts with blank line! return """ --- pySerial ({version}) - miniterm - help --- --- {exit:8} Exit program (alias {menu} Q) --- {menu:8} Menu escape key, followed by: --- Menu keys: --- {menu:7} Send the menu character itself to remote --- {exit:7} Send the exit character itself to remote --- {info:7} Show info --- {upload:7} Upload file (prompt will be shown) --- {repr:7} encoding --- {filter:7} edit filters --- Toggles: --- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK --- {echo:7} echo {eol:7} EOL --- --- Port settings ({menu} 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 """.format(version=getattr(serial, 'VERSION', 'unknown version'), exit=key_description(self.exit_character), menu=key_description(self.menu_character), rts=key_description('\x12'), dtr=key_description('\x04'), brk=key_description('\x02'), echo=key_description('\x05'), info=key_description('\x09'), upload=key_description('\x15'), repr=key_description('\x01'), filter=key_description('\x06'), eol=key_description('\x0c')) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # default args can be used to override when calling main() from an other script # e.g to create a miniterm-my-device.py def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None): """Command line tool, entry point""" import argparse parser = argparse.ArgumentParser( description='Miniterm - A simple terminal program for the serial port.') parser.add_argument( 'port', nargs='?', help='serial port name ("-" to show port list)', default=default_port) parser.add_argument( 'baudrate', nargs='?', type=int, help='set baud rate, default: %(default)s', default=default_baudrate) group = parser.add_argument_group('port settings') 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( '--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=default_rts) group.add_argument( '--dtr', type=int, help='set initial DTR line state (possible values: 0, 1)', default=default_dtr) group.add_argument( '--non-exclusive', dest='exclusive', action='store_false', help='disable locking for native ports', default=True) group.add_argument( '--ask', action='store_true', help='ask again for port when open fails', default=False) group = parser.add_argument_group('data handling') group.add_argument( '-e', '--echo', action='store_true', help='enable local echo (default off)', default=False) group.add_argument( '--encoding', dest='serial_port_encoding', metavar='CODEC', help='set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s', default='UTF-8') group.add_argument( '-f', '--filter', action='append', metavar='NAME', help='add text transformation', default=[]) group.add_argument( '--eol', choices=['CR', 'LF', 'CRLF'], type=lambda c: c.upper(), help='end of line mode', default='CRLF') group.add_argument( '--raw', action='store_true', help='Do no apply any encodings/transformations', default=False) group = parser.add_argument_group('hotkeys') group.add_argument( '--exit-char', type=int, metavar='NUM', help='Unicode of special character that is used to exit the application, default: %(default)s', default=0x1d) # GS/CTRL+] group.add_argument( '--menu-char', type=int, metavar='NUM', help='Unicode code of special character that is used to control miniterm (menu), default: %(default)s', default=0x14) # Menu: CTRL+T group = parser.add_argument_group('diagnostics') group.add_argument( '-q', '--quiet', action='store_true', help='suppress non-error messages', default=False) group.add_argument( '--develop', action='store_true', help='show Python traceback on error', default=False) args = parser.parse_args() if args.menu_char == args.exit_char: parser.error('--exit-char can not be the same as --menu-char') if args.filter: if 'help' in args.filter: sys.stderr.write('Available filters:\n') sys.stderr.write('\n'.join( '{:<10} = {.__doc__}'.format(k, v) for k, v in sorted(TRANSFORMATIONS.items()))) sys.stderr.write('\n') sys.exit(1) filters = args.filter else: filters = ['default'] while True: # no port given on command line -> ask user now if args.port is None or args.port == '-': try: args.port = ask_for_port() except KeyboardInterrupt: sys.stderr.write('\n') parser.error('user aborted and port is not given') else: if not args.port: parser.error('port is not given') try: serial_instance = serial.serial_for_url( args.port, args.baudrate, parity=args.parity, rtscts=args.rtscts, xonxoff=args.xonxoff, do_not_open=True) if not hasattr(serial_instance, 'cancel_read'): # enable timeout for alive flag polling if cancel_read is not available serial_instance.timeout = 1 if args.dtr is not None: if not args.quiet: sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive')) serial_instance.dtr = args.dtr if args.rts is not None: if not args.quiet: sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive')) serial_instance.rts = args.rts if isinstance(serial_instance, serial.Serial): serial_instance.exclusive = args.exclusive serial_instance.open() except serial.SerialException as e: sys.stderr.write('could not open port {!r}: {}\n'.format(args.port, e)) if args.develop: raise if not args.ask: sys.exit(1) else: args.port = '-' else: break miniterm = Miniterm( serial_instance, echo=args.echo, eol=args.eol.lower(), filters=filters) miniterm.exit_character = unichr(args.exit_char) miniterm.menu_character = unichr(args.menu_char) miniterm.raw = args.raw miniterm.set_rx_encoding(args.serial_port_encoding) miniterm.set_tx_encoding(args.serial_port_encoding) if not args.quiet: sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format( p=miniterm.serial)) sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format( key_description(miniterm.exit_character), key_description(miniterm.menu_character), key_description(miniterm.menu_character), key_description('\x08'))) miniterm.start() try: miniterm.join(True) except KeyboardInterrupt: pass if not args.quiet: sys.stderr.write('\n--- exit ---\n') miniterm.join() miniterm.close() # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if __name__ == '__main__': main() pyserial-3.5/serial/__init__.py0000644000175000017500000000621413756627410017061 0ustar chrischris00000000000000#!/usr/bin/env python # # This is a wrapper module for different platform implementations # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2020 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import import sys import importlib from serial.serialutil import * #~ SerialBase, SerialException, to_bytes, iterbytes __version__ = '3.5' VERSION = __version__ # pylint: disable=wrong-import-position if sys.platform == 'cli': from serial.serialcli import Serial else: import os # chose an implementation, depending on os if os.name == 'nt': # sys.platform == 'win32': from serial.serialwin32 import Serial elif os.name == 'posix': from serial.serialposix import Serial, PosixPollSerial, VTIMESerial # noqa elif os.name == 'java': from serial.serialjava import Serial else: raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) protocol_handler_packages = [ 'serial.urlhandler', ] def serial_for_url(url, *args, **kwargs): """\ Get an instance of the Serial class, depending on port/url. The port is not opened when the keyword parameter 'do_not_open' is true, by default it is. All other parameters are directly passed to the __init__ method when the port is instantiated. The list of package names that is searched for protocol handlers is kept in ``protocol_handler_packages``. e.g. we want to support a URL ``foobar://``. A module ``my_handlers.protocol_foobar`` is provided by the user. Then ``protocol_handler_packages.append("my_handlers")`` would extend the search path so that ``serial_for_url("foobar://"))`` would work. """ # check and remove extra parameter to not confuse the Serial class do_open = not kwargs.pop('do_not_open', False) # the default is to use the native implementation klass = Serial try: url_lowercase = url.lower() except AttributeError: # it's not a string, use default pass else: # if it is an URL, try to import the handler module from the list of possible packages if '://' in url_lowercase: protocol = url_lowercase.split('://', 1)[0] module_name = '.protocol_{}'.format(protocol) for package_name in protocol_handler_packages: try: importlib.import_module(package_name) handler_module = importlib.import_module(module_name, package_name) except ImportError: continue else: if hasattr(handler_module, 'serial_class_for_url'): url, klass = handler_module.serial_class_for_url(url) else: klass = handler_module.Serial break else: raise ValueError('invalid URL, protocol {!r} not known'.format(protocol)) # instantiate and open when desired instance = klass(None, *args, **kwargs) instance.port = url if do_open: instance.open() return instance pyserial-3.5/serial/serialposix.py0000664000175000017500000010446713756630436017701 0ustar chrischris00000000000000#!/usr/bin/env python # # backend for serial IO for POSIX compatible systems, like Linux, OSX # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2020 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause # # parts based on code from Grant B. Edwards : # ftp://ftp.visi.com/users/grante/python/PosixSerial.py # # references: http://www.easysw.com/~mike/serial/serial.html # Collection of port names (was previously used by number_to_device which was # removed. # - Linux /dev/ttyS%d (confirmed) # - cygwin/win32 /dev/com%d (confirmed) # - openbsd (OpenBSD) /dev/cua%02d # - bsd*, freebsd* /dev/cuad%d # - darwin (OS X) /dev/cuad%d # - netbsd /dev/dty%02d (NetBSD 1.6 testing by Erk) # - irix (IRIX) /dev/ttyf%d (partially tested) names depending on flow control # - hp (HP-UX) /dev/tty%dp0 (not tested) # - sunos (Solaris/SunOS) /dev/tty%c (letters, 'a'..'z') (confirmed) # - aix (AIX) /dev/tty%d from __future__ import absolute_import # pylint: disable=abstract-method import errno import fcntl import os import select import struct import sys import termios import serial from serial.serialutil import SerialBase, SerialException, to_bytes, \ PortNotOpenError, SerialTimeoutException, Timeout class PlatformSpecificBase(object): BAUDRATE_CONSTANTS = {} def _set_special_baudrate(self, baudrate): raise NotImplementedError('non-standard baudrates are not supported on this platform') def _set_rs485_mode(self, rs485_settings): raise NotImplementedError('RS485 not supported on this platform') def set_low_latency_mode(self, low_latency_settings): raise NotImplementedError('Low latency not supported on this platform') def _update_break_state(self): """\ Set break: Controls TXD. When active, no transmitting is possible. """ if self._break_state: fcntl.ioctl(self.fd, TIOCSBRK) else: fcntl.ioctl(self.fd, TIOCCBRK) # some systems support an extra flag to enable the two in POSIX unsupported # paritiy settings for MARK and SPACE CMSPAR = 0 # default, for unsupported platforms, override below # try to detect the OS so that a device can be selected... # this code block should supply a device() and set_special_baudrate() function # for the platform plat = sys.platform.lower() if plat[:5] == 'linux': # Linux (confirmed) # noqa import array # extra termios flags CMSPAR = 0o10000000000 # Use "stick" (mark/space) parity # baudrate ioctls TCGETS2 = 0x802C542A TCSETS2 = 0x402C542B BOTHER = 0o010000 # RS485 ioctls TIOCGRS485 = 0x542E TIOCSRS485 = 0x542F SER_RS485_ENABLED = 0b00000001 SER_RS485_RTS_ON_SEND = 0b00000010 SER_RS485_RTS_AFTER_SEND = 0b00000100 SER_RS485_RX_DURING_TX = 0b00010000 class PlatformSpecific(PlatformSpecificBase): BAUDRATE_CONSTANTS = { 0: 0o000000, # hang up 50: 0o000001, 75: 0o000002, 110: 0o000003, 134: 0o000004, 150: 0o000005, 200: 0o000006, 300: 0o000007, 600: 0o000010, 1200: 0o000011, 1800: 0o000012, 2400: 0o000013, 4800: 0o000014, 9600: 0o000015, 19200: 0o000016, 38400: 0o000017, 57600: 0o010001, 115200: 0o010002, 230400: 0o010003, 460800: 0o010004, 500000: 0o010005, 576000: 0o010006, 921600: 0o010007, 1000000: 0o010010, 1152000: 0o010011, 1500000: 0o010012, 2000000: 0o010013, 2500000: 0o010014, 3000000: 0o010015, 3500000: 0o010016, 4000000: 0o010017 } def set_low_latency_mode(self, low_latency_settings): buf = array.array('i', [0] * 32) try: # get serial_struct fcntl.ioctl(self.fd, termios.TIOCGSERIAL, buf) # set or unset ASYNC_LOW_LATENCY flag if low_latency_settings: buf[4] |= 0x2000 else: buf[4] &= ~0x2000 # set serial_struct fcntl.ioctl(self.fd, termios.TIOCSSERIAL, buf) except IOError as e: raise ValueError('Failed to update ASYNC_LOW_LATENCY flag to {}: {}'.format(low_latency_settings, e)) def _set_special_baudrate(self, baudrate): # right size is 44 on x86_64, allow for some growth buf = array.array('i', [0] * 64) try: # get serial_struct fcntl.ioctl(self.fd, TCGETS2, buf) # set custom speed buf[2] &= ~termios.CBAUD buf[2] |= BOTHER buf[9] = buf[10] = baudrate # set serial_struct fcntl.ioctl(self.fd, TCSETS2, buf) except IOError as e: raise ValueError('Failed to set custom baud rate ({}): {}'.format(baudrate, e)) def _set_rs485_mode(self, rs485_settings): buf = array.array('i', [0] * 8) # flags, delaytx, delayrx, padding try: fcntl.ioctl(self.fd, TIOCGRS485, buf) buf[0] |= SER_RS485_ENABLED if rs485_settings is not None: if rs485_settings.loopback: buf[0] |= SER_RS485_RX_DURING_TX else: buf[0] &= ~SER_RS485_RX_DURING_TX if rs485_settings.rts_level_for_tx: buf[0] |= SER_RS485_RTS_ON_SEND else: buf[0] &= ~SER_RS485_RTS_ON_SEND if rs485_settings.rts_level_for_rx: buf[0] |= SER_RS485_RTS_AFTER_SEND else: buf[0] &= ~SER_RS485_RTS_AFTER_SEND if rs485_settings.delay_before_tx is not None: buf[1] = int(rs485_settings.delay_before_tx * 1000) if rs485_settings.delay_before_rx is not None: buf[2] = int(rs485_settings.delay_before_rx * 1000) else: buf[0] = 0 # clear SER_RS485_ENABLED fcntl.ioctl(self.fd, TIOCSRS485, buf) except IOError as e: raise ValueError('Failed to set RS485 mode: {}'.format(e)) elif plat == 'cygwin': # cygwin/win32 (confirmed) class PlatformSpecific(PlatformSpecificBase): BAUDRATE_CONSTANTS = { 128000: 0x01003, 256000: 0x01005, 500000: 0x01007, 576000: 0x01008, 921600: 0x01009, 1000000: 0x0100a, 1152000: 0x0100b, 1500000: 0x0100c, 2000000: 0x0100d, 2500000: 0x0100e, 3000000: 0x0100f } elif plat[:6] == 'darwin': # OS X import array IOSSIOSPEED = 0x80045402 # _IOW('T', 2, speed_t) class PlatformSpecific(PlatformSpecificBase): osx_version = os.uname()[2].split('.') TIOCSBRK = 0x2000747B # _IO('t', 123) TIOCCBRK = 0x2000747A # _IO('t', 122) # Tiger or above can support arbitrary serial speeds if int(osx_version[0]) >= 8: def _set_special_baudrate(self, baudrate): # use IOKit-specific call to set up high speeds buf = array.array('i', [baudrate]) fcntl.ioctl(self.fd, IOSSIOSPEED, buf, 1) def _update_break_state(self): """\ Set break: Controls TXD. When active, no transmitting is possible. """ if self._break_state: fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK) else: fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK) elif plat[:3] == 'bsd' or \ plat[:7] == 'freebsd' or \ plat[:6] == 'netbsd' or \ plat[:7] == 'openbsd': class ReturnBaudrate(object): def __getitem__(self, key): return key class PlatformSpecific(PlatformSpecificBase): # Only tested on FreeBSD: # The baud rate may be passed in as # a literal value. BAUDRATE_CONSTANTS = ReturnBaudrate() TIOCSBRK = 0x2000747B # _IO('t', 123) TIOCCBRK = 0x2000747A # _IO('t', 122) def _update_break_state(self): """\ Set break: Controls TXD. When active, no transmitting is possible. """ if self._break_state: fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK) else: fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK) else: class PlatformSpecific(PlatformSpecificBase): pass # load some constants for later use. # try to use values from termios, use defaults from linux otherwise TIOCMGET = getattr(termios, 'TIOCMGET', 0x5415) TIOCMBIS = getattr(termios, 'TIOCMBIS', 0x5416) TIOCMBIC = getattr(termios, 'TIOCMBIC', 0x5417) TIOCMSET = getattr(termios, 'TIOCMSET', 0x5418) # TIOCM_LE = getattr(termios, 'TIOCM_LE', 0x001) TIOCM_DTR = getattr(termios, 'TIOCM_DTR', 0x002) TIOCM_RTS = getattr(termios, 'TIOCM_RTS', 0x004) # TIOCM_ST = getattr(termios, 'TIOCM_ST', 0x008) # TIOCM_SR = getattr(termios, 'TIOCM_SR', 0x010) TIOCM_CTS = getattr(termios, 'TIOCM_CTS', 0x020) TIOCM_CAR = getattr(termios, 'TIOCM_CAR', 0x040) TIOCM_RNG = getattr(termios, 'TIOCM_RNG', 0x080) TIOCM_DSR = getattr(termios, 'TIOCM_DSR', 0x100) TIOCM_CD = getattr(termios, 'TIOCM_CD', TIOCM_CAR) TIOCM_RI = getattr(termios, 'TIOCM_RI', TIOCM_RNG) # TIOCM_OUT1 = getattr(termios, 'TIOCM_OUT1', 0x2000) # TIOCM_OUT2 = getattr(termios, 'TIOCM_OUT2', 0x4000) if hasattr(termios, 'TIOCINQ'): TIOCINQ = termios.TIOCINQ else: TIOCINQ = getattr(termios, 'FIONREAD', 0x541B) TIOCOUTQ = getattr(termios, 'TIOCOUTQ', 0x5411) TIOCM_zero_str = struct.pack('I', 0) TIOCM_RTS_str = struct.pack('I', TIOCM_RTS) TIOCM_DTR_str = struct.pack('I', TIOCM_DTR) TIOCSBRK = getattr(termios, 'TIOCSBRK', 0x5427) TIOCCBRK = getattr(termios, 'TIOCCBRK', 0x5428) class Serial(SerialBase, PlatformSpecific): """\ Serial port class POSIX implementation. Serial port configuration is done with termios and fcntl. Runs on Linux and many other Un*x like systems. """ def open(self): """\ Open port with current settings. This may throw a SerialException if the port cannot be opened.""" if self._port is None: raise SerialException("Port must be configured before it can be used.") if self.is_open: raise SerialException("Port is already open.") self.fd = None # open try: self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK) except OSError as msg: self.fd = None raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg)) #~ fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # set blocking self.pipe_abort_read_r, self.pipe_abort_read_w = None, None self.pipe_abort_write_r, self.pipe_abort_write_w = None, None try: self._reconfigure_port(force_update=True) try: if not self._dsrdtr: self._update_dtr_state() if not self._rtscts: self._update_rts_state() except IOError as e: # ignore Invalid argument and Inappropriate ioctl if e.errno not in (errno.EINVAL, errno.ENOTTY): raise self._reset_input_buffer() self.pipe_abort_read_r, self.pipe_abort_read_w = os.pipe() self.pipe_abort_write_r, self.pipe_abort_write_w = os.pipe() fcntl.fcntl(self.pipe_abort_read_r, fcntl.F_SETFL, os.O_NONBLOCK) fcntl.fcntl(self.pipe_abort_write_r, fcntl.F_SETFL, os.O_NONBLOCK) except BaseException: try: os.close(self.fd) except Exception: # ignore any exception when closing the port # also to keep original exception that happened when setting up pass self.fd = None if self.pipe_abort_read_w is not None: os.close(self.pipe_abort_read_w) self.pipe_abort_read_w = None if self.pipe_abort_read_r is not None: os.close(self.pipe_abort_read_r) self.pipe_abort_read_r = None if self.pipe_abort_write_w is not None: os.close(self.pipe_abort_write_w) self.pipe_abort_write_w = None if self.pipe_abort_write_r is not None: os.close(self.pipe_abort_write_r) self.pipe_abort_write_r = None raise self.is_open = True def _reconfigure_port(self, force_update=False): """Set communication parameters on opened port.""" if self.fd is None: raise SerialException("Can only operate on a valid file descriptor") # if exclusive lock is requested, create it before we modify anything else if self._exclusive is not None: if self._exclusive: try: fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError as msg: raise SerialException(msg.errno, "Could not exclusively lock port {}: {}".format(self._port, msg)) else: fcntl.flock(self.fd, fcntl.LOCK_UN) custom_baud = None vmin = vtime = 0 # timeout is done via select if self._inter_byte_timeout is not None: vmin = 1 vtime = int(self._inter_byte_timeout * 10) try: orig_attr = termios.tcgetattr(self.fd) iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here raise SerialException("Could not configure port: {}".format(msg)) # set up raw mode / no echo / binary cflag |= (termios.CLOCAL | termios.CREAD) lflag &= ~(termios.ICANON | termios.ECHO | termios.ECHOE | termios.ECHOK | termios.ECHONL | termios.ISIG | termios.IEXTEN) # |termios.ECHOPRT for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk if hasattr(termios, flag): lflag &= ~getattr(termios, flag) oflag &= ~(termios.OPOST | termios.ONLCR | termios.OCRNL) iflag &= ~(termios.INLCR | termios.IGNCR | termios.ICRNL | termios.IGNBRK) if hasattr(termios, 'IUCLC'): iflag &= ~termios.IUCLC if hasattr(termios, 'PARMRK'): iflag &= ~termios.PARMRK # setup baud rate try: ispeed = ospeed = getattr(termios, 'B{}'.format(self._baudrate)) except AttributeError: try: ispeed = ospeed = self.BAUDRATE_CONSTANTS[self._baudrate] except KeyError: #~ raise ValueError('Invalid baud rate: %r' % self._baudrate) # See if BOTHER is defined for this platform; if it is, use # this for a speed not defined in the baudrate constants list. try: ispeed = ospeed = BOTHER except NameError: # may need custom baud rate, it isn't in our list. ispeed = ospeed = getattr(termios, 'B38400') try: custom_baud = int(self._baudrate) # store for later except ValueError: raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate)) else: if custom_baud < 0: raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate)) # setup char len cflag &= ~termios.CSIZE if self._bytesize == 8: cflag |= termios.CS8 elif self._bytesize == 7: cflag |= termios.CS7 elif self._bytesize == 6: cflag |= termios.CS6 elif self._bytesize == 5: cflag |= termios.CS5 else: raise ValueError('Invalid char len: {!r}'.format(self._bytesize)) # setup stop bits if self._stopbits == serial.STOPBITS_ONE: cflag &= ~(termios.CSTOPB) elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: cflag |= (termios.CSTOPB) # XXX same as TWO.. there is no POSIX support for 1.5 elif self._stopbits == serial.STOPBITS_TWO: cflag |= (termios.CSTOPB) else: raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits)) # setup parity iflag &= ~(termios.INPCK | termios.ISTRIP) if self._parity == serial.PARITY_NONE: cflag &= ~(termios.PARENB | termios.PARODD | CMSPAR) elif self._parity == serial.PARITY_EVEN: cflag &= ~(termios.PARODD | CMSPAR) cflag |= (termios.PARENB) elif self._parity == serial.PARITY_ODD: cflag &= ~CMSPAR cflag |= (termios.PARENB | termios.PARODD) elif self._parity == serial.PARITY_MARK and CMSPAR: cflag |= (termios.PARENB | CMSPAR | termios.PARODD) elif self._parity == serial.PARITY_SPACE and CMSPAR: cflag |= (termios.PARENB | CMSPAR) cflag &= ~(termios.PARODD) else: raise ValueError('Invalid parity: {!r}'.format(self._parity)) # setup flow control # xonxoff if hasattr(termios, 'IXANY'): if self._xonxoff: iflag |= (termios.IXON | termios.IXOFF) # |termios.IXANY) else: iflag &= ~(termios.IXON | termios.IXOFF | termios.IXANY) else: if self._xonxoff: iflag |= (termios.IXON | termios.IXOFF) else: iflag &= ~(termios.IXON | termios.IXOFF) # rtscts if hasattr(termios, 'CRTSCTS'): if self._rtscts: cflag |= (termios.CRTSCTS) else: cflag &= ~(termios.CRTSCTS) elif hasattr(termios, 'CNEW_RTSCTS'): # try it with alternate constant name if self._rtscts: cflag |= (termios.CNEW_RTSCTS) else: cflag &= ~(termios.CNEW_RTSCTS) # XXX should there be a warning if setting up rtscts (and xonxoff etc) fails?? # buffer # vmin "minimal number of characters to be read. 0 for non blocking" if vmin < 0 or vmin > 255: raise ValueError('Invalid vmin: {!r}'.format(vmin)) cc[termios.VMIN] = vmin # vtime if vtime < 0 or vtime > 255: raise ValueError('Invalid vtime: {!r}'.format(vtime)) cc[termios.VTIME] = vtime # activate settings if force_update or [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr: termios.tcsetattr( self.fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) # apply custom baud rate, if any if custom_baud is not None: self._set_special_baudrate(custom_baud) if self._rs485_mode is not None: self._set_rs485_mode(self._rs485_mode) def close(self): """Close port""" if self.is_open: if self.fd is not None: os.close(self.fd) self.fd = None os.close(self.pipe_abort_read_w) os.close(self.pipe_abort_read_r) os.close(self.pipe_abort_write_w) os.close(self.pipe_abort_write_r) self.pipe_abort_read_r, self.pipe_abort_read_w = None, None self.pipe_abort_write_r, self.pipe_abort_write_w = None, None self.is_open = False # - - - - - - - - - - - - - - - - - - - - - - - - @property def in_waiting(self): """Return the number of bytes currently in the input buffer.""" #~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str) s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str) return struct.unpack('I', s)[0] # select based implementation, proved to work on many systems def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.is_open: raise PortNotOpenError() read = bytearray() timeout = Timeout(self._timeout) while len(read) < size: try: ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left()) if self.pipe_abort_read_r in ready: os.read(self.pipe_abort_read_r, 1000) break # If select was used with a timeout, and the timeout occurs, it # returns with empty lists -> thus abort read operation. # For timeout == 0 (non-blocking operation) also abort when # there is nothing to read. if not ready: break # timeout buf = os.read(self.fd, size - len(read)) except OSError as e: # this is for Python 3.x where select.error is a subclass of # OSError ignore BlockingIOErrors and EINTR. other errors are shown # https://www.python.org/dev/peps/pep-0475. if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('read failed: {}'.format(e)) except select.error as e: # this is for Python 2.x # ignore BlockingIOErrors and EINTR. all errors are shown # see also http://www.python.org/dev/peps/pep-3151/#select if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('read failed: {}'.format(e)) else: # read should always return some data as select reported it was # ready to read when we get to this point. if not buf: # Disconnected devices, at least on Linux, show the # behavior that they are always ready to read immediately # but reading returns nothing. raise SerialException( 'device reports readiness to read but returned no data ' '(device disconnected or multiple access on port?)') read.extend(buf) if timeout.expired(): break return bytes(read) def cancel_read(self): if self.is_open: os.write(self.pipe_abort_read_w, b"x") def cancel_write(self): if self.is_open: os.write(self.pipe_abort_write_w, b"x") def write(self, data): """Output the given byte string over the serial port.""" if not self.is_open: raise PortNotOpenError() d = to_bytes(data) tx_len = length = len(d) timeout = Timeout(self._write_timeout) while tx_len > 0: try: n = os.write(self.fd, d) if timeout.is_non_blocking: # Zero timeout indicates non-blocking - simply return the # number of bytes of data actually written return n elif not timeout.is_infinite: # when timeout is set, use select to wait for being ready # with the time left as timeout if timeout.expired(): raise SerialTimeoutException('Write timeout') abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], timeout.time_left()) if abort: os.read(self.pipe_abort_write_r, 1000) break if not ready: raise SerialTimeoutException('Write timeout') else: assert timeout.time_left() is None # wait for write operation abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], None) if abort: os.read(self.pipe_abort_write_r, 1) break if not ready: raise SerialException('write failed (select)') d = d[n:] tx_len -= n except SerialException: raise except OSError as e: # this is for Python 3.x where select.error is a subclass of # OSError ignore BlockingIOErrors and EINTR. other errors are shown # https://www.python.org/dev/peps/pep-0475. if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('write failed: {}'.format(e)) except select.error as e: # this is for Python 2.x # ignore BlockingIOErrors and EINTR. all errors are shown # see also http://www.python.org/dev/peps/pep-3151/#select if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('write failed: {}'.format(e)) if not timeout.is_non_blocking and timeout.expired(): raise SerialTimeoutException('Write timeout') return length - len(d) def flush(self): """\ Flush of file like objects. In this case, wait until all data is written. """ if not self.is_open: raise PortNotOpenError() termios.tcdrain(self.fd) def _reset_input_buffer(self): """Clear input buffer, discarding all that is in the buffer.""" termios.tcflush(self.fd, termios.TCIFLUSH) def reset_input_buffer(self): """Clear input buffer, discarding all that is in the buffer.""" if not self.is_open: raise PortNotOpenError() self._reset_input_buffer() def reset_output_buffer(self): """\ Clear output buffer, aborting the current output and discarding all that is in the buffer. """ if not self.is_open: raise PortNotOpenError() termios.tcflush(self.fd, termios.TCOFLUSH) def send_break(self, duration=0.25): """\ Send break condition. Timed, returns to idle state after given duration. """ if not self.is_open: raise PortNotOpenError() termios.tcsendbreak(self.fd, int(duration / 0.25)) def _update_rts_state(self): """Set terminal status line: Request To Send""" if self._rts_state: fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str) else: fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str) def _update_dtr_state(self): """Set terminal status line: Data Terminal Ready""" if self._dtr_state: fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str) else: fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str) @property def cts(self): """Read terminal status line: Clear To Send""" if not self.is_open: raise PortNotOpenError() s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) return struct.unpack('I', s)[0] & TIOCM_CTS != 0 @property def dsr(self): """Read terminal status line: Data Set Ready""" if not self.is_open: raise PortNotOpenError() s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) return struct.unpack('I', s)[0] & TIOCM_DSR != 0 @property def ri(self): """Read terminal status line: Ring Indicator""" if not self.is_open: raise PortNotOpenError() s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) return struct.unpack('I', s)[0] & TIOCM_RI != 0 @property def cd(self): """Read terminal status line: Carrier Detect""" if not self.is_open: raise PortNotOpenError() s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) return struct.unpack('I', s)[0] & TIOCM_CD != 0 # - - platform specific - - - - @property def out_waiting(self): """Return the number of bytes currently in the output buffer.""" #~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str) s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str) return struct.unpack('I', s)[0] def fileno(self): """\ For easier use of the serial port instance with select. WARNING: this function is not portable to different platforms! """ if not self.is_open: raise PortNotOpenError() return self.fd def set_input_flow_control(self, enable=True): """\ Manually control flow - when software flow control is enabled. This will send XON (true) or XOFF (false) to the other device. WARNING: this function is not portable to different platforms! """ if not self.is_open: raise PortNotOpenError() if enable: termios.tcflow(self.fd, termios.TCION) else: termios.tcflow(self.fd, termios.TCIOFF) def set_output_flow_control(self, enable=True): """\ Manually control flow of outgoing data - when hardware or software flow control is enabled. WARNING: this function is not portable to different platforms! """ if not self.is_open: raise PortNotOpenError() if enable: termios.tcflow(self.fd, termios.TCOON) else: termios.tcflow(self.fd, termios.TCOOFF) def nonblocking(self): """DEPRECATED - has no use""" import warnings warnings.warn("nonblocking() has no effect, already nonblocking", DeprecationWarning) class PosixPollSerial(Serial): """\ Poll based read implementation. Not all systems support poll properly. However this one has better handling of errors, such as a device disconnecting while it's in use (e.g. USB-serial unplugged). """ def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.is_open: raise PortNotOpenError() read = bytearray() timeout = Timeout(self._timeout) poll = select.poll() poll.register(self.fd, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL) poll.register(self.pipe_abort_read_r, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL) if size > 0: while len(read) < size: # print "\tread(): size",size, "have", len(read) #debug # wait until device becomes ready to read (or something fails) for fd, event in poll.poll(None if timeout.is_infinite else (timeout.time_left() * 1000)): if fd == self.pipe_abort_read_r: break if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL): raise SerialException('device reports error (poll)') # we don't care if it is select.POLLIN or timeout, that's # handled below if fd == self.pipe_abort_read_r: os.read(self.pipe_abort_read_r, 1000) break buf = os.read(self.fd, size - len(read)) read.extend(buf) if timeout.expired() \ or (self._inter_byte_timeout is not None and self._inter_byte_timeout > 0) and not buf: break # early abort on timeout return bytes(read) class VTIMESerial(Serial): """\ Implement timeout using vtime of tty device instead of using select. This means that no inter character timeout can be specified and that the error handling is degraded. Overall timeout is disabled when inter-character timeout is used. Note that this implementation does NOT support cancel_read(), it will just ignore that. """ def _reconfigure_port(self, force_update=True): """Set communication parameters on opened port.""" super(VTIMESerial, self)._reconfigure_port() fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # clear O_NONBLOCK if self._inter_byte_timeout is not None: vmin = 1 vtime = int(self._inter_byte_timeout * 10) elif self._timeout is None: vmin = 1 vtime = 0 else: vmin = 0 vtime = int(self._timeout * 10) try: orig_attr = termios.tcgetattr(self.fd) iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here raise serial.SerialException("Could not configure port: {}".format(msg)) if vtime < 0 or vtime > 255: raise ValueError('Invalid vtime: {!r}'.format(vtime)) cc[termios.VTIME] = vtime cc[termios.VMIN] = vmin termios.tcsetattr( self.fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.is_open: raise PortNotOpenError() read = bytearray() while len(read) < size: buf = os.read(self.fd, size - len(read)) if not buf: break read.extend(buf) return bytes(read) # hack to make hasattr return false cancel_read = property() pyserial-3.5/serial/rfc2217.py0000644000175000017500000016446413727523253016422 0ustar chrischris00000000000000#! python # # This module implements a RFC2217 compatible client. RF2217 descibes a # protocol to access serial ports over TCP/IP and allows setting the baud rate, # modem control lines etc. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause # TODO: # - setting control line -> answer is not checked (had problems with one of the # severs). consider implementing a compatibility mode flag to make check # conditional # - write timeout not implemented at all # ########################################################################### # observations and issues with servers # =========================================================================== # sredird V2.2.1 # - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz # - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding # [105 1] instead of the actual value. # - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger # numbers than 2**32? # - To get the signature [COM_PORT_OPTION 0] has to be sent. # - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done # =========================================================================== # telnetcpcd (untested) # - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz # - To get the signature [COM_PORT_OPTION] w/o data has to be sent. # =========================================================================== # ser2net # - does not negotiate BINARY or COM_PORT_OPTION for his side but at least # acknowledges that the client activates these options # - The configuration may be that the server prints a banner. As this client # implementation does a flushInput on connect, this banner is hidden from # the user application. # - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one # second. # - To get the signature [COM_PORT_OPTION 0] has to be sent. # - run a server: run ser2net daemon, in /etc/ser2net.conf: # 2000:telnet:0:/dev/ttyS0:9600 remctl banner # ########################################################################### # How to identify ports? pySerial might want to support other protocols in the # future, so lets use an URL scheme. # for RFC2217 compliant servers we will use this: # rfc2217://:[?option[&option...]] # # options: # - "logging" set log level print diagnostic messages (e.g. "logging=debug") # - "ign_set_control": do not look at the answers to SET_CONTROL # - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read. # Without this option it expects that the server sends notifications # automatically on change (which most servers do and is according to the # RFC). # the order of the options is not relevant from __future__ import absolute_import import logging import socket import struct import threading import time try: import urlparse except ImportError: import urllib.parse as urlparse try: import Queue except ImportError: import queue as Queue import serial from serial.serialutil import SerialBase, SerialException, to_bytes, \ iterbytes, PortNotOpenError, Timeout # port string is expected to be something like this: # rfc2217://host:port # host may be an IP or including domain, whatever. # port is 0...65535 # map log level names to constants. used in from_url() LOGGER_LEVELS = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, } # telnet protocol characters SE = b'\xf0' # Subnegotiation End NOP = b'\xf1' # No Operation DM = b'\xf2' # Data Mark BRK = b'\xf3' # Break IP = b'\xf4' # Interrupt process AO = b'\xf5' # Abort output AYT = b'\xf6' # Are You There EC = b'\xf7' # Erase Character EL = b'\xf8' # Erase Line GA = b'\xf9' # Go Ahead SB = b'\xfa' # Subnegotiation Begin WILL = b'\xfb' WONT = b'\xfc' DO = b'\xfd' DONT = b'\xfe' IAC = b'\xff' # Interpret As Command IAC_DOUBLED = b'\xff\xff' # selected telnet options BINARY = b'\x00' # 8-bit data path ECHO = b'\x01' # echo SGA = b'\x03' # suppress go ahead # RFC2217 COM_PORT_OPTION = b'\x2c' # Client to Access Server SET_BAUDRATE = b'\x01' SET_DATASIZE = b'\x02' SET_PARITY = b'\x03' SET_STOPSIZE = b'\x04' SET_CONTROL = b'\x05' NOTIFY_LINESTATE = b'\x06' NOTIFY_MODEMSTATE = b'\x07' FLOWCONTROL_SUSPEND = b'\x08' FLOWCONTROL_RESUME = b'\x09' SET_LINESTATE_MASK = b'\x0a' SET_MODEMSTATE_MASK = b'\x0b' PURGE_DATA = b'\x0c' SERVER_SET_BAUDRATE = b'\x65' SERVER_SET_DATASIZE = b'\x66' SERVER_SET_PARITY = b'\x67' SERVER_SET_STOPSIZE = b'\x68' SERVER_SET_CONTROL = b'\x69' SERVER_NOTIFY_LINESTATE = b'\x6a' SERVER_NOTIFY_MODEMSTATE = b'\x6b' SERVER_FLOWCONTROL_SUSPEND = b'\x6c' SERVER_FLOWCONTROL_RESUME = b'\x6d' SERVER_SET_LINESTATE_MASK = b'\x6e' SERVER_SET_MODEMSTATE_MASK = b'\x6f' SERVER_PURGE_DATA = b'\x70' RFC2217_ANSWER_MAP = { SET_BAUDRATE: SERVER_SET_BAUDRATE, SET_DATASIZE: SERVER_SET_DATASIZE, SET_PARITY: SERVER_SET_PARITY, SET_STOPSIZE: SERVER_SET_STOPSIZE, SET_CONTROL: SERVER_SET_CONTROL, NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE, NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE, FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND, FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME, SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK, SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK, PURGE_DATA: SERVER_PURGE_DATA, } SET_CONTROL_REQ_FLOW_SETTING = b'\x00' # Request Com Port Flow Control Setting (outbound/both) SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01' # Use No Flow Control (outbound/both) SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02' # Use XON/XOFF Flow Control (outbound/both) SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03' # Use HARDWARE Flow Control (outbound/both) SET_CONTROL_REQ_BREAK_STATE = b'\x04' # Request BREAK State SET_CONTROL_BREAK_ON = b'\x05' # Set BREAK State ON SET_CONTROL_BREAK_OFF = b'\x06' # Set BREAK State OFF SET_CONTROL_REQ_DTR = b'\x07' # Request DTR Signal State SET_CONTROL_DTR_ON = b'\x08' # Set DTR Signal State ON SET_CONTROL_DTR_OFF = b'\x09' # Set DTR Signal State OFF SET_CONTROL_REQ_RTS = b'\x0a' # Request RTS Signal State SET_CONTROL_RTS_ON = b'\x0b' # Set RTS Signal State ON SET_CONTROL_RTS_OFF = b'\x0c' # Set RTS Signal State OFF SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d' # Request Com Port Flow Control Setting (inbound) SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e' # Use No Flow Control (inbound) SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f' # Use XON/XOFF Flow Control (inbound) SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10' # Use HARDWARE Flow Control (inbound) SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11' # Use DCD Flow Control (outbound/both) SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12' # Use DTR Flow Control (inbound) SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13' # Use DSR Flow Control (outbound/both) LINESTATE_MASK_TIMEOUT = 128 # Time-out Error LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error LINESTATE_MASK_DATA_READY = 1 # Data Ready MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect) MODEMSTATE_MASK_RI = 64 # Ring Indicator MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send PURGE_RECEIVE_BUFFER = b'\x01' # Purge access server receive data buffer PURGE_TRANSMIT_BUFFER = b'\x02' # Purge access server transmit data buffer PURGE_BOTH_BUFFERS = b'\x03' # Purge both the access server receive data # buffer and the access server transmit data buffer RFC2217_PARITY_MAP = { serial.PARITY_NONE: 1, serial.PARITY_ODD: 2, serial.PARITY_EVEN: 3, serial.PARITY_MARK: 4, serial.PARITY_SPACE: 5, } RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items()) RFC2217_STOPBIT_MAP = { serial.STOPBITS_ONE: 1, serial.STOPBITS_ONE_POINT_FIVE: 3, serial.STOPBITS_TWO: 2, } RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items()) # Telnet filter states M_NORMAL = 0 M_IAC_SEEN = 1 M_NEGOTIATE = 2 # TelnetOption and TelnetSubnegotiation states REQUESTED = 'REQUESTED' ACTIVE = 'ACTIVE' INACTIVE = 'INACTIVE' REALLY_INACTIVE = 'REALLY_INACTIVE' class TelnetOption(object): """Manage a single telnet option, keeps track of DO/DONT WILL/WONT.""" def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None): """\ Initialize option. :param connection: connection used to transmit answers :param name: a readable name for debug outputs :param send_yes: what to send when option is to be enabled. :param send_no: what to send when option is to be disabled. :param ack_yes: what to expect when remote agrees on option. :param ack_no: what to expect when remote disagrees on option. :param initial_state: options initialized with REQUESTED are tried to be enabled on startup. use INACTIVE for all others. """ self.connection = connection self.name = name self.option = option self.send_yes = send_yes self.send_no = send_no self.ack_yes = ack_yes self.ack_no = ack_no self.state = initial_state self.active = False self.activation_callback = activation_callback def __repr__(self): """String for debug outputs""" return "{o.name}:{o.active}({o.state})".format(o=self) def process_incoming(self, command): """\ A DO/DONT/WILL/WONT was received for this option, update state and answer when needed. """ if command == self.ack_yes: if self.state is REQUESTED: self.state = ACTIVE self.active = True if self.activation_callback is not None: self.activation_callback() elif self.state is ACTIVE: pass elif self.state is INACTIVE: self.state = ACTIVE self.connection.telnet_send_option(self.send_yes, self.option) self.active = True if self.activation_callback is not None: self.activation_callback() elif self.state is REALLY_INACTIVE: self.connection.telnet_send_option(self.send_no, self.option) else: raise ValueError('option in illegal state {!r}'.format(self)) elif command == self.ack_no: if self.state is REQUESTED: self.state = INACTIVE self.active = False elif self.state is ACTIVE: self.state = INACTIVE self.connection.telnet_send_option(self.send_no, self.option) self.active = False elif self.state is INACTIVE: pass elif self.state is REALLY_INACTIVE: pass else: raise ValueError('option in illegal state {!r}'.format(self)) class TelnetSubnegotiation(object): """\ A object to handle subnegotiation of options. In this case actually sub-sub options for RFC 2217. It is used to track com port options. """ def __init__(self, connection, name, option, ack_option=None): if ack_option is None: ack_option = option self.connection = connection self.name = name self.option = option self.value = None self.ack_option = ack_option self.state = INACTIVE def __repr__(self): """String for debug outputs.""" return "{sn.name}:{sn.state}".format(sn=self) def set(self, value): """\ Request a change of the value. a request is sent to the server. if the client needs to know if the change is performed he has to check the state of this object. """ self.value = value self.state = REQUESTED self.connection.rfc2217_send_subnegotiation(self.option, self.value) if self.connection.logger: self.connection.logger.debug("SB Requesting {} -> {!r}".format(self.name, self.value)) def is_ready(self): """\ Check if answer from server has been received. when server rejects the change, raise a ValueError. """ if self.state == REALLY_INACTIVE: raise ValueError("remote rejected value for option {!r}".format(self.name)) return self.state == ACTIVE # add property to have a similar interface as TelnetOption active = property(is_ready) def wait(self, timeout=3): """\ Wait until the subnegotiation has been acknowledged or timeout. It can also throw a value error when the answer from the server does not match the value sent. """ timeout_timer = Timeout(timeout) while not timeout_timer.expired(): time.sleep(0.05) # prevent 100% CPU load if self.is_ready(): break else: raise SerialException("timeout while waiting for option {!r}".format(self.name)) def check_answer(self, suboption): """\ Check an incoming subnegotiation block. The parameter already has cut off the header like sub option number and com port option value. """ if self.value == suboption[:len(self.value)]: self.state = ACTIVE else: # error propagation done in is_ready self.state = REALLY_INACTIVE if self.connection.logger: self.connection.logger.debug("SB Answer {} -> {!r} -> {}".format(self.name, suboption, self.state)) class Serial(SerialBase): """Serial port implementation for RFC 2217 remote serial ports.""" BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200) def __init__(self, *args, **kwargs): self._thread = None self._socket = None self._linestate = 0 self._modemstate = None self._modemstate_timeout = Timeout(-1) self._remote_suspend_flow = False self._write_lock = None self.logger = None self._ignore_set_control_answer = False self._poll_modem_state = False self._network_timeout = 3 self._telnet_options = None self._rfc2217_port_settings = None self._rfc2217_options = None self._read_buffer = None super(Serial, self).__init__(*args, **kwargs) # must be last call in case of auto-open def open(self): """\ Open port with current settings. This may throw a SerialException if the port cannot be opened. """ self.logger = None self._ignore_set_control_answer = False self._poll_modem_state = False self._network_timeout = 3 if self._port is None: raise SerialException("Port must be configured before it can be used.") if self.is_open: raise SerialException("Port is already open.") try: self._socket = socket.create_connection(self.from_url(self.portstr), timeout=5) # XXX good value? self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) except Exception as msg: self._socket = None raise SerialException("Could not open port {}: {}".format(self.portstr, msg)) # use a thread save queue as buffer. it also simplifies implementing # the read timeout self._read_buffer = Queue.Queue() # to ensure that user writes does not interfere with internal # telnet/rfc2217 options establish a lock self._write_lock = threading.Lock() # name the following separately so that, below, a check can be easily done mandadory_options = [ TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED), ] # all supported telnet options self._telnet_options = [ TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED), TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED), TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE), TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED), ] + mandadory_options # RFC 2217 specific states # COM port settings self._rfc2217_port_settings = { 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE), 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE), 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY), 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE), } # There are more subnegotiation objects, combine all in one dictionary # for easy access self._rfc2217_options = { 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA), 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL), } self._rfc2217_options.update(self._rfc2217_port_settings) # cache for line and modem states that the server sends to us self._linestate = 0 self._modemstate = None self._modemstate_timeout = Timeout(-1) # RFC 2217 flow control between server and client self._remote_suspend_flow = False self.is_open = True self._thread = threading.Thread(target=self._telnet_read_loop) self._thread.setDaemon(True) self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port)) self._thread.start() try: # must clean-up if open fails # negotiate Telnet/RFC 2217 -> send initial requests for option in self._telnet_options: if option.state is REQUESTED: self.telnet_send_option(option.send_yes, option.option) # now wait until important options are negotiated timeout = Timeout(self._network_timeout) while not timeout.expired(): time.sleep(0.05) # prevent 100% CPU load if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options): break else: raise SerialException( "Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options)) if self.logger: self.logger.info("Negotiated options: {}".format(self._telnet_options)) # fine, go on, set RFC 2217 specific things self._reconfigure_port() # all things set up get, now a clean start if not self._dsrdtr: self._update_dtr_state() if not self._rtscts: self._update_rts_state() self.reset_input_buffer() self.reset_output_buffer() except: self.close() raise def _reconfigure_port(self): """Set communication parameters on opened port.""" if self._socket is None: raise SerialException("Can only operate on open ports") # if self._timeout != 0 and self._interCharTimeout is not None: # XXX if self._write_timeout is not None: raise NotImplementedError('write_timeout is currently not supported') # XXX # Setup the connection # to get good performance, all parameter changes are sent first... if not 0 < self._baudrate < 2 ** 32: raise ValueError("invalid baudrate: {!r}".format(self._baudrate)) self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate)) self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize)) self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity])) self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits])) # and now wait until parameters are active items = self._rfc2217_port_settings.values() if self.logger: self.logger.debug("Negotiating settings: {}".format(items)) timeout = Timeout(self._network_timeout) while not timeout.expired(): time.sleep(0.05) # prevent 100% CPU load if sum(o.active for o in items) == len(items): break else: raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items)) if self.logger: self.logger.info("Negotiated settings: {}".format(items)) if self._rtscts and self._xonxoff: raise ValueError('xonxoff and rtscts together are not supported') elif self._rtscts: self.rfc2217_set_control(SET_CONTROL_USE_HW_FLOW_CONTROL) elif self._xonxoff: self.rfc2217_set_control(SET_CONTROL_USE_SW_FLOW_CONTROL) else: self.rfc2217_set_control(SET_CONTROL_USE_NO_FLOW_CONTROL) def close(self): """Close port""" self.is_open = False if self._socket: try: self._socket.shutdown(socket.SHUT_RDWR) self._socket.close() except: # ignore errors. pass if self._thread: self._thread.join(7) # XXX more than socket timeout self._thread = None # in case of quick reconnects, give the server some time time.sleep(0.3) self._socket = None def from_url(self, url): """\ extract host and port from an URL string, other settings are extracted an stored in instance """ parts = urlparse.urlsplit(url) if parts.scheme != "rfc2217": raise SerialException( 'expected a string in the form ' '"rfc2217://:[?option[&option...]]": ' 'not starting with rfc2217:// ({!r})'.format(parts.scheme)) try: # process options now, directly altering self for option, values in urlparse.parse_qs(parts.query, True).items(): if option == 'logging': logging.basicConfig() # XXX is that good to call it here? self.logger = logging.getLogger('pySerial.rfc2217') self.logger.setLevel(LOGGER_LEVELS[values[0]]) self.logger.debug('enabled logging') elif option == 'ign_set_control': self._ignore_set_control_answer = True elif option == 'poll_modem': self._poll_modem_state = True elif option == 'timeout': self._network_timeout = float(values[0]) else: raise ValueError('unknown option: {!r}'.format(option)) if not 0 <= parts.port < 65536: raise ValueError("port not in range 0...65535") except ValueError as e: raise SerialException( 'expected a string in the form ' '"rfc2217://:[?option[&option...]]": {}'.format(e)) return (parts.hostname, parts.port) # - - - - - - - - - - - - - - - - - - - - - - - - @property def in_waiting(self): """Return the number of bytes currently in the input buffer.""" if not self.is_open: raise PortNotOpenError() return self._read_buffer.qsize() def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.is_open: raise PortNotOpenError() data = bytearray() try: timeout = Timeout(self._timeout) while len(data) < size: if self._thread is None or not self._thread.is_alive(): raise SerialException('connection failed (reader thread died)') buf = self._read_buffer.get(True, timeout.time_left()) if buf is None: return bytes(data) data += buf if timeout.expired(): break except Queue.Empty: # -> timeout pass return bytes(data) def write(self, data): """\ Output the given byte string over the serial port. Can block if the connection is blocked. May raise SerialException if the connection is closed. """ if not self.is_open: raise PortNotOpenError() with self._write_lock: try: self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED)) except socket.error as e: raise SerialException("connection failed (socket error): {}".format(e)) return len(data) def reset_input_buffer(self): """Clear input buffer, discarding all that is in the buffer.""" if not self.is_open: raise PortNotOpenError() self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER) # empty read buffer while self._read_buffer.qsize(): self._read_buffer.get(False) def reset_output_buffer(self): """\ Clear output buffer, aborting the current output and discarding all that is in the buffer. """ if not self.is_open: raise PortNotOpenError() self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER) def _update_break_state(self): """\ Set break: Controls TXD. When active, to transmitting is possible. """ if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive')) if self._break_state: self.rfc2217_set_control(SET_CONTROL_BREAK_ON) else: self.rfc2217_set_control(SET_CONTROL_BREAK_OFF) def _update_rts_state(self): """Set terminal status line: Request To Send.""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive')) if self._rts_state: self.rfc2217_set_control(SET_CONTROL_RTS_ON) else: self.rfc2217_set_control(SET_CONTROL_RTS_OFF) def _update_dtr_state(self): """Set terminal status line: Data Terminal Ready.""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive')) if self._dtr_state: self.rfc2217_set_control(SET_CONTROL_DTR_ON) else: self.rfc2217_set_control(SET_CONTROL_DTR_OFF) @property def cts(self): """Read terminal status line: Clear To Send.""" if not self.is_open: raise PortNotOpenError() return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS) @property def dsr(self): """Read terminal status line: Data Set Ready.""" if not self.is_open: raise PortNotOpenError() return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR) @property def ri(self): """Read terminal status line: Ring Indicator.""" if not self.is_open: raise PortNotOpenError() return bool(self.get_modem_state() & MODEMSTATE_MASK_RI) @property def cd(self): """Read terminal status line: Carrier Detect.""" if not self.is_open: raise PortNotOpenError() return bool(self.get_modem_state() & MODEMSTATE_MASK_CD) # - - - platform specific - - - # None so far # - - - RFC2217 specific - - - def _telnet_read_loop(self): """Read loop for the socket.""" mode = M_NORMAL suboption = None try: while self.is_open: try: data = self._socket.recv(1024) except socket.timeout: # just need to get out of recv form time to time to check if # still alive continue except socket.error as e: # connection fails -> terminate loop if self.logger: self.logger.debug("socket error in reader thread: {}".format(e)) self._read_buffer.put(None) break if not data: self._read_buffer.put(None) break # lost connection for byte in iterbytes(data): if mode == M_NORMAL: # interpret as command or as data if byte == IAC: mode = M_IAC_SEEN else: # store data in read buffer or sub option buffer # depending on state if suboption is not None: suboption += byte else: self._read_buffer.put(byte) elif mode == M_IAC_SEEN: if byte == IAC: # interpret as command doubled -> insert character # itself if suboption is not None: suboption += IAC else: self._read_buffer.put(IAC) mode = M_NORMAL elif byte == SB: # sub option start suboption = bytearray() mode = M_NORMAL elif byte == SE: # sub option end -> process it now self._telnet_process_subnegotiation(bytes(suboption)) suboption = None mode = M_NORMAL elif byte in (DO, DONT, WILL, WONT): # negotiation telnet_command = byte mode = M_NEGOTIATE else: # other telnet commands self._telnet_process_command(byte) mode = M_NORMAL elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following self._telnet_negotiate_option(telnet_command, byte) mode = M_NORMAL finally: if self.logger: self.logger.debug("read thread terminated") # - incoming telnet commands and options def _telnet_process_command(self, command): """Process commands other than DO, DONT, WILL, WONT.""" # Currently none. RFC2217 only uses negotiation and subnegotiation. if self.logger: self.logger.warning("ignoring Telnet command: {!r}".format(command)) def _telnet_negotiate_option(self, command, option): """Process incoming DO, DONT, WILL, WONT.""" # check our registered telnet options and forward command to them # they know themselves if they have to answer or not known = False for item in self._telnet_options: # can have more than one match! as some options are duplicated for # 'us' and 'them' if item.option == option: item.process_incoming(command) known = True if not known: # handle unknown options # only answer to positive requests and deny them if command == WILL or command == DO: self.telnet_send_option((DONT if command == WILL else WONT), option) if self.logger: self.logger.warning("rejected Telnet option: {!r}".format(option)) def _telnet_process_subnegotiation(self, suboption): """Process subnegotiation, the data between IAC SB and IAC SE.""" if suboption[0:1] == COM_PORT_OPTION: if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3: self._linestate = ord(suboption[2:3]) # ensure it is a number if self.logger: self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate)) elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3: self._modemstate = ord(suboption[2:3]) # ensure it is a number if self.logger: self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate)) # update time when we think that a poll would make sense self._modemstate_timeout.restart(0.3) elif suboption[1:2] == FLOWCONTROL_SUSPEND: self._remote_suspend_flow = True elif suboption[1:2] == FLOWCONTROL_RESUME: self._remote_suspend_flow = False else: for item in self._rfc2217_options.values(): if item.ack_option == suboption[1:2]: #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:]) item.check_answer(bytes(suboption[2:])) break else: if self.logger: self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption)) else: if self.logger: self.logger.warning("ignoring subnegotiation: {!r}".format(suboption)) # - outgoing telnet commands and options def _internal_raw_write(self, data): """internal socket write with no data escaping. used to send telnet stuff.""" with self._write_lock: self._socket.sendall(data) def telnet_send_option(self, action, option): """Send DO, DONT, WILL, WONT.""" self._internal_raw_write(IAC + action + option) def rfc2217_send_subnegotiation(self, option, value=b''): """Subnegotiation of RFC2217 parameters.""" value = value.replace(IAC, IAC_DOUBLED) self._internal_raw_write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE) def rfc2217_send_purge(self, value): """\ Send purge request to the remote. (PURGE_RECEIVE_BUFFER / PURGE_TRANSMIT_BUFFER / PURGE_BOTH_BUFFERS) """ item = self._rfc2217_options['purge'] item.set(value) # transmit desired purge type item.wait(self._network_timeout) # wait for acknowledge from the server def rfc2217_set_control(self, value): """transmit change of control line to remote""" item = self._rfc2217_options['control'] item.set(value) # transmit desired control type if self._ignore_set_control_answer: # answers are ignored when option is set. compatibility mode for # servers that answer, but not the expected one... (or no answer # at all) i.e. sredird time.sleep(0.1) # this helps getting the unit tests passed else: item.wait(self._network_timeout) # wait for acknowledge from the server def rfc2217_flow_server_ready(self): """\ check if server is ready to receive data. block for some time when not. """ #~ if self._remote_suspend_flow: #~ wait--- def get_modem_state(self): """\ get last modem state (cached value. If value is "old", request a new one. This cache helps that we don't issue to many requests when e.g. all status lines, one after the other is queried by the user (CTS, DSR etc.) """ # active modem state polling enabled? is the value fresh enough? if self._poll_modem_state and self._modemstate_timeout.expired(): if self.logger: self.logger.debug('polling modem state') # when it is older, request an update self.rfc2217_send_subnegotiation(NOTIFY_MODEMSTATE) timeout = Timeout(self._network_timeout) while not timeout.expired(): time.sleep(0.05) # prevent 100% CPU load # when expiration time is updated, it means that there is a new # value if not self._modemstate_timeout.expired(): break else: if self.logger: self.logger.warning('poll for modem state failed') # even when there is a timeout, do not generate an error just # return the last known value. this way we can support buggy # servers that do not respond to polls, but send automatic # updates. if self._modemstate is not None: if self.logger: self.logger.debug('using cached modem state') return self._modemstate else: # never received a notification from the server raise SerialException("remote sends no NOTIFY_MODEMSTATE") ############################################################################# # The following is code that helps implementing an RFC 2217 server. class PortManager(object): """\ This class manages the state of Telnet and RFC 2217. It needs a serial instance and a connection to work with. Connection is expected to implement a (thread safe) write function, that writes the string to the network. """ def __init__(self, serial_port, connection, logger=None): self.serial = serial_port self.connection = connection self.logger = logger self._client_is_rfc2217 = False # filter state machine self.mode = M_NORMAL self.suboption = None self.telnet_command = None # states for modem/line control events self.modemstate_mask = 255 self.last_modemstate = None self.linstate_mask = 0 # all supported telnet options self._telnet_options = [ TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED), TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE), TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED), TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok), TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok), ] # negotiate Telnet/RFC2217 -> send initial requests if self.logger: self.logger.debug("requesting initial Telnet/RFC 2217 options") for option in self._telnet_options: if option.state is REQUESTED: self.telnet_send_option(option.send_yes, option.option) # issue 1st modem state notification def _client_ok(self): """\ callback of telnet option. It gets called when option is activated. This one here is used to detect when the client agrees on RFC 2217. A flag is set so that other functions like check_modem_lines know if the client is OK. """ # The callback is used for we and they so if one party agrees, we're # already happy. it seems not all servers do the negotiation correctly # and i guess there are incorrect clients too.. so be happy if client # answers one or the other positively. self._client_is_rfc2217 = True if self.logger: self.logger.info("client accepts RFC 2217") # this is to ensure that the client gets a notification, even if there # was no change self.check_modem_lines(force_notification=True) # - outgoing telnet commands and options def telnet_send_option(self, action, option): """Send DO, DONT, WILL, WONT.""" self.connection.write(IAC + action + option) def rfc2217_send_subnegotiation(self, option, value=b''): """Subnegotiation of RFC 2217 parameters.""" value = value.replace(IAC, IAC_DOUBLED) self.connection.write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE) # - check modem lines, needs to be called periodically from user to # establish polling def check_modem_lines(self, force_notification=False): """\ read control lines from serial port and compare the last value sent to remote. send updates on changes. """ modemstate = ( (self.serial.cts and MODEMSTATE_MASK_CTS) | (self.serial.dsr and MODEMSTATE_MASK_DSR) | (self.serial.ri and MODEMSTATE_MASK_RI) | (self.serial.cd and MODEMSTATE_MASK_CD)) # check what has changed deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0 if deltas & MODEMSTATE_MASK_CTS: modemstate |= MODEMSTATE_MASK_CTS_CHANGE if deltas & MODEMSTATE_MASK_DSR: modemstate |= MODEMSTATE_MASK_DSR_CHANGE if deltas & MODEMSTATE_MASK_RI: modemstate |= MODEMSTATE_MASK_RI_CHANGE if deltas & MODEMSTATE_MASK_CD: modemstate |= MODEMSTATE_MASK_CD_CHANGE # if new state is different and the mask allows this change, send # notification. suppress notifications when client is not rfc2217 if modemstate != self.last_modemstate or force_notification: if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification: self.rfc2217_send_subnegotiation( SERVER_NOTIFY_MODEMSTATE, to_bytes([modemstate & self.modemstate_mask])) if self.logger: self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate)) # save last state, but forget about deltas. # otherwise it would also notify about changing deltas which is # probably not very useful self.last_modemstate = modemstate & 0xf0 # - outgoing data escaping def escape(self, data): """\ This generator function is for the user. All outgoing data has to be properly escaped, so that no IAC character in the data stream messes up the Telnet state machine in the server. socket.sendall(escape(data)) """ for byte in iterbytes(data): if byte == IAC: yield IAC yield IAC else: yield byte # - incoming data filter def filter(self, data): """\ Handle a bunch of incoming bytes. This is a generator. It will yield all characters not of interest for Telnet/RFC 2217. The idea is that the reader thread pushes data from the socket through this filter: for byte in filter(socket.recv(1024)): # do things like CR/LF conversion/whatever # and write data to the serial port serial.write(byte) (socket error handling code left as exercise for the reader) """ for byte in iterbytes(data): if self.mode == M_NORMAL: # interpret as command or as data if byte == IAC: self.mode = M_IAC_SEEN else: # store data in sub option buffer or pass it to our # consumer depending on state if self.suboption is not None: self.suboption += byte else: yield byte elif self.mode == M_IAC_SEEN: if byte == IAC: # interpret as command doubled -> insert character # itself if self.suboption is not None: self.suboption += byte else: yield byte self.mode = M_NORMAL elif byte == SB: # sub option start self.suboption = bytearray() self.mode = M_NORMAL elif byte == SE: # sub option end -> process it now self._telnet_process_subnegotiation(bytes(self.suboption)) self.suboption = None self.mode = M_NORMAL elif byte in (DO, DONT, WILL, WONT): # negotiation self.telnet_command = byte self.mode = M_NEGOTIATE else: # other telnet commands self._telnet_process_command(byte) self.mode = M_NORMAL elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following self._telnet_negotiate_option(self.telnet_command, byte) self.mode = M_NORMAL # - incoming telnet commands and options def _telnet_process_command(self, command): """Process commands other than DO, DONT, WILL, WONT.""" # Currently none. RFC2217 only uses negotiation and subnegotiation. if self.logger: self.logger.warning("ignoring Telnet command: {!r}".format(command)) def _telnet_negotiate_option(self, command, option): """Process incoming DO, DONT, WILL, WONT.""" # check our registered telnet options and forward command to them # they know themselves if they have to answer or not known = False for item in self._telnet_options: # can have more than one match! as some options are duplicated for # 'us' and 'them' if item.option == option: item.process_incoming(command) known = True if not known: # handle unknown options # only answer to positive requests and deny them if command == WILL or command == DO: self.telnet_send_option((DONT if command == WILL else WONT), option) if self.logger: self.logger.warning("rejected Telnet option: {!r}".format(option)) def _telnet_process_subnegotiation(self, suboption): """Process subnegotiation, the data between IAC SB and IAC SE.""" if suboption[0:1] == COM_PORT_OPTION: if self.logger: self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption)) if suboption[1:2] == SET_BAUDRATE: backup = self.serial.baudrate try: (baudrate,) = struct.unpack(b"!I", suboption[2:6]) if baudrate != 0: self.serial.baudrate = baudrate except ValueError as e: if self.logger: self.logger.error("failed to set baud rate: {}".format(e)) self.serial.baudrate = backup else: if self.logger: self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate)) self.rfc2217_send_subnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate)) elif suboption[1:2] == SET_DATASIZE: backup = self.serial.bytesize try: (datasize,) = struct.unpack(b"!B", suboption[2:3]) if datasize != 0: self.serial.bytesize = datasize except ValueError as e: if self.logger: self.logger.error("failed to set data size: {}".format(e)) self.serial.bytesize = backup else: if self.logger: self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize)) self.rfc2217_send_subnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize)) elif suboption[1:2] == SET_PARITY: backup = self.serial.parity try: parity = struct.unpack(b"!B", suboption[2:3])[0] if parity != 0: self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity] except ValueError as e: if self.logger: self.logger.error("failed to set parity: {}".format(e)) self.serial.parity = backup else: if self.logger: self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity)) self.rfc2217_send_subnegotiation( SERVER_SET_PARITY, struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity])) elif suboption[1:2] == SET_STOPSIZE: backup = self.serial.stopbits try: stopbits = struct.unpack(b"!B", suboption[2:3])[0] if stopbits != 0: self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits] except ValueError as e: if self.logger: self.logger.error("failed to set stop bits: {}".format(e)) self.serial.stopbits = backup else: if self.logger: self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits)) self.rfc2217_send_subnegotiation( SERVER_SET_STOPSIZE, struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])) elif suboption[1:2] == SET_CONTROL: if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING: if self.serial.xonxoff: self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) elif self.serial.rtscts: self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) else: self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL: self.serial.xonxoff = False self.serial.rtscts = False if self.logger: self.logger.info("changed flow control to None") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL: self.serial.xonxoff = True if self.logger: self.logger.info("changed flow control to XON/XOFF") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL: self.serial.rtscts = True if self.logger: self.logger.info("changed flow control to RTS/CTS") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE: if self.logger: self.logger.warning("requested break state - not implemented") pass # XXX needs cached value elif suboption[2:3] == SET_CONTROL_BREAK_ON: self.serial.break_condition = True if self.logger: self.logger.info("changed BREAK to active") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON) elif suboption[2:3] == SET_CONTROL_BREAK_OFF: self.serial.break_condition = False if self.logger: self.logger.info("changed BREAK to inactive") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF) elif suboption[2:3] == SET_CONTROL_REQ_DTR: if self.logger: self.logger.warning("requested DTR state - not implemented") pass # XXX needs cached value elif suboption[2:3] == SET_CONTROL_DTR_ON: self.serial.dtr = True if self.logger: self.logger.info("changed DTR to active") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON) elif suboption[2:3] == SET_CONTROL_DTR_OFF: self.serial.dtr = False if self.logger: self.logger.info("changed DTR to inactive") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF) elif suboption[2:3] == SET_CONTROL_REQ_RTS: if self.logger: self.logger.warning("requested RTS state - not implemented") pass # XXX needs cached value #~ self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) elif suboption[2:3] == SET_CONTROL_RTS_ON: self.serial.rts = True if self.logger: self.logger.info("changed RTS to active") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) elif suboption[2:3] == SET_CONTROL_RTS_OFF: self.serial.rts = False if self.logger: self.logger.info("changed RTS to inactive") self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF) #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN: #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN: #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN: #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN: #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL: #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL: #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL: elif suboption[1:2] == NOTIFY_LINESTATE: # client polls for current state self.rfc2217_send_subnegotiation( SERVER_NOTIFY_LINESTATE, to_bytes([0])) # sorry, nothing like that implemented elif suboption[1:2] == NOTIFY_MODEMSTATE: if self.logger: self.logger.info("request for modem state") # client polls for current state self.check_modem_lines(force_notification=True) elif suboption[1:2] == FLOWCONTROL_SUSPEND: if self.logger: self.logger.info("suspend") self._remote_suspend_flow = True elif suboption[1:2] == FLOWCONTROL_RESUME: if self.logger: self.logger.info("resume") self._remote_suspend_flow = False elif suboption[1:2] == SET_LINESTATE_MASK: self.linstate_mask = ord(suboption[2:3]) # ensure it is a number if self.logger: self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask)) elif suboption[1:2] == SET_MODEMSTATE_MASK: self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number if self.logger: self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask)) elif suboption[1:2] == PURGE_DATA: if suboption[2:3] == PURGE_RECEIVE_BUFFER: self.serial.reset_input_buffer() if self.logger: self.logger.info("purge in") self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER) elif suboption[2:3] == PURGE_TRANSMIT_BUFFER: self.serial.reset_output_buffer() if self.logger: self.logger.info("purge out") self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER) elif suboption[2:3] == PURGE_BOTH_BUFFERS: self.serial.reset_input_buffer() self.serial.reset_output_buffer() if self.logger: self.logger.info("purge both") self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS) else: if self.logger: self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:]))) else: if self.logger: self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:]))) else: if self.logger: self.logger.warning("unknown subnegotiation: {!r}".format(suboption)) # simple client test if __name__ == '__main__': import sys s = Serial('rfc2217://localhost:7000', 115200) sys.stdout.write('{}\n'.format(s)) sys.stdout.write("write...\n") s.write(b"hello\n") s.flush() sys.stdout.write("read: {}\n".format(s.read(5))) s.close() pyserial-3.5/serial/serialwin32.py0000644000175000017500000004747413730355053017472 0ustar chrischris00000000000000#! python # # backend for Windows ("win32" incl. 32/64 bit support) # # (C) 2001-2020 Chris Liechti # # This file is part of pySerial. https://github.com/pyserial/pyserial # SPDX-License-Identifier: BSD-3-Clause # # Initial patch to use ctypes by Giovanni Bajo from __future__ import absolute_import # pylint: disable=invalid-name,too-few-public-methods import ctypes import time from serial import win32 import serial from serial.serialutil import SerialBase, SerialException, to_bytes, PortNotOpenError, SerialTimeoutException class Serial(SerialBase): """Serial port implementation for Win32 based on ctypes.""" BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200) def __init__(self, *args, **kwargs): self._port_handle = None self._overlapped_read = None self._overlapped_write = None super(Serial, self).__init__(*args, **kwargs) def open(self): """\ Open port with current settings. This may throw a SerialException if the port cannot be opened. """ if self._port is None: raise SerialException("Port must be configured before it can be used.") if self.is_open: raise SerialException("Port is already open.") # the "\\.\COMx" format is required for devices other than COM1-COM8 # not all versions of windows seem to support this properly # so that the first few ports are used with the DOS device name port = self.name try: if port.upper().startswith('COM') and int(port[3:]) > 8: port = '\\\\.\\' + port except ValueError: # for like COMnotanumber pass self._port_handle = win32.CreateFile( port, win32.GENERIC_READ | win32.GENERIC_WRITE, 0, # exclusive access None, # no security win32.OPEN_EXISTING, win32.FILE_ATTRIBUTE_NORMAL | win32.FILE_FLAG_OVERLAPPED, 0) if self._port_handle == win32.INVALID_HANDLE_VALUE: self._port_handle = None # 'cause __del__ is called anyway raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError())) try: self._overlapped_read = win32.OVERLAPPED() self._overlapped_read.hEvent = win32.CreateEvent(None, 1, 0, None) self._overlapped_write = win32.OVERLAPPED() #~ self._overlapped_write.hEvent = win32.CreateEvent(None, 1, 0, None) self._overlapped_write.hEvent = win32.CreateEvent(None, 0, 0, None) # Setup a 4k buffer win32.SetupComm(self._port_handle, 4096, 4096) # Save original timeout values: self._orgTimeouts = win32.COMMTIMEOUTS() win32.GetCommTimeouts(self._port_handle, ctypes.byref(self._orgTimeouts)) self._reconfigure_port() # Clear buffers: # Remove anything that was there win32.PurgeComm( self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT | win32.PURGE_RXCLEAR | win32.PURGE_RXABORT) except: try: self._close() except: # ignore any exception when closing the port # also to keep original exception that happened when setting up pass self._port_handle = None raise else: self.is_open = True def _reconfigure_port(self): """Set communication parameters on opened port.""" if not self._port_handle: raise SerialException("Can only operate on a valid port handle") # Set Windows timeout values # timeouts is a tuple with the following items: # (ReadIntervalTimeout,ReadTotalTimeoutMultiplier, # ReadTotalTimeoutConstant,WriteTotalTimeoutMultiplier, # WriteTotalTimeoutConstant) timeouts = win32.COMMTIMEOUTS() if self._timeout is None: pass # default of all zeros is OK elif self._timeout == 0: timeouts.ReadIntervalTimeout = win32.MAXDWORD else: timeouts.ReadTotalTimeoutConstant = max(int(self._timeout * 1000), 1) if self._timeout != 0 and self._inter_byte_timeout is not None: timeouts.ReadIntervalTimeout = max(int(self._inter_byte_timeout * 1000), 1) if self._write_timeout is None: pass elif self._write_timeout == 0: timeouts.WriteTotalTimeoutConstant = win32.MAXDWORD else: timeouts.WriteTotalTimeoutConstant = max(int(self._write_timeout * 1000), 1) win32.SetCommTimeouts(self._port_handle, ctypes.byref(timeouts)) win32.SetCommMask(self._port_handle, win32.EV_ERR) # Setup the connection info. # Get state and modify it: comDCB = win32.DCB() win32.GetCommState(self._port_handle, ctypes.byref(comDCB)) comDCB.BaudRate = self._baudrate if self._bytesize == serial.FIVEBITS: comDCB.ByteSize = 5 elif self._bytesize == serial.SIXBITS: comDCB.ByteSize = 6 elif self._bytesize == serial.SEVENBITS: comDCB.ByteSize = 7 elif self._bytesize == serial.EIGHTBITS: comDCB.ByteSize = 8 else: raise ValueError("Unsupported number of data bits: {!r}".format(self._bytesize)) if self._parity == serial.PARITY_NONE: comDCB.Parity = win32.NOPARITY comDCB.fParity = 0 # Disable Parity Check elif self._parity == serial.PARITY_EVEN: comDCB.Parity = win32.EVENPARITY comDCB.fParity = 1 # Enable Parity Check elif self._parity == serial.PARITY_ODD: comDCB.Parity = win32.ODDPARITY comDCB.fParity = 1 # Enable Parity Check elif self._parity == serial.PARITY_MARK: comDCB.Parity = win32.MARKPARITY comDCB.fParity = 1 # Enable Parity Check elif self._parity == serial.PARITY_SPACE: comDCB.Parity = win32.SPACEPARITY comDCB.fParity = 1 # Enable Parity Check else: raise ValueError("Unsupported parity mode: {!r}".format(self._parity)) if self._stopbits == serial.STOPBITS_ONE: comDCB.StopBits = win32.ONESTOPBIT elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: comDCB.StopBits = win32.ONE5STOPBITS elif self._stopbits == serial.STOPBITS_TWO: comDCB.StopBits = win32.TWOSTOPBITS else: raise ValueError("Unsupported number of stop bits: {!r}".format(self._stopbits)) comDCB.fBinary = 1 # Enable Binary Transmission # Char. w/ Parity-Err are replaced with 0xff (if fErrorChar is set to TRUE) if self._rs485_mode is None: if self._rtscts: comDCB.fRtsControl = win32.RTS_CONTROL_HANDSHAKE else: comDCB.fRtsControl = win32.RTS_CONTROL_ENABLE if self._rts_state else win32.RTS_CONTROL_DISABLE comDCB.fOutxCtsFlow = self._rtscts else: # checks for unsupported settings # XXX verify if platform really does not have a setting for those if not self._rs485_mode.rts_level_for_tx: raise ValueError( 'Unsupported value for RS485Settings.rts_level_for_tx: {!r} (only True is allowed)'.format( self._rs485_mode.rts_level_for_tx,)) if self._rs485_mode.rts_level_for_rx: raise ValueError( 'Unsupported value for RS485Settings.rts_level_for_rx: {!r} (only False is allowed)'.format( self._rs485_mode.rts_level_for_rx,)) if self._rs485_mode.delay_before_tx is not None: raise ValueError( 'Unsupported value for RS485Settings.delay_before_tx: {!r} (only None is allowed)'.format( self._rs485_mode.delay_before_tx,)) if self._rs485_mode.delay_before_rx is not None: raise ValueError( 'Unsupported value for RS485Settings.delay_before_rx: {!r} (only None is allowed)'.format( self._rs485_mode.delay_before_rx,)) if self._rs485_mode.loopback: raise ValueError( 'Unsupported value for RS485Settings.loopback: {!r} (only False is allowed)'.format( self._rs485_mode.loopback,)) comDCB.fRtsControl = win32.RTS_CONTROL_TOGGLE comDCB.fOutxCtsFlow = 0 if self._dsrdtr: comDCB.fDtrControl = win32.DTR_CONTROL_HANDSHAKE else: comDCB.fDtrControl = win32.DTR_CONTROL_ENABLE if self._dtr_state else win32.DTR_CONTROL_DISABLE comDCB.fOutxDsrFlow = self._dsrdtr comDCB.fOutX = self._xonxoff comDCB.fInX = self._xonxoff comDCB.fNull = 0 comDCB.fErrorChar = 0 comDCB.fAbortOnError = 0 comDCB.XonChar = serial.XON comDCB.XoffChar = serial.XOFF if not win32.SetCommState(self._port_handle, ctypes.byref(comDCB)): raise SerialException( 'Cannot configure port, something went wrong. ' 'Original message: {!r}'.format(ctypes.WinError())) #~ def __del__(self): #~ self.close() def _close(self): """internal close port helper""" if self._port_handle is not None: # Restore original timeout values: win32.SetCommTimeouts(self._port_handle, self._orgTimeouts) if self._overlapped_read is not None: self.cancel_read() win32.CloseHandle(self._overlapped_read.hEvent) self._overlapped_read = None if self._overlapped_write is not None: self.cancel_write() win32.CloseHandle(self._overlapped_write.hEvent) self._overlapped_write = None win32.CloseHandle(self._port_handle) self._port_handle = None def close(self): """Close port""" if self.is_open: self._close() self.is_open = False # - - - - - - - - - - - - - - - - - - - - - - - - @property def in_waiting(self): """Return the number of bytes currently in the input buffer.""" flags = win32.DWORD() comstat = win32.COMSTAT() if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) return comstat.cbInQue def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.is_open: raise PortNotOpenError() if size > 0: win32.ResetEvent(self._overlapped_read.hEvent) flags = win32.DWORD() comstat = win32.COMSTAT() if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) n = min(comstat.cbInQue, size) if self.timeout == 0 else size if n > 0: buf = ctypes.create_string_buffer(n) rc = win32.DWORD() read_ok = win32.ReadFile( self._port_handle, buf, n, ctypes.byref(rc), ctypes.byref(self._overlapped_read)) if not read_ok and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): raise SerialException("ReadFile failed ({!r})".format(ctypes.WinError())) result_ok = win32.GetOverlappedResult( self._port_handle, ctypes.byref(self._overlapped_read), ctypes.byref(rc), True) if not result_ok: if win32.GetLastError() != win32.ERROR_OPERATION_ABORTED: raise SerialException("GetOverlappedResult failed ({!r})".format(ctypes.WinError())) read = buf.raw[:rc.value] else: read = bytes() else: read = bytes() return bytes(read) def write(self, data): """Output the given byte string over the serial port.""" if not self.is_open: raise PortNotOpenError() #~ if not isinstance(data, (bytes, bytearray)): #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) # convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview data = to_bytes(data) if data: #~ win32event.ResetEvent(self._overlapped_write.hEvent) n = win32.DWORD() success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write) if self._write_timeout != 0: # if blocking (None) or w/ write timeout (>0) if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError())) # Wait for the write to complete. #~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE) win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True) if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED: return n.value # canceled IO is no error if n.value != len(data): raise SerialTimeoutException('Write timeout') return n.value else: errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError() if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY, win32.ERROR_OPERATION_ABORTED): return 0 elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): # no info on true length provided by OS function in async mode return len(data) else: raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError())) else: return 0 def flush(self): """\ Flush of file like objects. In this case, wait until all data is written. """ while self.out_waiting: time.sleep(0.05) # XXX could also use WaitCommEvent with mask EV_TXEMPTY, but it would # require overlapped IO and it's also only possible to set a single mask # on the port--- def reset_input_buffer(self): """Clear input buffer, discarding all that is in the buffer.""" if not self.is_open: raise PortNotOpenError() win32.PurgeComm(self._port_handle, win32.PURGE_RXCLEAR | win32.PURGE_RXABORT) def reset_output_buffer(self): """\ Clear output buffer, aborting the current output and discarding all that is in the buffer. """ if not self.is_open: raise PortNotOpenError() win32.PurgeComm(self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT) def _update_break_state(self): """Set break: Controls TXD. When active, to transmitting is possible.""" if not self.is_open: raise PortNotOpenError() if self._break_state: win32.SetCommBreak(self._port_handle) else: win32.ClearCommBreak(self._port_handle) def _update_rts_state(self): """Set terminal status line: Request To Send""" if self._rts_state: win32.EscapeCommFunction(self._port_handle, win32.SETRTS) else: win32.EscapeCommFunction(self._port_handle, win32.CLRRTS) def _update_dtr_state(self): """Set terminal status line: Data Terminal Ready""" if self._dtr_state: win32.EscapeCommFunction(self._port_handle, win32.SETDTR) else: win32.EscapeCommFunction(self._port_handle, win32.CLRDTR) def _GetCommModemStatus(self): if not self.is_open: raise PortNotOpenError() stat = win32.DWORD() win32.GetCommModemStatus(self._port_handle, ctypes.byref(stat)) return stat.value @property def cts(self): """Read terminal status line: Clear To Send""" return win32.MS_CTS_ON & self._GetCommModemStatus() != 0 @property def dsr(self): """Read terminal status line: Data Set Ready""" return win32.MS_DSR_ON & self._GetCommModemStatus() != 0 @property def ri(self): """Read terminal status line: Ring Indicator""" return win32.MS_RING_ON & self._GetCommModemStatus() != 0 @property def cd(self): """Read terminal status line: Carrier Detect""" return win32.MS_RLSD_ON & self._GetCommModemStatus() != 0 # - - platform specific - - - - def set_buffer_size(self, rx_size=4096, tx_size=None): """\ Recommend a buffer size to the driver (device driver can ignore this value). Must be called after the port is opened. """ if tx_size is None: tx_size = rx_size win32.SetupComm(self._port_handle, rx_size, tx_size) def set_output_flow_control(self, enable=True): """\ Manually control flow - when software flow control is enabled. This will do the same as if XON (true) or XOFF (false) are received from the other device and control the transmission accordingly. WARNING: this function is not portable to different platforms! """ if not self.is_open: raise PortNotOpenError() if enable: win32.EscapeCommFunction(self._port_handle, win32.SETXON) else: win32.EscapeCommFunction(self._port_handle, win32.SETXOFF) @property def out_waiting(self): """Return how many bytes the in the outgoing buffer""" flags = win32.DWORD() comstat = win32.COMSTAT() if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) return comstat.cbOutQue def _cancel_overlapped_io(self, overlapped): """Cancel a blocking read operation, may be called from other thread""" # check if read operation is pending rc = win32.DWORD() err = win32.GetOverlappedResult( self._port_handle, ctypes.byref(overlapped), ctypes.byref(rc), False) if not err and win32.GetLastError() in (win32.ERROR_IO_PENDING, win32.ERROR_IO_INCOMPLETE): # cancel, ignoring any errors (e.g. it may just have finished on its own) win32.CancelIoEx(self._port_handle, overlapped) def cancel_read(self): """Cancel a blocking read operation, may be called from other thread""" self._cancel_overlapped_io(self._overlapped_read) def cancel_write(self): """Cancel a blocking write operation, may be called from other thread""" self._cancel_overlapped_io(self._overlapped_write) @SerialBase.exclusive.setter def exclusive(self, exclusive): """Change the exclusive access setting.""" if exclusive is not None and not exclusive: raise ValueError('win32 only supports exclusive access (not: {})'.format(exclusive)) else: serial.SerialBase.exclusive.__set__(self, exclusive) pyserial-3.5/serial/rs485.py0000644000175000017500000000635113641767344016215 0ustar chrischris00000000000000#!/usr/bin/env python # RS485 support # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause """\ The settings for RS485 are stored in a dedicated object that can be applied to serial ports (where supported). NOTE: Some implementations may only support a subset of the settings. """ from __future__ import absolute_import import time import serial class RS485Settings(object): def __init__( self, rts_level_for_tx=True, rts_level_for_rx=False, loopback=False, delay_before_tx=None, delay_before_rx=None): self.rts_level_for_tx = rts_level_for_tx self.rts_level_for_rx = rts_level_for_rx self.loopback = loopback self.delay_before_tx = delay_before_tx self.delay_before_rx = delay_before_rx class RS485(serial.Serial): """\ A subclass that replaces the write method with one that toggles RTS according to the RS485 settings. NOTE: This may work unreliably on some serial ports (control signals not synchronized or delayed compared to data). Using delays may be unreliable (varying times, larger than expected) as the OS may not support very fine grained delays (no smaller than in the order of tens of milliseconds). NOTE: Some implementations support this natively. Better performance can be expected when the native version is used. NOTE: The loopback property is ignored by this implementation. The actual behavior depends on the used hardware. Usage: ser = RS485(...) ser.rs485_mode = RS485Settings(...) ser.write(b'hello') """ def __init__(self, *args, **kwargs): super(RS485, self).__init__(*args, **kwargs) self._alternate_rs485_settings = None def write(self, b): """Write to port, controlling RTS before and after transmitting.""" if self._alternate_rs485_settings is not None: # apply level for TX and optional delay self.setRTS(self._alternate_rs485_settings.rts_level_for_tx) if self._alternate_rs485_settings.delay_before_tx is not None: time.sleep(self._alternate_rs485_settings.delay_before_tx) # write and wait for data to be written super(RS485, self).write(b) super(RS485, self).flush() # optional delay and apply level for RX if self._alternate_rs485_settings.delay_before_rx is not None: time.sleep(self._alternate_rs485_settings.delay_before_rx) self.setRTS(self._alternate_rs485_settings.rts_level_for_rx) else: super(RS485, self).write(b) # redirect where the property stores the settings so that underlying Serial # instance does not see them @property def rs485_mode(self): """\ Enable RS485 mode and apply new settings, set to None to disable. See serial.rs485.RS485Settings for more info about the value. """ return self._alternate_rs485_settings @rs485_mode.setter def rs485_mode(self, rs485_settings): self._alternate_rs485_settings = rs485_settings pyserial-3.5/serial/__main__.py0000664000175000017500000000005513731763261017037 0ustar chrischris00000000000000from .tools import miniterm miniterm.main() pyserial-3.5/serial/urlhandler/0000775000175000017500000000000013756631132017103 5ustar chrischris00000000000000pyserial-3.5/serial/urlhandler/protocol_cp2110.py0000644000175000017500000002053413727523312022304 0ustar chrischris00000000000000#! python # # Backend for Silicon Labs CP2110/4 HID-to-UART devices. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2015 Chris Liechti # (C) 2019 Google LLC # # SPDX-License-Identifier: BSD-3-Clause # This backend implements support for HID-to-UART devices manufactured # by Silicon Labs and marketed as CP2110 and CP2114. The # implementation is (mostly) OS-independent and in userland. It relies # on cython-hidapi (https://github.com/trezor/cython-hidapi). # The HID-to-UART protocol implemented by CP2110/4 is described in the # AN434 document from Silicon Labs: # https://www.silabs.com/documents/public/application-notes/AN434-CP2110-4-Interface-Specification.pdf # TODO items: # - rtscts support is configured for hardware flow control, but the # signaling is missing (AN434 suggests this is done through GPIO). # - Cancelling reads and writes is not supported. # - Baudrate validation is not implemented, as it depends on model and configuration. import struct import threading try: import urlparse except ImportError: import urllib.parse as urlparse try: import Queue except ImportError: import queue as Queue import hid # hidapi import serial from serial.serialutil import SerialBase, SerialException, PortNotOpenError, to_bytes, Timeout # Report IDs and related constant _REPORT_GETSET_UART_ENABLE = 0x41 _DISABLE_UART = 0x00 _ENABLE_UART = 0x01 _REPORT_SET_PURGE_FIFOS = 0x43 _PURGE_TX_FIFO = 0x01 _PURGE_RX_FIFO = 0x02 _REPORT_GETSET_UART_CONFIG = 0x50 _REPORT_SET_TRANSMIT_LINE_BREAK = 0x51 _REPORT_SET_STOP_LINE_BREAK = 0x52 class Serial(SerialBase): # This is not quite correct. AN343 specifies that the minimum # baudrate is different between CP2110 and CP2114, and it's halved # when using non-8-bit symbols. BAUDRATES = (300, 375, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000) def __init__(self, *args, **kwargs): self._hid_handle = None self._read_buffer = None self._thread = None super(Serial, self).__init__(*args, **kwargs) def open(self): if self._port is None: raise SerialException("Port must be configured before it can be used.") if self.is_open: raise SerialException("Port is already open.") self._read_buffer = Queue.Queue() self._hid_handle = hid.device() try: portpath = self.from_url(self.portstr) self._hid_handle.open_path(portpath) except OSError as msg: raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg)) try: self._reconfigure_port() except: try: self._hid_handle.close() except: pass self._hid_handle = None raise else: self.is_open = True self._thread = threading.Thread(target=self._hid_read_loop) self._thread.setDaemon(True) self._thread.setName('pySerial CP2110 reader thread for {}'.format(self._port)) self._thread.start() def from_url(self, url): parts = urlparse.urlsplit(url) if parts.scheme != "cp2110": raise SerialException( 'expected a string in the forms ' '"cp2110:///dev/hidraw9" or "cp2110://0001:0023:00": ' 'not starting with cp2110:// {{!r}}'.format(parts.scheme)) if parts.netloc: # cp2100://BUS:DEVICE:ENDPOINT, for libusb return parts.netloc.encode('utf-8') return parts.path.encode('utf-8') def close(self): self.is_open = False if self._thread: self._thread.join(1) # read timeout is 0.1 self._thread = None self._hid_handle.close() self._hid_handle = None def _reconfigure_port(self): parity_value = None if self._parity == serial.PARITY_NONE: parity_value = 0x00 elif self._parity == serial.PARITY_ODD: parity_value = 0x01 elif self._parity == serial.PARITY_EVEN: parity_value = 0x02 elif self._parity == serial.PARITY_MARK: parity_value = 0x03 elif self._parity == serial.PARITY_SPACE: parity_value = 0x04 else: raise ValueError('Invalid parity: {!r}'.format(self._parity)) if self.rtscts: flow_control_value = 0x01 else: flow_control_value = 0x00 data_bits_value = None if self._bytesize == 5: data_bits_value = 0x00 elif self._bytesize == 6: data_bits_value = 0x01 elif self._bytesize == 7: data_bits_value = 0x02 elif self._bytesize == 8: data_bits_value = 0x03 else: raise ValueError('Invalid char len: {!r}'.format(self._bytesize)) stop_bits_value = None if self._stopbits == serial.STOPBITS_ONE: stop_bits_value = 0x00 elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: stop_bits_value = 0x01 elif self._stopbits == serial.STOPBITS_TWO: stop_bits_value = 0x01 else: raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits)) configuration_report = struct.pack( '>BLBBBB', _REPORT_GETSET_UART_CONFIG, self._baudrate, parity_value, flow_control_value, data_bits_value, stop_bits_value) self._hid_handle.send_feature_report(configuration_report) self._hid_handle.send_feature_report( bytes((_REPORT_GETSET_UART_ENABLE, _ENABLE_UART))) self._update_break_state() @property def in_waiting(self): return self._read_buffer.qsize() def reset_input_buffer(self): if not self.is_open: raise PortNotOpenError() self._hid_handle.send_feature_report( bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_RX_FIFO))) # empty read buffer while self._read_buffer.qsize(): self._read_buffer.get(False) def reset_output_buffer(self): if not self.is_open: raise PortNotOpenError() self._hid_handle.send_feature_report( bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_TX_FIFO))) def _update_break_state(self): if not self._hid_handle: raise PortNotOpenError() if self._break_state: self._hid_handle.send_feature_report( bytes((_REPORT_SET_TRANSMIT_LINE_BREAK, 0))) else: # Note that while AN434 states "There are no data bytes in # the payload other than the Report ID", either hidapi or # Linux does not seem to send the report otherwise. self._hid_handle.send_feature_report( bytes((_REPORT_SET_STOP_LINE_BREAK, 0))) def read(self, size=1): if not self.is_open: raise PortNotOpenError() data = bytearray() try: timeout = Timeout(self._timeout) while len(data) < size: if self._thread is None: raise SerialException('connection failed (reader thread died)') buf = self._read_buffer.get(True, timeout.time_left()) if buf is None: return bytes(data) data += buf if timeout.expired(): break except Queue.Empty: # -> timeout pass return bytes(data) def write(self, data): if not self.is_open: raise PortNotOpenError() data = to_bytes(data) tx_len = len(data) while tx_len > 0: to_be_sent = min(tx_len, 0x3F) report = to_bytes([to_be_sent]) + data[:to_be_sent] self._hid_handle.write(report) data = data[to_be_sent:] tx_len = len(data) def _hid_read_loop(self): try: while self.is_open: data = self._hid_handle.read(64, timeout_ms=100) if not data: continue data_len = data.pop(0) assert data_len == len(data) self._read_buffer.put(bytearray(data)) finally: self._thread = None pyserial-3.5/serial/urlhandler/protocol_loop.py0000644000175000017500000002457713730355201022354 0ustar chrischris00000000000000#! python # # This module implements a loop back connection receiving itself what it sent. # # The purpose of this module is.. well... You can run the unit tests with it. # and it was so easy to implement ;-) # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2020 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause # # URL format: loop://[option[/option...]] # options: # - "debug" print diagnostic messages from __future__ import absolute_import import logging import numbers import time try: import urlparse except ImportError: import urllib.parse as urlparse try: import queue except ImportError: import Queue as queue from serial.serialutil import SerialBase, SerialException, to_bytes, iterbytes, SerialTimeoutException, PortNotOpenError # map log level names to constants. used in from_url() LOGGER_LEVELS = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, } class Serial(SerialBase): """Serial port implementation that simulates a loop back connection in plain software.""" BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200) def __init__(self, *args, **kwargs): self.buffer_size = 4096 self.queue = None self.logger = None self._cancel_write = False super(Serial, self).__init__(*args, **kwargs) def open(self): """\ Open port with current settings. This may throw a SerialException if the port cannot be opened. """ if self.is_open: raise SerialException("Port is already open.") self.logger = None self.queue = queue.Queue(self.buffer_size) if self._port is None: raise SerialException("Port must be configured before it can be used.") # not that there is anything to open, but the function applies the # options found in the URL self.from_url(self.port) # not that there anything to configure... self._reconfigure_port() # all things set up get, now a clean start self.is_open = True if not self._dsrdtr: self._update_dtr_state() if not self._rtscts: self._update_rts_state() self.reset_input_buffer() self.reset_output_buffer() def close(self): if self.is_open: self.is_open = False try: self.queue.put_nowait(None) except queue.Full: pass super(Serial, self).close() def _reconfigure_port(self): """\ Set communication parameters on opened port. For the loop:// protocol all settings are ignored! """ # not that's it of any real use, but it helps in the unit tests if not isinstance(self._baudrate, numbers.Integral) or not 0 < self._baudrate < 2 ** 32: raise ValueError("invalid baudrate: {!r}".format(self._baudrate)) if self.logger: self.logger.info('_reconfigure_port()') def from_url(self, url): """extract host and port from an URL string""" parts = urlparse.urlsplit(url) if parts.scheme != "loop": raise SerialException( 'expected a string in the form ' '"loop://[?logging={debug|info|warning|error}]": not starting ' 'with loop:// ({!r})'.format(parts.scheme)) try: # process options now, directly altering self for option, values in urlparse.parse_qs(parts.query, True).items(): if option == 'logging': logging.basicConfig() # XXX is that good to call it here? self.logger = logging.getLogger('pySerial.loop') self.logger.setLevel(LOGGER_LEVELS[values[0]]) self.logger.debug('enabled logging') else: raise ValueError('unknown option: {!r}'.format(option)) except ValueError as e: raise SerialException( 'expected a string in the form ' '"loop://[?logging={debug|info|warning|error}]": {}'.format(e)) # - - - - - - - - - - - - - - - - - - - - - - - - @property def in_waiting(self): """Return the number of bytes currently in the input buffer.""" if not self.is_open: raise PortNotOpenError() if self.logger: # attention the logged value can differ from return value in # threaded environments... self.logger.debug('in_waiting -> {:d}'.format(self.queue.qsize())) return self.queue.qsize() def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.is_open: raise PortNotOpenError() if self._timeout is not None and self._timeout != 0: timeout = time.time() + self._timeout else: timeout = None data = bytearray() while size > 0 and self.is_open: try: b = self.queue.get(timeout=self._timeout) # XXX inter char timeout except queue.Empty: if self._timeout == 0: break else: if b is not None: data += b size -= 1 else: break # check for timeout now, after data has been read. # useful for timeout = 0 (non blocking) read if timeout and time.time() > timeout: if self.logger: self.logger.info('read timeout') break return bytes(data) def cancel_read(self): self.queue.put_nowait(None) def cancel_write(self): self._cancel_write = True def write(self, data): """\ Output the given byte string over the serial port. Can block if the connection is blocked. May raise SerialException if the connection is closed. """ self._cancel_write = False if not self.is_open: raise PortNotOpenError() data = to_bytes(data) # calculate aprox time that would be used to send the data time_used_to_send = 10.0 * len(data) / self._baudrate # when a write timeout is configured check if we would be successful # (not sending anything, not even the part that would have time) if self._write_timeout is not None and time_used_to_send > self._write_timeout: # must wait so that unit test succeeds time_left = self._write_timeout while time_left > 0 and not self._cancel_write: time.sleep(min(time_left, 0.5)) time_left -= 0.5 if self._cancel_write: return 0 # XXX raise SerialTimeoutException('Write timeout') for byte in iterbytes(data): self.queue.put(byte, timeout=self._write_timeout) return len(data) def reset_input_buffer(self): """Clear input buffer, discarding all that is in the buffer.""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('reset_input_buffer()') try: while self.queue.qsize(): self.queue.get_nowait() except queue.Empty: pass def reset_output_buffer(self): """\ Clear output buffer, aborting the current output and discarding all that is in the buffer. """ if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('reset_output_buffer()') try: while self.queue.qsize(): self.queue.get_nowait() except queue.Empty: pass @property def out_waiting(self): """Return how many bytes the in the outgoing buffer""" if not self.is_open: raise PortNotOpenError() if self.logger: # attention the logged value can differ from return value in # threaded environments... self.logger.debug('out_waiting -> {:d}'.format(self.queue.qsize())) return self.queue.qsize() def _update_break_state(self): """\ Set break: Controls TXD. When active, to transmitting is possible. """ if self.logger: self.logger.info('_update_break_state({!r})'.format(self._break_state)) def _update_rts_state(self): """Set terminal status line: Request To Send""" if self.logger: self.logger.info('_update_rts_state({!r}) -> state of CTS'.format(self._rts_state)) def _update_dtr_state(self): """Set terminal status line: Data Terminal Ready""" if self.logger: self.logger.info('_update_dtr_state({!r}) -> state of DSR'.format(self._dtr_state)) @property def cts(self): """Read terminal status line: Clear To Send""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('CTS -> state of RTS ({!r})'.format(self._rts_state)) return self._rts_state @property def dsr(self): """Read terminal status line: Data Set Ready""" if self.logger: self.logger.info('DSR -> state of DTR ({!r})'.format(self._dtr_state)) return self._dtr_state @property def ri(self): """Read terminal status line: Ring Indicator""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('returning dummy for RI') return False @property def cd(self): """Read terminal status line: Carrier Detect""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('returning dummy for CD') return True # - - - platform specific - - - # None so far # simple client test if __name__ == '__main__': import sys s = Serial('loop://') sys.stdout.write('{}\n'.format(s)) sys.stdout.write("write...\n") s.write("hello\n") s.flush() sys.stdout.write("read: {!r}\n".format(s.read(5))) s.close() pyserial-3.5/serial/urlhandler/protocol_alt.py0000644000175000017500000000376113641767344022173 0ustar chrischris00000000000000#! python # # This module implements a special URL handler that allows selecting an # alternate implementation provided by some backends. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause # # URL format: alt://port[?option[=value][&option[=value]]] # options: # - class=X used class named X instead of Serial # # example: # use poll based implementation on Posix (Linux): # python -m serial.tools.miniterm alt:///dev/ttyUSB0?class=PosixPollSerial from __future__ import absolute_import try: import urlparse except ImportError: import urllib.parse as urlparse import serial def serial_class_for_url(url): """extract host and port from an URL string""" parts = urlparse.urlsplit(url) if parts.scheme != 'alt': raise serial.SerialException( 'expected a string in the form "alt://port[?option[=value][&option[=value]]]": ' 'not starting with alt:// ({!r})'.format(parts.scheme)) class_name = 'Serial' try: for option, values in urlparse.parse_qs(parts.query, True).items(): if option == 'class': class_name = values[0] else: raise ValueError('unknown option: {!r}'.format(option)) except ValueError as e: raise serial.SerialException( 'expected a string in the form ' '"alt://port[?option[=value][&option[=value]]]": {!r}'.format(e)) if not hasattr(serial, class_name): raise ValueError('unknown class: {!r}'.format(class_name)) cls = getattr(serial, class_name) if not issubclass(cls, serial.Serial): raise ValueError('class {!r} is not an instance of Serial'.format(class_name)) return (''.join([parts.netloc, parts.path]), cls) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if __name__ == '__main__': s = serial.serial_for_url('alt:///dev/ttyS0?class=PosixPollSerial') print(s) pyserial-3.5/serial/urlhandler/__init__.py0000644000175000017500000000000013641767344021210 0ustar chrischris00000000000000pyserial-3.5/serial/urlhandler/protocol_spy.py0000644000175000017500000002165213756630707022224 0ustar chrischris00000000000000#! python # # This module implements a special URL handler that wraps an other port, # print the traffic for debugging purposes. With this, it is possible # to debug the serial port traffic on every application that uses # serial_for_url. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause # # URL format: spy://port[?option[=value][&option[=value]]] # options: # - dev=X a file or device to write to # - color use escape code to colorize output # - raw forward raw bytes instead of hexdump # # example: # redirect output to an other terminal window on Posix (Linux): # python -m serial.tools.miniterm spy:///dev/ttyUSB0?dev=/dev/pts/14\&color from __future__ import absolute_import import sys import time import serial from serial.serialutil import to_bytes try: import urlparse except ImportError: import urllib.parse as urlparse def sixteen(data): """\ yield tuples of hex and ASCII display in multiples of 16. Includes a space after 8 bytes and (None, None) after 16 bytes and at the end. """ n = 0 for b in serial.iterbytes(data): yield ('{:02X} '.format(ord(b)), b.decode('ascii') if b' ' <= b < b'\x7f' else '.') n += 1 if n == 8: yield (' ', '') elif n >= 16: yield (None, None) n = 0 if n > 0: while n < 16: n += 1 if n == 8: yield (' ', '') yield (' ', ' ') yield (None, None) def hexdump(data): """yield lines with hexdump of data""" values = [] ascii = [] offset = 0 for h, a in sixteen(data): if h is None: yield (offset, ' '.join([''.join(values), ''.join(ascii)])) del values[:] del ascii[:] offset += 0x10 else: values.append(h) ascii.append(a) class FormatRaw(object): """Forward only RX and TX data to output.""" def __init__(self, output, color): self.output = output self.color = color self.rx_color = '\x1b[32m' self.tx_color = '\x1b[31m' def rx(self, data): """show received data""" if self.color: self.output.write(self.rx_color) self.output.write(data) self.output.flush() def tx(self, data): """show transmitted data""" if self.color: self.output.write(self.tx_color) self.output.write(data) self.output.flush() def control(self, name, value): """(do not) show control calls""" pass class FormatHexdump(object): """\ Create a hex dump of RX ad TX data, show when control lines are read or written. output example:: 000000.000 Q-RX flushInput 000002.469 RTS inactive 000002.773 RTS active 000003.001 TX 48 45 4C 4C 4F HELLO 000003.102 RX 48 45 4C 4C 4F HELLO """ def __init__(self, output, color): self.start_time = time.time() self.output = output self.color = color self.rx_color = '\x1b[32m' self.tx_color = '\x1b[31m' self.control_color = '\x1b[37m' def write_line(self, timestamp, label, value, value2=''): self.output.write('{:010.3f} {:4} {}{}\n'.format(timestamp, label, value, value2)) self.output.flush() def rx(self, data): """show received data as hex dump""" if self.color: self.output.write(self.rx_color) if data: for offset, row in hexdump(data): self.write_line(time.time() - self.start_time, 'RX', '{:04X} '.format(offset), row) else: self.write_line(time.time() - self.start_time, 'RX', '') def tx(self, data): """show transmitted data as hex dump""" if self.color: self.output.write(self.tx_color) for offset, row in hexdump(data): self.write_line(time.time() - self.start_time, 'TX', '{:04X} '.format(offset), row) def control(self, name, value): """show control calls""" if self.color: self.output.write(self.control_color) self.write_line(time.time() - self.start_time, name, value) class Serial(serial.Serial): """\ Inherit the native Serial port implementation and wrap all the methods and attributes. """ # pylint: disable=no-member def __init__(self, *args, **kwargs): super(Serial, self).__init__(*args, **kwargs) self.formatter = None self.show_all = False @serial.Serial.port.setter def port(self, value): if value is not None: serial.Serial.port.__set__(self, self.from_url(value)) def from_url(self, url): """extract host and port from an URL string""" parts = urlparse.urlsplit(url) if parts.scheme != 'spy': raise serial.SerialException( 'expected a string in the form ' '"spy://port[?option[=value][&option[=value]]]": ' 'not starting with spy:// ({!r})'.format(parts.scheme)) # process options now, directly altering self formatter = FormatHexdump color = False output = sys.stderr try: for option, values in urlparse.parse_qs(parts.query, True).items(): if option == 'file': output = open(values[0], 'w') elif option == 'color': color = True elif option == 'raw': formatter = FormatRaw elif option == 'all': self.show_all = True else: raise ValueError('unknown option: {!r}'.format(option)) except ValueError as e: raise serial.SerialException( 'expected a string in the form ' '"spy://port[?option[=value][&option[=value]]]": {}'.format(e)) self.formatter = formatter(output, color) return ''.join([parts.netloc, parts.path]) def write(self, tx): tx = to_bytes(tx) self.formatter.tx(tx) return super(Serial, self).write(tx) def read(self, size=1): rx = super(Serial, self).read(size) if rx or self.show_all: self.formatter.rx(rx) return rx if hasattr(serial.Serial, 'cancel_read'): def cancel_read(self): self.formatter.control('Q-RX', 'cancel_read') super(Serial, self).cancel_read() if hasattr(serial.Serial, 'cancel_write'): def cancel_write(self): self.formatter.control('Q-TX', 'cancel_write') super(Serial, self).cancel_write() @property def in_waiting(self): n = super(Serial, self).in_waiting if self.show_all: self.formatter.control('Q-RX', 'in_waiting -> {}'.format(n)) return n def flush(self): self.formatter.control('Q-TX', 'flush') super(Serial, self).flush() def reset_input_buffer(self): self.formatter.control('Q-RX', 'reset_input_buffer') super(Serial, self).reset_input_buffer() def reset_output_buffer(self): self.formatter.control('Q-TX', 'reset_output_buffer') super(Serial, self).reset_output_buffer() def send_break(self, duration=0.25): self.formatter.control('BRK', 'send_break {}s'.format(duration)) super(Serial, self).send_break(duration) @serial.Serial.break_condition.setter def break_condition(self, level): self.formatter.control('BRK', 'active' if level else 'inactive') serial.Serial.break_condition.__set__(self, level) @serial.Serial.rts.setter def rts(self, level): self.formatter.control('RTS', 'active' if level else 'inactive') serial.Serial.rts.__set__(self, level) @serial.Serial.dtr.setter def dtr(self, level): self.formatter.control('DTR', 'active' if level else 'inactive') serial.Serial.dtr.__set__(self, level) @serial.Serial.cts.getter def cts(self): level = super(Serial, self).cts self.formatter.control('CTS', 'active' if level else 'inactive') return level @serial.Serial.dsr.getter def dsr(self): level = super(Serial, self).dsr self.formatter.control('DSR', 'active' if level else 'inactive') return level @serial.Serial.ri.getter def ri(self): level = super(Serial, self).ri self.formatter.control('RI', 'active' if level else 'inactive') return level @serial.Serial.cd.getter def cd(self): level = super(Serial, self).cd self.formatter.control('CD', 'active' if level else 'inactive') return level # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if __name__ == '__main__': ser = Serial(None) ser.port = 'spy:///dev/ttyS0' print(ser) pyserial-3.5/serial/urlhandler/protocol_socket.py0000644000175000017500000003373313727523310022671 0ustar chrischris00000000000000#! python # # This module implements a simple socket based client. # It does not support changing any port parameters and will silently ignore any # requests to do so. # # The purpose of this module is that applications using pySerial can connect to # TCP/IP to serial port converters that do not support RFC 2217. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2001-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause # # URL format: socket://:[/option[/option...]] # options: # - "debug" print diagnostic messages from __future__ import absolute_import import errno import logging import select import socket import time try: import urlparse except ImportError: import urllib.parse as urlparse from serial.serialutil import SerialBase, SerialException, to_bytes, \ PortNotOpenError, SerialTimeoutException, Timeout # map log level names to constants. used in from_url() LOGGER_LEVELS = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, } POLL_TIMEOUT = 5 class Serial(SerialBase): """Serial port implementation for plain sockets.""" BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200) def open(self): """\ Open port with current settings. This may throw a SerialException if the port cannot be opened. """ self.logger = None if self._port is None: raise SerialException("Port must be configured before it can be used.") if self.is_open: raise SerialException("Port is already open.") try: # timeout is used for write timeout support :/ and to get an initial connection timeout self._socket = socket.create_connection(self.from_url(self.portstr), timeout=POLL_TIMEOUT) except Exception as msg: self._socket = None raise SerialException("Could not open port {}: {}".format(self.portstr, msg)) # after connecting, switch to non-blocking, we're using select self._socket.setblocking(False) # not that there is anything to configure... self._reconfigure_port() # all things set up get, now a clean start self.is_open = True if not self._dsrdtr: self._update_dtr_state() if not self._rtscts: self._update_rts_state() self.reset_input_buffer() self.reset_output_buffer() def _reconfigure_port(self): """\ Set communication parameters on opened port. For the socket:// protocol all settings are ignored! """ if self._socket is None: raise SerialException("Can only operate on open ports") if self.logger: self.logger.info('ignored port configuration change') def close(self): """Close port""" if self.is_open: if self._socket: try: self._socket.shutdown(socket.SHUT_RDWR) self._socket.close() except: # ignore errors. pass self._socket = None self.is_open = False # in case of quick reconnects, give the server some time time.sleep(0.3) def from_url(self, url): """extract host and port from an URL string""" parts = urlparse.urlsplit(url) if parts.scheme != "socket": raise SerialException( 'expected a string in the form ' '"socket://:[?logging={debug|info|warning|error}]": ' 'not starting with socket:// ({!r})'.format(parts.scheme)) try: # process options now, directly altering self for option, values in urlparse.parse_qs(parts.query, True).items(): if option == 'logging': logging.basicConfig() # XXX is that good to call it here? self.logger = logging.getLogger('pySerial.socket') self.logger.setLevel(LOGGER_LEVELS[values[0]]) self.logger.debug('enabled logging') else: raise ValueError('unknown option: {!r}'.format(option)) if not 0 <= parts.port < 65536: raise ValueError("port not in range 0...65535") except ValueError as e: raise SerialException( 'expected a string in the form ' '"socket://:[?logging={debug|info|warning|error}]": {}'.format(e)) return (parts.hostname, parts.port) # - - - - - - - - - - - - - - - - - - - - - - - - @property def in_waiting(self): """Return the number of bytes currently in the input buffer.""" if not self.is_open: raise PortNotOpenError() # Poll the socket to see if it is ready for reading. # If ready, at least one byte will be to read. lr, lw, lx = select.select([self._socket], [], [], 0) return len(lr) # select based implementation, similar to posix, but only using socket API # to be portable, additionally handle socket timeout which is used to # emulate write timeouts def read(self, size=1): """\ Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read. """ if not self.is_open: raise PortNotOpenError() read = bytearray() timeout = Timeout(self._timeout) while len(read) < size: try: ready, _, _ = select.select([self._socket], [], [], timeout.time_left()) # If select was used with a timeout, and the timeout occurs, it # returns with empty lists -> thus abort read operation. # For timeout == 0 (non-blocking operation) also abort when # there is nothing to read. if not ready: break # timeout buf = self._socket.recv(size - len(read)) # read should always return some data as select reported it was # ready to read when we get to this point, unless it is EOF if not buf: raise SerialException('socket disconnected') read.extend(buf) except OSError as e: # this is for Python 3.x where select.error is a subclass of # OSError ignore BlockingIOErrors and EINTR. other errors are shown # https://www.python.org/dev/peps/pep-0475. if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('read failed: {}'.format(e)) except (select.error, socket.error) as e: # this is for Python 2.x # ignore BlockingIOErrors and EINTR. all errors are shown # see also http://www.python.org/dev/peps/pep-3151/#select if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('read failed: {}'.format(e)) if timeout.expired(): break return bytes(read) def write(self, data): """\ Output the given byte string over the serial port. Can block if the connection is blocked. May raise SerialException if the connection is closed. """ if not self.is_open: raise PortNotOpenError() d = to_bytes(data) tx_len = length = len(d) timeout = Timeout(self._write_timeout) while tx_len > 0: try: n = self._socket.send(d) if timeout.is_non_blocking: # Zero timeout indicates non-blocking - simply return the # number of bytes of data actually written return n elif not timeout.is_infinite: # when timeout is set, use select to wait for being ready # with the time left as timeout if timeout.expired(): raise SerialTimeoutException('Write timeout') _, ready, _ = select.select([], [self._socket], [], timeout.time_left()) if not ready: raise SerialTimeoutException('Write timeout') else: assert timeout.time_left() is None # wait for write operation _, ready, _ = select.select([], [self._socket], [], None) if not ready: raise SerialException('write failed (select)') d = d[n:] tx_len -= n except SerialException: raise except OSError as e: # this is for Python 3.x where select.error is a subclass of # OSError ignore BlockingIOErrors and EINTR. other errors are shown # https://www.python.org/dev/peps/pep-0475. if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('write failed: {}'.format(e)) except select.error as e: # this is for Python 2.x # ignore BlockingIOErrors and EINTR. all errors are shown # see also http://www.python.org/dev/peps/pep-3151/#select if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('write failed: {}'.format(e)) if not timeout.is_non_blocking and timeout.expired(): raise SerialTimeoutException('Write timeout') return length - len(d) def reset_input_buffer(self): """Clear input buffer, discarding all that is in the buffer.""" if not self.is_open: raise PortNotOpenError() # just use recv to remove input, while there is some ready = True while ready: ready, _, _ = select.select([self._socket], [], [], 0) try: if ready: ready = self._socket.recv(4096) except OSError as e: # this is for Python 3.x where select.error is a subclass of # OSError ignore BlockingIOErrors and EINTR. other errors are shown # https://www.python.org/dev/peps/pep-0475. if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('read failed: {}'.format(e)) except (select.error, socket.error) as e: # this is for Python 2.x # ignore BlockingIOErrors and EINTR. all errors are shown # see also http://www.python.org/dev/peps/pep-3151/#select if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): raise SerialException('read failed: {}'.format(e)) def reset_output_buffer(self): """\ Clear output buffer, aborting the current output and discarding all that is in the buffer. """ if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('ignored reset_output_buffer') def send_break(self, duration=0.25): """\ Send break condition. Timed, returns to idle state after given duration. """ if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('ignored send_break({!r})'.format(duration)) def _update_break_state(self): """Set break: Controls TXD. When active, to transmitting is possible.""" if self.logger: self.logger.info('ignored _update_break_state({!r})'.format(self._break_state)) def _update_rts_state(self): """Set terminal status line: Request To Send""" if self.logger: self.logger.info('ignored _update_rts_state({!r})'.format(self._rts_state)) def _update_dtr_state(self): """Set terminal status line: Data Terminal Ready""" if self.logger: self.logger.info('ignored _update_dtr_state({!r})'.format(self._dtr_state)) @property def cts(self): """Read terminal status line: Clear To Send""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('returning dummy for cts') return True @property def dsr(self): """Read terminal status line: Data Set Ready""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('returning dummy for dsr') return True @property def ri(self): """Read terminal status line: Ring Indicator""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('returning dummy for ri') return False @property def cd(self): """Read terminal status line: Carrier Detect""" if not self.is_open: raise PortNotOpenError() if self.logger: self.logger.info('returning dummy for cd)') return True # - - - platform specific - - - # works on Linux and probably all the other POSIX systems def fileno(self): """Get the file handle of the underlying socket for use with select""" return self._socket.fileno() # # simple client test if __name__ == '__main__': import sys s = Serial('socket://localhost:7000') sys.stdout.write('{}\n'.format(s)) sys.stdout.write("write...\n") s.write(b"hello\n") s.flush() sys.stdout.write("read: {}\n".format(s.read(5))) s.close() pyserial-3.5/serial/urlhandler/protocol_hwgrep.py0000644000175000017500000000612713641767344022706 0ustar chrischris00000000000000#! python # # This module implements a special URL handler that uses the port listing to # find ports by searching the string descriptions. # # This file is part of pySerial. https://github.com/pyserial/pyserial # (C) 2011-2015 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause # # URL format: hwgrep://&