feat(core): 更新py-esptool (version: 4.8.1)
This commit is contained in:
@@ -255,11 +255,11 @@ Boards.changeTo = (boardName) => {
|
||||
let pyToolsPath = "{srcPath}/tools/python/";
|
||||
let obj = {};
|
||||
let pyTools = {
|
||||
'esptool': 'esptool/__init__.py',
|
||||
'esptool': 'esptool_main.py',
|
||||
'kflash': 'kflash.py',
|
||||
'stm32loader': 'stm32loader.py',
|
||||
'stm32bl': 'stm32bl.py',
|
||||
'ampy': 'ampy/cli.py'
|
||||
'ampy': 'ampy_main.py'
|
||||
};
|
||||
if (!Env.hasSocketServer) {
|
||||
for (let key in pyTools) {
|
||||
|
||||
@@ -36,7 +36,7 @@ class AmpyExt extends Ampy {
|
||||
run: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 run "{{&filePath}}"'
|
||||
}
|
||||
|
||||
this.AMPY_PATH = path.join(Env.srcDirPath, './tools/python/ampy/cli.py');
|
||||
this.AMPY_PATH = path.join(Env.srcDirPath, './tools/python/ampy_main.py');
|
||||
|
||||
this.AMPY_TEMPLATE = Mustache.render('"{{&python3}}" "{{&y}}"', {
|
||||
python3: Env.python3Path,
|
||||
|
||||
@@ -37,8 +37,8 @@ config = dotenv.find_dotenv(filename=".ampy", usecwd=True)
|
||||
if config:
|
||||
dotenv.load_dotenv(dotenv_path=config)
|
||||
|
||||
import files as files
|
||||
import pyboard as pyboard
|
||||
import ampy.files as files
|
||||
import ampy.pyboard as pyboard
|
||||
|
||||
|
||||
_board = None
|
||||
|
||||
@@ -23,7 +23,7 @@ import ast
|
||||
import textwrap
|
||||
import sys
|
||||
|
||||
from pyboard import PyboardError
|
||||
from ampy.pyboard import PyboardError
|
||||
|
||||
|
||||
BUFFER_SIZE = 32 # Amount of data to read or write to the serial port at a time.
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a wrapper module for different platform implementations
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
from serial.serialutil import *
|
||||
#~ SerialBase, SerialException, to_bytes, iterbytes
|
||||
|
||||
__version__ = '3.5'
|
||||
|
||||
VERSION = __version__
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
if sys.platform == 'cli':
|
||||
from serial.serialcli import Serial
|
||||
else:
|
||||
import os
|
||||
# chose an implementation, depending on os
|
||||
if os.name == 'nt': # sys.platform == 'win32':
|
||||
from serial.serialwin32 import Serial
|
||||
elif os.name == 'posix':
|
||||
from serial.serialposix import Serial, PosixPollSerial, VTIMESerial # noqa
|
||||
elif os.name == 'java':
|
||||
from serial.serialjava import Serial
|
||||
else:
|
||||
raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
|
||||
|
||||
|
||||
protocol_handler_packages = [
|
||||
'serial.urlhandler',
|
||||
]
|
||||
|
||||
|
||||
def serial_for_url(url, *args, **kwargs):
|
||||
"""\
|
||||
Get an instance of the Serial class, depending on port/url. The port is not
|
||||
opened when the keyword parameter 'do_not_open' is true, by default it
|
||||
is. All other parameters are directly passed to the __init__ method when
|
||||
the port is instantiated.
|
||||
|
||||
The list of package names that is searched for protocol handlers is kept in
|
||||
``protocol_handler_packages``.
|
||||
|
||||
e.g. we want to support a URL ``foobar://``. A module
|
||||
``my_handlers.protocol_foobar`` is provided by the user. Then
|
||||
``protocol_handler_packages.append("my_handlers")`` would extend the search
|
||||
path so that ``serial_for_url("foobar://"))`` would work.
|
||||
"""
|
||||
# check and remove extra parameter to not confuse the Serial class
|
||||
do_open = not kwargs.pop('do_not_open', False)
|
||||
# the default is to use the native implementation
|
||||
klass = Serial
|
||||
try:
|
||||
url_lowercase = url.lower()
|
||||
except AttributeError:
|
||||
# it's not a string, use default
|
||||
pass
|
||||
else:
|
||||
# if it is an URL, try to import the handler module from the list of possible packages
|
||||
if '://' in url_lowercase:
|
||||
protocol = url_lowercase.split('://', 1)[0]
|
||||
module_name = '.protocol_{}'.format(protocol)
|
||||
for package_name in protocol_handler_packages:
|
||||
try:
|
||||
importlib.import_module(package_name)
|
||||
handler_module = importlib.import_module(module_name, package_name)
|
||||
except ImportError:
|
||||
continue
|
||||
else:
|
||||
if hasattr(handler_module, 'serial_class_for_url'):
|
||||
url, klass = handler_module.serial_class_for_url(url)
|
||||
else:
|
||||
klass = handler_module.Serial
|
||||
break
|
||||
else:
|
||||
raise ValueError('invalid URL, protocol {!r} not known'.format(protocol))
|
||||
# instantiate and open when desired
|
||||
instance = klass(None, *args, **kwargs)
|
||||
instance.port = url
|
||||
if do_open:
|
||||
instance.open()
|
||||
return instance
|
||||
@@ -1,3 +0,0 @@
|
||||
from .tools import miniterm
|
||||
|
||||
miniterm.main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# RS485 support
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
The settings for RS485 are stored in a dedicated object that can be applied to
|
||||
serial ports (where supported).
|
||||
NOTE: Some implementations may only support a subset of the settings.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import time
|
||||
import serial
|
||||
|
||||
|
||||
class RS485Settings(object):
|
||||
def __init__(
|
||||
self,
|
||||
rts_level_for_tx=True,
|
||||
rts_level_for_rx=False,
|
||||
loopback=False,
|
||||
delay_before_tx=None,
|
||||
delay_before_rx=None):
|
||||
self.rts_level_for_tx = rts_level_for_tx
|
||||
self.rts_level_for_rx = rts_level_for_rx
|
||||
self.loopback = loopback
|
||||
self.delay_before_tx = delay_before_tx
|
||||
self.delay_before_rx = delay_before_rx
|
||||
|
||||
|
||||
class RS485(serial.Serial):
|
||||
"""\
|
||||
A subclass that replaces the write method with one that toggles RTS
|
||||
according to the RS485 settings.
|
||||
|
||||
NOTE: This may work unreliably on some serial ports (control signals not
|
||||
synchronized or delayed compared to data). Using delays may be
|
||||
unreliable (varying times, larger than expected) as the OS may not
|
||||
support very fine grained delays (no smaller than in the order of
|
||||
tens of milliseconds).
|
||||
|
||||
NOTE: Some implementations support this natively. Better performance
|
||||
can be expected when the native version is used.
|
||||
|
||||
NOTE: The loopback property is ignored by this implementation. The actual
|
||||
behavior depends on the used hardware.
|
||||
|
||||
Usage:
|
||||
|
||||
ser = RS485(...)
|
||||
ser.rs485_mode = RS485Settings(...)
|
||||
ser.write(b'hello')
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RS485, self).__init__(*args, **kwargs)
|
||||
self._alternate_rs485_settings = None
|
||||
|
||||
def write(self, b):
|
||||
"""Write to port, controlling RTS before and after transmitting."""
|
||||
if self._alternate_rs485_settings is not None:
|
||||
# apply level for TX and optional delay
|
||||
self.setRTS(self._alternate_rs485_settings.rts_level_for_tx)
|
||||
if self._alternate_rs485_settings.delay_before_tx is not None:
|
||||
time.sleep(self._alternate_rs485_settings.delay_before_tx)
|
||||
# write and wait for data to be written
|
||||
super(RS485, self).write(b)
|
||||
super(RS485, self).flush()
|
||||
# optional delay and apply level for RX
|
||||
if self._alternate_rs485_settings.delay_before_rx is not None:
|
||||
time.sleep(self._alternate_rs485_settings.delay_before_rx)
|
||||
self.setRTS(self._alternate_rs485_settings.rts_level_for_rx)
|
||||
else:
|
||||
super(RS485, self).write(b)
|
||||
|
||||
# redirect where the property stores the settings so that underlying Serial
|
||||
# instance does not see them
|
||||
@property
|
||||
def rs485_mode(self):
|
||||
"""\
|
||||
Enable RS485 mode and apply new settings, set to None to disable.
|
||||
See serial.rs485.RS485Settings for more info about the value.
|
||||
"""
|
||||
return self._alternate_rs485_settings
|
||||
|
||||
@rs485_mode.setter
|
||||
def rs485_mode(self, rs485_settings):
|
||||
self._alternate_rs485_settings = rs485_settings
|
||||
@@ -1,253 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Backend for .NET/Mono (IronPython), .NET >= 2
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2008-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import System
|
||||
import System.IO.Ports
|
||||
from serial.serialutil import *
|
||||
|
||||
# must invoke function with byte array, make a helper to convert strings
|
||||
# to byte arrays
|
||||
sab = System.Array[System.Byte]
|
||||
|
||||
|
||||
def as_byte_array(string):
|
||||
return sab([ord(x) for x in string]) # XXX will require adaption when run with a 3.x compatible IronPython
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation for .NET/Mono."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
try:
|
||||
self._port_handle = System.IO.Ports.SerialPort(self.portstr)
|
||||
except Exception as msg:
|
||||
self._port_handle = None
|
||||
raise SerialException("could not open port %s: %s" % (self.portstr, msg))
|
||||
|
||||
# if RTS and/or DTR are not set before open, they default to True
|
||||
if self._rts_state is None:
|
||||
self._rts_state = True
|
||||
if self._dtr_state is None:
|
||||
self._dtr_state = True
|
||||
|
||||
self._reconfigure_port()
|
||||
self._port_handle.Open()
|
||||
self.is_open = True
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
self.reset_input_buffer()
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self._port_handle:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
#~ self._port_handle.ReceivedBytesThreshold = 1
|
||||
|
||||
if self._timeout is None:
|
||||
self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
|
||||
else:
|
||||
self._port_handle.ReadTimeout = int(self._timeout * 1000)
|
||||
|
||||
# if self._timeout != 0 and self._interCharTimeout is not None:
|
||||
# timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:]
|
||||
|
||||
if self._write_timeout is None:
|
||||
self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
|
||||
else:
|
||||
self._port_handle.WriteTimeout = int(self._write_timeout * 1000)
|
||||
|
||||
# Setup the connection info.
|
||||
try:
|
||||
self._port_handle.BaudRate = self._baudrate
|
||||
except IOError as e:
|
||||
# catch errors from illegal baudrate settings
|
||||
raise ValueError(str(e))
|
||||
|
||||
if self._bytesize == FIVEBITS:
|
||||
self._port_handle.DataBits = 5
|
||||
elif self._bytesize == SIXBITS:
|
||||
self._port_handle.DataBits = 6
|
||||
elif self._bytesize == SEVENBITS:
|
||||
self._port_handle.DataBits = 7
|
||||
elif self._bytesize == EIGHTBITS:
|
||||
self._port_handle.DataBits = 8
|
||||
else:
|
||||
raise ValueError("Unsupported number of data bits: %r" % self._bytesize)
|
||||
|
||||
if self._parity == PARITY_NONE:
|
||||
self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k
|
||||
elif self._parity == PARITY_EVEN:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Even
|
||||
elif self._parity == PARITY_ODD:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Odd
|
||||
elif self._parity == PARITY_MARK:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Mark
|
||||
elif self._parity == PARITY_SPACE:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Space
|
||||
else:
|
||||
raise ValueError("Unsupported parity mode: %r" % self._parity)
|
||||
|
||||
if self._stopbits == STOPBITS_ONE:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.One
|
||||
elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive
|
||||
elif self._stopbits == STOPBITS_TWO:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.Two
|
||||
else:
|
||||
raise ValueError("Unsupported number of stop bits: %r" % self._stopbits)
|
||||
|
||||
if self._rtscts and self._xonxoff:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff
|
||||
elif self._rtscts:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend
|
||||
elif self._xonxoff:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff
|
||||
else:
|
||||
self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None') # reserved keyword in Py3k
|
||||
|
||||
#~ def __del__(self):
|
||||
#~ self.close()
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self._port_handle:
|
||||
try:
|
||||
self._port_handle.Close()
|
||||
except System.IO.Ports.InvalidOperationException:
|
||||
# ignore errors. can happen for unplugged USB serial devices
|
||||
pass
|
||||
self._port_handle = None
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of characters currently in the input buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self._port_handle.BytesToRead
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
# must use single byte reads as this is the only way to read
|
||||
# without applying encodings
|
||||
data = bytearray()
|
||||
while size:
|
||||
try:
|
||||
data.append(self._port_handle.ReadByte())
|
||||
except System.TimeoutException:
|
||||
break
|
||||
else:
|
||||
size -= 1
|
||||
return bytes(data)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given string over the serial port."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
#~ if not isinstance(data, (bytes, bytearray)):
|
||||
#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
try:
|
||||
# must call overloaded method with byte array argument
|
||||
# as this is the only one not applying encodings
|
||||
self._port_handle.Write(as_byte_array(data), 0, len(data))
|
||||
except System.TimeoutException:
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
return len(data)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.DiscardInBuffer()
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.DiscardOutBuffer()
|
||||
|
||||
def _update_break_state(self):
|
||||
"""
|
||||
Set break: Controls TXD. When active, to transmitting is possible.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.BreakState = bool(self._break_state)
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.RtsEnable = bool(self._rts_state)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
self._port_handle.DtrEnable = bool(self._dtr_state)
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self._port_handle.CtsHolding
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self._port_handle.DsrHolding
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
#~ return self._port_handle.XXX
|
||||
return False # XXX an error would be better
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
return self._port_handle.CDHolding
|
||||
|
||||
# - - platform specific - - - -
|
||||
# none
|
||||
@@ -1,251 +0,0 @@
|
||||
#!jython
|
||||
#
|
||||
# Backend Jython with JavaComm
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from serial.serialutil import *
|
||||
|
||||
|
||||
def my_import(name):
|
||||
mod = __import__(name)
|
||||
components = name.split('.')
|
||||
for comp in components[1:]:
|
||||
mod = getattr(mod, comp)
|
||||
return mod
|
||||
|
||||
|
||||
def detect_java_comm(names):
|
||||
"""try given list of modules and return that imports"""
|
||||
for name in names:
|
||||
try:
|
||||
mod = my_import(name)
|
||||
mod.SerialPort
|
||||
return mod
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
raise ImportError("No Java Communications API implementation found")
|
||||
|
||||
|
||||
# Java Communications API implementations
|
||||
# http://mho.republika.pl/java/comm/
|
||||
|
||||
comm = detect_java_comm([
|
||||
'javax.comm', # Sun/IBM
|
||||
'gnu.io', # RXTX
|
||||
])
|
||||
|
||||
|
||||
def device(portnumber):
|
||||
"""Turn a port number into a device name"""
|
||||
enum = comm.CommPortIdentifier.getPortIdentifiers()
|
||||
ports = []
|
||||
while enum.hasMoreElements():
|
||||
el = enum.nextElement()
|
||||
if el.getPortType() == comm.CommPortIdentifier.PORT_SERIAL:
|
||||
ports.append(el)
|
||||
return ports[portnumber].getName()
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""\
|
||||
Serial port class, implemented with Java Communications API and
|
||||
thus usable with jython and the appropriate java extension.
|
||||
"""
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
if type(self._port) == type(''): # strings are taken directly
|
||||
portId = comm.CommPortIdentifier.getPortIdentifier(self._port)
|
||||
else:
|
||||
portId = comm.CommPortIdentifier.getPortIdentifier(device(self._port)) # numbers are transformed to a comport id obj
|
||||
try:
|
||||
self.sPort = portId.open("python serial module", 10)
|
||||
except Exception as msg:
|
||||
self.sPort = None
|
||||
raise SerialException("Could not open port: %s" % msg)
|
||||
self._reconfigurePort()
|
||||
self._instream = self.sPort.getInputStream()
|
||||
self._outstream = self.sPort.getOutputStream()
|
||||
self.is_open = True
|
||||
|
||||
def _reconfigurePort(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self.sPort:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
self.sPort.enableReceiveTimeout(30)
|
||||
if self._bytesize == FIVEBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_5
|
||||
elif self._bytesize == SIXBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_6
|
||||
elif self._bytesize == SEVENBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_7
|
||||
elif self._bytesize == EIGHTBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_8
|
||||
else:
|
||||
raise ValueError("unsupported bytesize: %r" % self._bytesize)
|
||||
|
||||
if self._stopbits == STOPBITS_ONE:
|
||||
jstopbits = comm.SerialPort.STOPBITS_1
|
||||
elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
|
||||
jstopbits = comm.SerialPort.STOPBITS_1_5
|
||||
elif self._stopbits == STOPBITS_TWO:
|
||||
jstopbits = comm.SerialPort.STOPBITS_2
|
||||
else:
|
||||
raise ValueError("unsupported number of stopbits: %r" % self._stopbits)
|
||||
|
||||
if self._parity == PARITY_NONE:
|
||||
jparity = comm.SerialPort.PARITY_NONE
|
||||
elif self._parity == PARITY_EVEN:
|
||||
jparity = comm.SerialPort.PARITY_EVEN
|
||||
elif self._parity == PARITY_ODD:
|
||||
jparity = comm.SerialPort.PARITY_ODD
|
||||
elif self._parity == PARITY_MARK:
|
||||
jparity = comm.SerialPort.PARITY_MARK
|
||||
elif self._parity == PARITY_SPACE:
|
||||
jparity = comm.SerialPort.PARITY_SPACE
|
||||
else:
|
||||
raise ValueError("unsupported parity type: %r" % self._parity)
|
||||
|
||||
jflowin = jflowout = 0
|
||||
if self._rtscts:
|
||||
jflowin |= comm.SerialPort.FLOWCONTROL_RTSCTS_IN
|
||||
jflowout |= comm.SerialPort.FLOWCONTROL_RTSCTS_OUT
|
||||
if self._xonxoff:
|
||||
jflowin |= comm.SerialPort.FLOWCONTROL_XONXOFF_IN
|
||||
jflowout |= comm.SerialPort.FLOWCONTROL_XONXOFF_OUT
|
||||
|
||||
self.sPort.setSerialPortParams(self._baudrate, jdatabits, jstopbits, jparity)
|
||||
self.sPort.setFlowControlMode(jflowin | jflowout)
|
||||
|
||||
if self._timeout >= 0:
|
||||
self.sPort.enableReceiveTimeout(int(self._timeout*1000))
|
||||
else:
|
||||
self.sPort.disableReceiveTimeout()
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self.sPort:
|
||||
self._instream.close()
|
||||
self._outstream.close()
|
||||
self.sPort.close()
|
||||
self.sPort = None
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of characters currently in the input buffer."""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
return self._instream.available()
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
read = bytearray()
|
||||
if size > 0:
|
||||
while len(read) < size:
|
||||
x = self._instream.read()
|
||||
if x == -1:
|
||||
if self.timeout >= 0:
|
||||
break
|
||||
else:
|
||||
read.append(x)
|
||||
return bytes(read)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given string over the serial port."""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
if not isinstance(data, (bytes, bytearray)):
|
||||
raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
self._outstream.write(data)
|
||||
return len(data)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self._instream.skip(self._instream.available())
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self._outstream.flush()
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""Send break condition. Timed, returns to idle state after given duration."""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.sendBreak(duration*1000.0)
|
||||
|
||||
def _update_break_state(self):
|
||||
"""Set break: Controls TXD. When active, to transmitting is possible."""
|
||||
if self.fd is None:
|
||||
raise PortNotOpenError()
|
||||
raise SerialException("The _update_break_state function is not implemented in java.")
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.setRTS(self._rts_state)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.setDTR(self._dtr_state)
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.isCTS()
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.isDSR()
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.isRI()
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.sPort:
|
||||
raise PortNotOpenError()
|
||||
self.sPort.isCD()
|
||||
@@ -1,900 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# backend for serial IO for POSIX compatible systems, like Linux, OSX
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# parts based on code from Grant B. Edwards <grante@visi.com>:
|
||||
# ftp://ftp.visi.com/users/grante/python/PosixSerial.py
|
||||
#
|
||||
# references: http://www.easysw.com/~mike/serial/serial.html
|
||||
|
||||
# Collection of port names (was previously used by number_to_device which was
|
||||
# removed.
|
||||
# - Linux /dev/ttyS%d (confirmed)
|
||||
# - cygwin/win32 /dev/com%d (confirmed)
|
||||
# - openbsd (OpenBSD) /dev/cua%02d
|
||||
# - bsd*, freebsd* /dev/cuad%d
|
||||
# - darwin (OS X) /dev/cuad%d
|
||||
# - netbsd /dev/dty%02d (NetBSD 1.6 testing by Erk)
|
||||
# - irix (IRIX) /dev/ttyf%d (partially tested) names depending on flow control
|
||||
# - hp (HP-UX) /dev/tty%dp0 (not tested)
|
||||
# - sunos (Solaris/SunOS) /dev/tty%c (letters, 'a'..'z') (confirmed)
|
||||
# - aix (AIX) /dev/tty%d
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
import errno
|
||||
import fcntl
|
||||
import os
|
||||
import 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()
|
||||
@@ -1,697 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Base class and support functions used by various backends.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import io
|
||||
import time
|
||||
|
||||
# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)``
|
||||
# isn't returning the contents (very unfortunate). Therefore we need special
|
||||
# cases and test for it. Ensure that there is a ``memoryview`` object for older
|
||||
# Python versions. This is easier than making every test dependent on its
|
||||
# existence.
|
||||
try:
|
||||
memoryview
|
||||
except (NameError, AttributeError):
|
||||
# implementation does not matter as we do not really use it.
|
||||
# it just must not inherit from something else we might care for.
|
||||
class memoryview(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
pass
|
||||
|
||||
try:
|
||||
unicode
|
||||
except (NameError, AttributeError):
|
||||
unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name
|
||||
|
||||
try:
|
||||
basestring
|
||||
except (NameError, AttributeError):
|
||||
basestring = (str,) # for Python 3, pylint: disable=redefined-builtin,invalid-name
|
||||
|
||||
|
||||
# "for byte in data" fails for python3 as it returns ints instead of bytes
|
||||
def iterbytes(b):
|
||||
"""Iterate over bytes, returning bytes instead of ints (python3)"""
|
||||
if isinstance(b, memoryview):
|
||||
b = b.tobytes()
|
||||
i = 0
|
||||
while True:
|
||||
a = b[i:i + 1]
|
||||
i += 1
|
||||
if a:
|
||||
yield a
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11'
|
||||
# so a simple ``bytes(sequence)`` doesn't work for all versions
|
||||
def to_bytes(seq):
|
||||
"""convert a sequence to a bytes type"""
|
||||
if isinstance(seq, bytes):
|
||||
return seq
|
||||
elif isinstance(seq, bytearray):
|
||||
return bytes(seq)
|
||||
elif isinstance(seq, memoryview):
|
||||
return seq.tobytes()
|
||||
elif isinstance(seq, unicode):
|
||||
raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq))
|
||||
else:
|
||||
# handle list of integers and bytes (one or more items) for Python 2 and 3
|
||||
return bytes(bytearray(seq))
|
||||
|
||||
|
||||
# create control bytes
|
||||
XON = to_bytes([17])
|
||||
XOFF = to_bytes([19])
|
||||
|
||||
CR = to_bytes([13])
|
||||
LF = to_bytes([10])
|
||||
|
||||
|
||||
PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S'
|
||||
STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2)
|
||||
FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8)
|
||||
|
||||
PARITY_NAMES = {
|
||||
PARITY_NONE: 'None',
|
||||
PARITY_EVEN: 'Even',
|
||||
PARITY_ODD: 'Odd',
|
||||
PARITY_MARK: 'Mark',
|
||||
PARITY_SPACE: 'Space',
|
||||
}
|
||||
|
||||
|
||||
class SerialException(IOError):
|
||||
"""Base class for serial port related exceptions."""
|
||||
|
||||
|
||||
class SerialTimeoutException(SerialException):
|
||||
"""Write timeouts give an exception"""
|
||||
|
||||
|
||||
class PortNotOpenError(SerialException):
|
||||
"""Port is not open"""
|
||||
def __init__(self):
|
||||
super(PortNotOpenError, self).__init__('Attempting to use a port that is not open')
|
||||
|
||||
|
||||
class Timeout(object):
|
||||
"""\
|
||||
Abstraction for timeout operations. Using time.monotonic() if available
|
||||
or time.time() in all other cases.
|
||||
|
||||
The class can also be initialized with 0 or None, in order to support
|
||||
non-blocking and fully blocking I/O operations. The attributes
|
||||
is_non_blocking and is_infinite are set accordingly.
|
||||
"""
|
||||
if hasattr(time, 'monotonic'):
|
||||
# Timeout implementation with time.monotonic(). This function is only
|
||||
# supported by Python 3.3 and above. It returns a time in seconds
|
||||
# (float) just as time.time(), but is not affected by system clock
|
||||
# adjustments.
|
||||
TIME = time.monotonic
|
||||
else:
|
||||
# Timeout implementation with time.time(). This is compatible with all
|
||||
# Python versions but has issues if the clock is adjusted while the
|
||||
# timeout is running.
|
||||
TIME = time.time
|
||||
|
||||
def __init__(self, duration):
|
||||
"""Initialize a timeout with given duration"""
|
||||
self.is_infinite = (duration is None)
|
||||
self.is_non_blocking = (duration == 0)
|
||||
self.duration = duration
|
||||
if duration is not None:
|
||||
self.target_time = self.TIME() + duration
|
||||
else:
|
||||
self.target_time = None
|
||||
|
||||
def expired(self):
|
||||
"""Return a boolean, telling if the timeout has expired"""
|
||||
return self.target_time is not None and self.time_left() <= 0
|
||||
|
||||
def time_left(self):
|
||||
"""Return how many seconds are left until the timeout expires"""
|
||||
if self.is_non_blocking:
|
||||
return 0
|
||||
elif self.is_infinite:
|
||||
return None
|
||||
else:
|
||||
delta = self.target_time - self.TIME()
|
||||
if delta > self.duration:
|
||||
# clock jumped, recalculate
|
||||
self.target_time = self.TIME() + self.duration
|
||||
return self.duration
|
||||
else:
|
||||
return max(0, delta)
|
||||
|
||||
def restart(self, duration):
|
||||
"""\
|
||||
Restart a timeout, only supported if a timeout was already set up
|
||||
before.
|
||||
"""
|
||||
self.duration = duration
|
||||
self.target_time = self.TIME() + duration
|
||||
|
||||
|
||||
class SerialBase(io.RawIOBase):
|
||||
"""\
|
||||
Serial port base class. Provides __init__ function and properties to
|
||||
get/set port settings.
|
||||
"""
|
||||
|
||||
# default values, may be overridden in subclasses that do not support all values
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000,
|
||||
576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000,
|
||||
3000000, 3500000, 4000000)
|
||||
BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
|
||||
PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE)
|
||||
STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
|
||||
|
||||
def __init__(self,
|
||||
port=None,
|
||||
baudrate=9600,
|
||||
bytesize=EIGHTBITS,
|
||||
parity=PARITY_NONE,
|
||||
stopbits=STOPBITS_ONE,
|
||||
timeout=None,
|
||||
xonxoff=False,
|
||||
rtscts=False,
|
||||
write_timeout=None,
|
||||
dsrdtr=False,
|
||||
inter_byte_timeout=None,
|
||||
exclusive=None,
|
||||
**kwargs):
|
||||
"""\
|
||||
Initialize comm port object. If a "port" is given, then the port will be
|
||||
opened immediately. Otherwise a Serial port object in closed state
|
||||
is returned.
|
||||
"""
|
||||
|
||||
self.is_open = False
|
||||
self.portstr = None
|
||||
self.name = None
|
||||
# correct values are assigned below through properties
|
||||
self._port = None
|
||||
self._baudrate = None
|
||||
self._bytesize = None
|
||||
self._parity = None
|
||||
self._stopbits = None
|
||||
self._timeout = None
|
||||
self._write_timeout = None
|
||||
self._xonxoff = None
|
||||
self._rtscts = None
|
||||
self._dsrdtr = None
|
||||
self._inter_byte_timeout = None
|
||||
self._rs485_mode = None # disabled by default
|
||||
self._rts_state = True
|
||||
self._dtr_state = True
|
||||
self._break_state = False
|
||||
self._exclusive = None
|
||||
|
||||
# assign values using get/set methods using the properties feature
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.bytesize = bytesize
|
||||
self.parity = parity
|
||||
self.stopbits = stopbits
|
||||
self.timeout = timeout
|
||||
self.write_timeout = write_timeout
|
||||
self.xonxoff = xonxoff
|
||||
self.rtscts = rtscts
|
||||
self.dsrdtr = dsrdtr
|
||||
self.inter_byte_timeout = inter_byte_timeout
|
||||
self.exclusive = exclusive
|
||||
|
||||
# watch for backward compatible kwargs
|
||||
if 'writeTimeout' in kwargs:
|
||||
self.write_timeout = kwargs.pop('writeTimeout')
|
||||
if 'interCharTimeout' in kwargs:
|
||||
self.inter_byte_timeout = kwargs.pop('interCharTimeout')
|
||||
if kwargs:
|
||||
raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs))
|
||||
|
||||
if port is not None:
|
||||
self.open()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# to be implemented by subclasses:
|
||||
# def open(self):
|
||||
# def close(self):
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
"""\
|
||||
Get the current port setting. The value that was passed on init or using
|
||||
setPort() is passed back.
|
||||
"""
|
||||
return self._port
|
||||
|
||||
@port.setter
|
||||
def port(self, port):
|
||||
"""\
|
||||
Change the port.
|
||||
"""
|
||||
if port is not None and not isinstance(port, basestring):
|
||||
raise ValueError('"port" must be None or a string, not {}'.format(type(port)))
|
||||
was_open = self.is_open
|
||||
if was_open:
|
||||
self.close()
|
||||
self.portstr = port
|
||||
self._port = port
|
||||
self.name = self.portstr
|
||||
if was_open:
|
||||
self.open()
|
||||
|
||||
@property
|
||||
def baudrate(self):
|
||||
"""Get the current baud rate setting."""
|
||||
return self._baudrate
|
||||
|
||||
@baudrate.setter
|
||||
def baudrate(self, baudrate):
|
||||
"""\
|
||||
Change baud rate. It raises a ValueError if the port is open and the
|
||||
baud rate is not possible. If the port is closed, then the value is
|
||||
accepted and the exception is raised when the port is opened.
|
||||
"""
|
||||
try:
|
||||
b = int(baudrate)
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
|
||||
else:
|
||||
if b < 0:
|
||||
raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
|
||||
self._baudrate = b
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def bytesize(self):
|
||||
"""Get the current byte size setting."""
|
||||
return self._bytesize
|
||||
|
||||
@bytesize.setter
|
||||
def bytesize(self, bytesize):
|
||||
"""Change byte size."""
|
||||
if bytesize not in self.BYTESIZES:
|
||||
raise ValueError("Not a valid byte size: {!r}".format(bytesize))
|
||||
self._bytesize = bytesize
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def exclusive(self):
|
||||
"""Get the current exclusive access setting."""
|
||||
return self._exclusive
|
||||
|
||||
@exclusive.setter
|
||||
def exclusive(self, exclusive):
|
||||
"""Change the exclusive access setting."""
|
||||
self._exclusive = exclusive
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def parity(self):
|
||||
"""Get the current parity setting."""
|
||||
return self._parity
|
||||
|
||||
@parity.setter
|
||||
def parity(self, parity):
|
||||
"""Change parity setting."""
|
||||
if parity not in self.PARITIES:
|
||||
raise ValueError("Not a valid parity: {!r}".format(parity))
|
||||
self._parity = parity
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def stopbits(self):
|
||||
"""Get the current stop bits setting."""
|
||||
return self._stopbits
|
||||
|
||||
@stopbits.setter
|
||||
def stopbits(self, stopbits):
|
||||
"""Change stop bits size."""
|
||||
if stopbits not in self.STOPBITS:
|
||||
raise ValueError("Not a valid stop bit size: {!r}".format(stopbits))
|
||||
self._stopbits = stopbits
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""Get the current timeout setting."""
|
||||
return self._timeout
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, timeout):
|
||||
"""Change timeout setting."""
|
||||
if timeout is not None:
|
||||
try:
|
||||
timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
if timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
self._timeout = timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def write_timeout(self):
|
||||
"""Get the current timeout setting."""
|
||||
return self._write_timeout
|
||||
|
||||
@write_timeout.setter
|
||||
def write_timeout(self, timeout):
|
||||
"""Change timeout setting."""
|
||||
if timeout is not None:
|
||||
if timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
try:
|
||||
timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
|
||||
self._write_timeout = timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def inter_byte_timeout(self):
|
||||
"""Get the current inter-character timeout setting."""
|
||||
return self._inter_byte_timeout
|
||||
|
||||
@inter_byte_timeout.setter
|
||||
def inter_byte_timeout(self, ic_timeout):
|
||||
"""Change inter-byte timeout setting."""
|
||||
if ic_timeout is not None:
|
||||
if ic_timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
|
||||
try:
|
||||
ic_timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
|
||||
|
||||
self._inter_byte_timeout = ic_timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def xonxoff(self):
|
||||
"""Get the current XON/XOFF setting."""
|
||||
return self._xonxoff
|
||||
|
||||
@xonxoff.setter
|
||||
def xonxoff(self, xonxoff):
|
||||
"""Change XON/XOFF setting."""
|
||||
self._xonxoff = xonxoff
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def rtscts(self):
|
||||
"""Get the current RTS/CTS flow control setting."""
|
||||
return self._rtscts
|
||||
|
||||
@rtscts.setter
|
||||
def rtscts(self, rtscts):
|
||||
"""Change RTS/CTS flow control setting."""
|
||||
self._rtscts = rtscts
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def dsrdtr(self):
|
||||
"""Get the current DSR/DTR flow control setting."""
|
||||
return self._dsrdtr
|
||||
|
||||
@dsrdtr.setter
|
||||
def dsrdtr(self, dsrdtr=None):
|
||||
"""Change DsrDtr flow control setting."""
|
||||
if dsrdtr is None:
|
||||
# if not set, keep backwards compatibility and follow rtscts setting
|
||||
self._dsrdtr = self._rtscts
|
||||
else:
|
||||
# if defined independently, follow its value
|
||||
self._dsrdtr = dsrdtr
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def rts(self):
|
||||
return self._rts_state
|
||||
|
||||
@rts.setter
|
||||
def rts(self, value):
|
||||
self._rts_state = value
|
||||
if self.is_open:
|
||||
self._update_rts_state()
|
||||
|
||||
@property
|
||||
def dtr(self):
|
||||
return self._dtr_state
|
||||
|
||||
@dtr.setter
|
||||
def dtr(self, value):
|
||||
self._dtr_state = value
|
||||
if self.is_open:
|
||||
self._update_dtr_state()
|
||||
|
||||
@property
|
||||
def break_condition(self):
|
||||
return self._break_state
|
||||
|
||||
@break_condition.setter
|
||||
def break_condition(self, value):
|
||||
self._break_state = value
|
||||
if self.is_open:
|
||||
self._update_break_state()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# functions useful for RS-485 adapters
|
||||
|
||||
@property
|
||||
def rs485_mode(self):
|
||||
"""\
|
||||
Enable RS485 mode and apply new settings, set to None to disable.
|
||||
See serial.rs485.RS485Settings for more info about the value.
|
||||
"""
|
||||
return self._rs485_mode
|
||||
|
||||
@rs485_mode.setter
|
||||
def rs485_mode(self, rs485_settings):
|
||||
self._rs485_mode = rs485_settings
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
_SAVED_SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff',
|
||||
'dsrdtr', 'rtscts', 'timeout', 'write_timeout',
|
||||
'inter_byte_timeout')
|
||||
|
||||
def get_settings(self):
|
||||
"""\
|
||||
Get current port settings as a dictionary. For use with
|
||||
apply_settings().
|
||||
"""
|
||||
return dict([(key, getattr(self, '_' + key)) for key in self._SAVED_SETTINGS])
|
||||
|
||||
def apply_settings(self, d):
|
||||
"""\
|
||||
Apply stored settings from a dictionary returned from
|
||||
get_settings(). It's allowed to delete keys from the dictionary. These
|
||||
values will simply left unchanged.
|
||||
"""
|
||||
for key in self._SAVED_SETTINGS:
|
||||
if key in d and d[key] != getattr(self, '_' + key): # check against internal "_" value
|
||||
setattr(self, key, d[key]) # set non "_" value to use properties write function
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of the current port settings and its state."""
|
||||
return '{name}<id=0x{id:x}, open={p.is_open}>(port={p.portstr!r}, ' \
|
||||
'baudrate={p.baudrate!r}, bytesize={p.bytesize!r}, parity={p.parity!r}, ' \
|
||||
'stopbits={p.stopbits!r}, timeout={p.timeout!r}, xonxoff={p.xonxoff!r}, ' \
|
||||
'rtscts={p.rtscts!r}, dsrdtr={p.dsrdtr!r})'.format(
|
||||
name=self.__class__.__name__, id=id(self), p=self)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# compatibility with io library
|
||||
# pylint: disable=invalid-name,missing-docstring
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def seekable(self):
|
||||
return False
|
||||
|
||||
def readinto(self, b):
|
||||
data = self.read(len(b))
|
||||
n = len(data)
|
||||
try:
|
||||
b[:n] = data
|
||||
except TypeError as err:
|
||||
import array
|
||||
if not isinstance(b, array.array):
|
||||
raise err
|
||||
b[:n] = array.array('b', data)
|
||||
return n
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# 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))
|
||||
@@ -1,477 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# backend for Windows ("win32" incl. 32/64 bit support)
|
||||
#
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# Initial patch to use ctypes by Giovanni Bajo <rasky@develer.com>
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# pylint: disable=invalid-name,too-few-public-methods
|
||||
import ctypes
|
||||
import time
|
||||
from serial import win32
|
||||
|
||||
import serial
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, PortNotOpenError, SerialTimeoutException
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation for Win32 based on ctypes."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._port_handle = None
|
||||
self._overlapped_read = None
|
||||
self._overlapped_write = None
|
||||
super(Serial, self).__init__(*args, **kwargs)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
# the "\\.\COMx" format is required for devices other than COM1-COM8
|
||||
# not all versions of windows seem to support this properly
|
||||
# so that the first few ports are used with the DOS device name
|
||||
port = self.name
|
||||
try:
|
||||
if port.upper().startswith('COM') and int(port[3:]) > 8:
|
||||
port = '\\\\.\\' + port
|
||||
except ValueError:
|
||||
# for like COMnotanumber
|
||||
pass
|
||||
self._port_handle = win32.CreateFile(
|
||||
port,
|
||||
win32.GENERIC_READ | win32.GENERIC_WRITE,
|
||||
0, # exclusive access
|
||||
None, # no security
|
||||
win32.OPEN_EXISTING,
|
||||
win32.FILE_ATTRIBUTE_NORMAL | win32.FILE_FLAG_OVERLAPPED,
|
||||
0)
|
||||
if self._port_handle == win32.INVALID_HANDLE_VALUE:
|
||||
self._port_handle = None # 'cause __del__ is called anyway
|
||||
raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError()))
|
||||
|
||||
try:
|
||||
self._overlapped_read = win32.OVERLAPPED()
|
||||
self._overlapped_read.hEvent = win32.CreateEvent(None, 1, 0, None)
|
||||
self._overlapped_write = win32.OVERLAPPED()
|
||||
#~ self._overlapped_write.hEvent = win32.CreateEvent(None, 1, 0, None)
|
||||
self._overlapped_write.hEvent = win32.CreateEvent(None, 0, 0, None)
|
||||
|
||||
# Setup a 4k buffer
|
||||
win32.SetupComm(self._port_handle, 4096, 4096)
|
||||
|
||||
# Save original timeout values:
|
||||
self._orgTimeouts = win32.COMMTIMEOUTS()
|
||||
win32.GetCommTimeouts(self._port_handle, ctypes.byref(self._orgTimeouts))
|
||||
|
||||
self._reconfigure_port()
|
||||
|
||||
# Clear buffers:
|
||||
# Remove anything that was there
|
||||
win32.PurgeComm(
|
||||
self._port_handle,
|
||||
win32.PURGE_TXCLEAR | win32.PURGE_TXABORT |
|
||||
win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
|
||||
except:
|
||||
try:
|
||||
self._close()
|
||||
except:
|
||||
# ignore any exception when closing the port
|
||||
# also to keep original exception that happened when setting up
|
||||
pass
|
||||
self._port_handle = None
|
||||
raise
|
||||
else:
|
||||
self.is_open = True
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self._port_handle:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
# Set Windows timeout values
|
||||
# timeouts is a tuple with the following items:
|
||||
# (ReadIntervalTimeout,ReadTotalTimeoutMultiplier,
|
||||
# ReadTotalTimeoutConstant,WriteTotalTimeoutMultiplier,
|
||||
# WriteTotalTimeoutConstant)
|
||||
timeouts = win32.COMMTIMEOUTS()
|
||||
if self._timeout is None:
|
||||
pass # default of all zeros is OK
|
||||
elif self._timeout == 0:
|
||||
timeouts.ReadIntervalTimeout = win32.MAXDWORD
|
||||
else:
|
||||
timeouts.ReadTotalTimeoutConstant = max(int(self._timeout * 1000), 1)
|
||||
if self._timeout != 0 and self._inter_byte_timeout is not None:
|
||||
timeouts.ReadIntervalTimeout = max(int(self._inter_byte_timeout * 1000), 1)
|
||||
|
||||
if self._write_timeout is None:
|
||||
pass
|
||||
elif self._write_timeout == 0:
|
||||
timeouts.WriteTotalTimeoutConstant = win32.MAXDWORD
|
||||
else:
|
||||
timeouts.WriteTotalTimeoutConstant = max(int(self._write_timeout * 1000), 1)
|
||||
win32.SetCommTimeouts(self._port_handle, ctypes.byref(timeouts))
|
||||
|
||||
win32.SetCommMask(self._port_handle, win32.EV_ERR)
|
||||
|
||||
# Setup the connection info.
|
||||
# Get state and modify it:
|
||||
comDCB = win32.DCB()
|
||||
win32.GetCommState(self._port_handle, ctypes.byref(comDCB))
|
||||
comDCB.BaudRate = self._baudrate
|
||||
|
||||
if self._bytesize == serial.FIVEBITS:
|
||||
comDCB.ByteSize = 5
|
||||
elif self._bytesize == serial.SIXBITS:
|
||||
comDCB.ByteSize = 6
|
||||
elif self._bytesize == serial.SEVENBITS:
|
||||
comDCB.ByteSize = 7
|
||||
elif self._bytesize == serial.EIGHTBITS:
|
||||
comDCB.ByteSize = 8
|
||||
else:
|
||||
raise ValueError("Unsupported number of data bits: {!r}".format(self._bytesize))
|
||||
|
||||
if self._parity == serial.PARITY_NONE:
|
||||
comDCB.Parity = win32.NOPARITY
|
||||
comDCB.fParity = 0 # Disable Parity Check
|
||||
elif self._parity == serial.PARITY_EVEN:
|
||||
comDCB.Parity = win32.EVENPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_ODD:
|
||||
comDCB.Parity = win32.ODDPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_MARK:
|
||||
comDCB.Parity = win32.MARKPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_SPACE:
|
||||
comDCB.Parity = win32.SPACEPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
else:
|
||||
raise ValueError("Unsupported parity mode: {!r}".format(self._parity))
|
||||
|
||||
if self._stopbits == serial.STOPBITS_ONE:
|
||||
comDCB.StopBits = win32.ONESTOPBIT
|
||||
elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
|
||||
comDCB.StopBits = win32.ONE5STOPBITS
|
||||
elif self._stopbits == serial.STOPBITS_TWO:
|
||||
comDCB.StopBits = win32.TWOSTOPBITS
|
||||
else:
|
||||
raise ValueError("Unsupported number of stop bits: {!r}".format(self._stopbits))
|
||||
|
||||
comDCB.fBinary = 1 # Enable Binary Transmission
|
||||
# Char. w/ Parity-Err are replaced with 0xff (if fErrorChar is set to TRUE)
|
||||
if self._rs485_mode is None:
|
||||
if self._rtscts:
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_HANDSHAKE
|
||||
else:
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_ENABLE if self._rts_state else win32.RTS_CONTROL_DISABLE
|
||||
comDCB.fOutxCtsFlow = self._rtscts
|
||||
else:
|
||||
# checks for unsupported settings
|
||||
# XXX verify if platform really does not have a setting for those
|
||||
if not self._rs485_mode.rts_level_for_tx:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.rts_level_for_tx: {!r} (only True is allowed)'.format(
|
||||
self._rs485_mode.rts_level_for_tx,))
|
||||
if self._rs485_mode.rts_level_for_rx:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.rts_level_for_rx: {!r} (only False is allowed)'.format(
|
||||
self._rs485_mode.rts_level_for_rx,))
|
||||
if self._rs485_mode.delay_before_tx is not None:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.delay_before_tx: {!r} (only None is allowed)'.format(
|
||||
self._rs485_mode.delay_before_tx,))
|
||||
if self._rs485_mode.delay_before_rx is not None:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.delay_before_rx: {!r} (only None is allowed)'.format(
|
||||
self._rs485_mode.delay_before_rx,))
|
||||
if self._rs485_mode.loopback:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.loopback: {!r} (only False is allowed)'.format(
|
||||
self._rs485_mode.loopback,))
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_TOGGLE
|
||||
comDCB.fOutxCtsFlow = 0
|
||||
|
||||
if self._dsrdtr:
|
||||
comDCB.fDtrControl = win32.DTR_CONTROL_HANDSHAKE
|
||||
else:
|
||||
comDCB.fDtrControl = win32.DTR_CONTROL_ENABLE if self._dtr_state else win32.DTR_CONTROL_DISABLE
|
||||
comDCB.fOutxDsrFlow = self._dsrdtr
|
||||
comDCB.fOutX = self._xonxoff
|
||||
comDCB.fInX = self._xonxoff
|
||||
comDCB.fNull = 0
|
||||
comDCB.fErrorChar = 0
|
||||
comDCB.fAbortOnError = 0
|
||||
comDCB.XonChar = serial.XON
|
||||
comDCB.XoffChar = serial.XOFF
|
||||
|
||||
if not win32.SetCommState(self._port_handle, ctypes.byref(comDCB)):
|
||||
raise SerialException(
|
||||
'Cannot configure port, something went wrong. '
|
||||
'Original message: {!r}'.format(ctypes.WinError()))
|
||||
|
||||
#~ def __del__(self):
|
||||
#~ self.close()
|
||||
|
||||
def _close(self):
|
||||
"""internal close port helper"""
|
||||
if self._port_handle is not None:
|
||||
# Restore original timeout values:
|
||||
win32.SetCommTimeouts(self._port_handle, self._orgTimeouts)
|
||||
if self._overlapped_read is not None:
|
||||
self.cancel_read()
|
||||
win32.CloseHandle(self._overlapped_read.hEvent)
|
||||
self._overlapped_read = None
|
||||
if self._overlapped_write is not None:
|
||||
self.cancel_write()
|
||||
win32.CloseHandle(self._overlapped_write.hEvent)
|
||||
self._overlapped_write = None
|
||||
win32.CloseHandle(self._port_handle)
|
||||
self._port_handle = None
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
self._close()
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
return comstat.cbInQue
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if size > 0:
|
||||
win32.ResetEvent(self._overlapped_read.hEvent)
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
n = min(comstat.cbInQue, size) if self.timeout == 0 else size
|
||||
if n > 0:
|
||||
buf = ctypes.create_string_buffer(n)
|
||||
rc = win32.DWORD()
|
||||
read_ok = win32.ReadFile(
|
||||
self._port_handle,
|
||||
buf,
|
||||
n,
|
||||
ctypes.byref(rc),
|
||||
ctypes.byref(self._overlapped_read))
|
||||
if not read_ok and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
raise SerialException("ReadFile failed ({!r})".format(ctypes.WinError()))
|
||||
result_ok = win32.GetOverlappedResult(
|
||||
self._port_handle,
|
||||
ctypes.byref(self._overlapped_read),
|
||||
ctypes.byref(rc),
|
||||
True)
|
||||
if not result_ok:
|
||||
if win32.GetLastError() != win32.ERROR_OPERATION_ABORTED:
|
||||
raise SerialException("GetOverlappedResult failed ({!r})".format(ctypes.WinError()))
|
||||
read = buf.raw[:rc.value]
|
||||
else:
|
||||
read = bytes()
|
||||
else:
|
||||
read = bytes()
|
||||
return bytes(read)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given byte string over the serial port."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
#~ if not isinstance(data, (bytes, bytearray)):
|
||||
#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
# convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview
|
||||
data = to_bytes(data)
|
||||
if data:
|
||||
#~ win32event.ResetEvent(self._overlapped_write.hEvent)
|
||||
n = win32.DWORD()
|
||||
success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write)
|
||||
if self._write_timeout != 0: # if blocking (None) or w/ write timeout (>0)
|
||||
if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))
|
||||
|
||||
# Wait for the write to complete.
|
||||
#~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE)
|
||||
win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True)
|
||||
if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED:
|
||||
return n.value # canceled IO is no error
|
||||
if n.value != len(data):
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
return n.value
|
||||
else:
|
||||
errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError()
|
||||
if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY,
|
||||
win32.ERROR_OPERATION_ABORTED):
|
||||
return 0
|
||||
elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
# no info on true length provided by OS function in async mode
|
||||
return len(data)
|
||||
else:
|
||||
raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))
|
||||
else:
|
||||
return 0
|
||||
|
||||
def flush(self):
|
||||
"""\
|
||||
Flush of file like objects. In this case, wait until all data
|
||||
is written.
|
||||
"""
|
||||
while self.out_waiting:
|
||||
time.sleep(0.05)
|
||||
# XXX could also use WaitCommEvent with mask EV_TXEMPTY, but it would
|
||||
# require overlapped IO and it's also only possible to set a single mask
|
||||
# on the port---
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
win32.PurgeComm(self._port_handle, win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and discarding all
|
||||
that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
win32.PurgeComm(self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT)
|
||||
|
||||
def _update_break_state(self):
|
||||
"""Set break: Controls TXD. When active, to transmitting is possible."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self._break_state:
|
||||
win32.SetCommBreak(self._port_handle)
|
||||
else:
|
||||
win32.ClearCommBreak(self._port_handle)
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self._rts_state:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETRTS)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.CLRRTS)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self._dtr_state:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETDTR)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.CLRDTR)
|
||||
|
||||
def _GetCommModemStatus(self):
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
stat = win32.DWORD()
|
||||
win32.GetCommModemStatus(self._port_handle, ctypes.byref(stat))
|
||||
return stat.value
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
return win32.MS_CTS_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
return win32.MS_DSR_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
return win32.MS_RING_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
return win32.MS_RLSD_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
# - - platform specific - - - -
|
||||
|
||||
def set_buffer_size(self, rx_size=4096, tx_size=None):
|
||||
"""\
|
||||
Recommend a buffer size to the driver (device driver can ignore this
|
||||
value). Must be called after the port is opened.
|
||||
"""
|
||||
if tx_size is None:
|
||||
tx_size = rx_size
|
||||
win32.SetupComm(self._port_handle, rx_size, tx_size)
|
||||
|
||||
def set_output_flow_control(self, enable=True):
|
||||
"""\
|
||||
Manually control flow - when software flow control is enabled.
|
||||
This will do the same as if XON (true) or XOFF (false) are received
|
||||
from the other device and control the transmission accordingly.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if enable:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETXON)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETXOFF)
|
||||
|
||||
@property
|
||||
def out_waiting(self):
|
||||
"""Return how many bytes the in the outgoing buffer"""
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
return comstat.cbOutQue
|
||||
|
||||
def _cancel_overlapped_io(self, overlapped):
|
||||
"""Cancel a blocking read operation, may be called from other thread"""
|
||||
# check if read operation is pending
|
||||
rc = win32.DWORD()
|
||||
err = win32.GetOverlappedResult(
|
||||
self._port_handle,
|
||||
ctypes.byref(overlapped),
|
||||
ctypes.byref(rc),
|
||||
False)
|
||||
if not err and win32.GetLastError() in (win32.ERROR_IO_PENDING, win32.ERROR_IO_INCOMPLETE):
|
||||
# cancel, ignoring any errors (e.g. it may just have finished on its own)
|
||||
win32.CancelIoEx(self._port_handle, overlapped)
|
||||
|
||||
def cancel_read(self):
|
||||
"""Cancel a blocking read operation, may be called from other thread"""
|
||||
self._cancel_overlapped_io(self._overlapped_read)
|
||||
|
||||
def cancel_write(self):
|
||||
"""Cancel a blocking write operation, may be called from other thread"""
|
||||
self._cancel_overlapped_io(self._overlapped_write)
|
||||
|
||||
@SerialBase.exclusive.setter
|
||||
def exclusive(self, exclusive):
|
||||
"""Change the exclusive access setting."""
|
||||
if exclusive is not None and not exclusive:
|
||||
raise ValueError('win32 only supports exclusive access (not: {})'.format(exclusive))
|
||||
else:
|
||||
serial.SerialBase.exclusive.__set__(self, exclusive)
|
||||
@@ -1,297 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Working with threading and pySerial
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Support threading with serial ports.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import serial
|
||||
import threading
|
||||
|
||||
|
||||
class Protocol(object):
|
||||
"""\
|
||||
Protocol as used by the ReaderThread. This base class provides empty
|
||||
implementations of all methods.
|
||||
"""
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Called when reader thread is started"""
|
||||
|
||||
def data_received(self, data):
|
||||
"""Called with snippets received from the serial port"""
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""\
|
||||
Called when the serial port is closed or the reader loop terminated
|
||||
otherwise.
|
||||
"""
|
||||
if isinstance(exc, Exception):
|
||||
raise exc
|
||||
|
||||
|
||||
class Packetizer(Protocol):
|
||||
"""
|
||||
Read binary packets from serial port. Packets are expected to be terminated
|
||||
with a TERMINATOR byte (null byte by default).
|
||||
|
||||
The class also keeps track of the transport.
|
||||
"""
|
||||
|
||||
TERMINATOR = b'\0'
|
||||
|
||||
def __init__(self):
|
||||
self.buffer = bytearray()
|
||||
self.transport = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Store transport"""
|
||||
self.transport = transport
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""Forget transport"""
|
||||
self.transport = None
|
||||
super(Packetizer, self).connection_lost(exc)
|
||||
|
||||
def data_received(self, data):
|
||||
"""Buffer received data, find TERMINATOR, call handle_packet"""
|
||||
self.buffer.extend(data)
|
||||
while self.TERMINATOR in self.buffer:
|
||||
packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
|
||||
self.handle_packet(packet)
|
||||
|
||||
def handle_packet(self, packet):
|
||||
"""Process packets - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_packet')
|
||||
|
||||
|
||||
class FramedPacket(Protocol):
|
||||
"""
|
||||
Read binary packets. Packets are expected to have a start and stop marker.
|
||||
|
||||
The class also keeps track of the transport.
|
||||
"""
|
||||
|
||||
START = b'('
|
||||
STOP = b')'
|
||||
|
||||
def __init__(self):
|
||||
self.packet = bytearray()
|
||||
self.in_packet = False
|
||||
self.transport = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Store transport"""
|
||||
self.transport = transport
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""Forget transport"""
|
||||
self.transport = None
|
||||
self.in_packet = False
|
||||
del self.packet[:]
|
||||
super(FramedPacket, self).connection_lost(exc)
|
||||
|
||||
def data_received(self, data):
|
||||
"""Find data enclosed in START/STOP, call handle_packet"""
|
||||
for byte in serial.iterbytes(data):
|
||||
if byte == self.START:
|
||||
self.in_packet = True
|
||||
elif byte == self.STOP:
|
||||
self.in_packet = False
|
||||
self.handle_packet(bytes(self.packet)) # make read-only copy
|
||||
del self.packet[:]
|
||||
elif self.in_packet:
|
||||
self.packet.extend(byte)
|
||||
else:
|
||||
self.handle_out_of_packet_data(byte)
|
||||
|
||||
def handle_packet(self, packet):
|
||||
"""Process packets - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_packet')
|
||||
|
||||
def handle_out_of_packet_data(self, data):
|
||||
"""Process data that is received outside of packets"""
|
||||
pass
|
||||
|
||||
|
||||
class LineReader(Packetizer):
|
||||
"""
|
||||
Read and write (Unicode) lines from/to serial port.
|
||||
The encoding is applied.
|
||||
"""
|
||||
|
||||
TERMINATOR = b'\r\n'
|
||||
ENCODING = 'utf-8'
|
||||
UNICODE_HANDLING = 'replace'
|
||||
|
||||
def handle_packet(self, packet):
|
||||
self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING))
|
||||
|
||||
def handle_line(self, line):
|
||||
"""Process one line - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_line')
|
||||
|
||||
def write_line(self, text):
|
||||
"""
|
||||
Write text to the transport. ``text`` is a Unicode string and the encoding
|
||||
is applied before sending 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()
|
||||
@@ -1,126 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This is a codec to create and decode hexdumps with spaces between characters. used by miniterm.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Python 'hex' Codec - 2-digit hex with spaces content transfer encoding.
|
||||
|
||||
Encode and decode may be a bit 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,
|
||||
)
|
||||
@@ -1,110 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Serial port enumeration. Console tool and backend selection.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
This module will provide a function called comports that returns an
|
||||
iterable (generator or list) that will enumerate available com ports. Note that
|
||||
on some systems non-existent ports may be listed.
|
||||
|
||||
Additionally a grep function is supplied that can be used to search for ports
|
||||
based on their descriptions or hardware ID.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
# chose an implementation, depending on os
|
||||
#~ if sys.platform == 'cli':
|
||||
#~ else:
|
||||
if os.name == 'nt': # sys.platform == 'win32':
|
||||
from serial.tools.list_ports_windows import comports
|
||||
elif os.name == 'posix':
|
||||
from serial.tools.list_ports_posix import comports
|
||||
#~ elif os.name == 'java':
|
||||
else:
|
||||
raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
def grep(regexp, include_links=False):
|
||||
"""\
|
||||
Search for ports using a regular expression. Port name, description and
|
||||
hardware ID are searched. The function returns an iterable that returns the
|
||||
same tuples as comport() would do.
|
||||
"""
|
||||
r = re.compile(regexp, re.I)
|
||||
for info in comports(include_links):
|
||||
port, desc, hwid = info
|
||||
if r.search(port) or r.search(desc) or r.search(hwid):
|
||||
yield info
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Serial port enumeration')
|
||||
|
||||
parser.add_argument(
|
||||
'regexp',
|
||||
nargs='?',
|
||||
help='only show ports that match this regex')
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='show more messages')
|
||||
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help='suppress all messages')
|
||||
|
||||
parser.add_argument(
|
||||
'-n',
|
||||
type=int,
|
||||
help='only output the N-th entry')
|
||||
|
||||
parser.add_argument(
|
||||
'-s', '--include-links',
|
||||
action='store_true',
|
||||
help='include entries that are symlinks to real devices')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
hits = 0
|
||||
# get iteraror w/ or w/o filter
|
||||
if args.regexp:
|
||||
if not args.quiet:
|
||||
sys.stderr.write("Filtered list with regexp: {!r}\n".format(args.regexp))
|
||||
iterator = sorted(grep(args.regexp, include_links=args.include_links))
|
||||
else:
|
||||
iterator = sorted(comports(include_links=args.include_links))
|
||||
# list them
|
||||
for n, (port, desc, hwid) in enumerate(iterator, 1):
|
||||
if args.n is None or args.n == n:
|
||||
sys.stdout.write("{:20}\n".format(port))
|
||||
if args.verbose:
|
||||
sys.stdout.write(" desc: {}\n".format(desc))
|
||||
sys.stdout.write(" hwid: {}\n".format(hwid))
|
||||
hits += 1
|
||||
if not args.quiet:
|
||||
if hits:
|
||||
sys.stderr.write("{} ports found\n".format(hits))
|
||||
else:
|
||||
sys.stderr.write("no ports found\n")
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a helper module for the various platform dependent list_port
|
||||
# implementations.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
|
||||
|
||||
def numsplit(text):
|
||||
"""\
|
||||
Convert string into a list of texts and numbers in order to support a
|
||||
natural sorting.
|
||||
"""
|
||||
result = []
|
||||
for group in re.split(r'(\d+)', text):
|
||||
if group:
|
||||
try:
|
||||
group = int(group)
|
||||
except ValueError:
|
||||
pass
|
||||
result.append(group)
|
||||
return result
|
||||
|
||||
|
||||
class ListPortInfo(object):
|
||||
"""Info collection base class for serial ports"""
|
||||
|
||||
def __init__(self, device, skip_link_detection=False):
|
||||
self.device = device
|
||||
self.name = os.path.basename(device)
|
||||
self.description = 'n/a'
|
||||
self.hwid = 'n/a'
|
||||
# USB specific data
|
||||
self.vid = None
|
||||
self.pid = None
|
||||
self.serial_number = None
|
||||
self.location = None
|
||||
self.manufacturer = None
|
||||
self.product = None
|
||||
self.interface = None
|
||||
# special handling for links
|
||||
if not skip_link_detection and device is not None and os.path.islink(device):
|
||||
self.hwid = 'LINK={}'.format(os.path.realpath(device))
|
||||
|
||||
def usb_description(self):
|
||||
"""return a short string to name the port based on USB info"""
|
||||
if self.interface is not None:
|
||||
return '{} - {}'.format(self.product, self.interface)
|
||||
elif self.product is not None:
|
||||
return self.product
|
||||
else:
|
||||
return self.name
|
||||
|
||||
def usb_info(self):
|
||||
"""return a string with USB related information about device"""
|
||||
return 'USB VID:PID={:04X}:{:04X}{}{}'.format(
|
||||
self.vid or 0,
|
||||
self.pid or 0,
|
||||
' SER={}'.format(self.serial_number) if self.serial_number is not None else '',
|
||||
' LOCATION={}'.format(self.location) if self.location is not None else '')
|
||||
|
||||
def apply_usb_info(self):
|
||||
"""update description and hwid from USB data"""
|
||||
self.description = self.usb_description()
|
||||
self.hwid = self.usb_info()
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, ListPortInfo) and self.device == other.device
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.device)
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, ListPortInfo):
|
||||
raise TypeError('unorderable types: {}() and {}()'.format(
|
||||
type(self).__name__,
|
||||
type(other).__name__))
|
||||
return numsplit(self.device) < numsplit(other.device)
|
||||
|
||||
def __str__(self):
|
||||
return '{} - {}'.format(self.device, self.description)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Item access: backwards compatible -> (port, desc, hwid)"""
|
||||
if index == 0:
|
||||
return self.device
|
||||
elif index == 1:
|
||||
return self.description
|
||||
elif index == 2:
|
||||
return self.hwid
|
||||
else:
|
||||
raise IndexError('{} > 2'.format(index))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def list_links(devices):
|
||||
"""\
|
||||
search all /dev devices and look for symlinks to known ports already
|
||||
listed in devices.
|
||||
"""
|
||||
links = []
|
||||
for device in glob.glob('/dev/*'):
|
||||
if os.path.islink(device) and os.path.realpath(device) in devices:
|
||||
links.append(device)
|
||||
return links
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
print(ListPortInfo('dummy'))
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports including details on
|
||||
# GNU/Linux systems.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import glob
|
||||
import os
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
|
||||
class SysFS(list_ports_common.ListPortInfo):
|
||||
"""Wrapper for easy sysfs access and device info"""
|
||||
|
||||
def __init__(self, device):
|
||||
super(SysFS, self).__init__(device)
|
||||
# special handling for links
|
||||
if device is not None and os.path.islink(device):
|
||||
device = os.path.realpath(device)
|
||||
is_link = True
|
||||
else:
|
||||
is_link = False
|
||||
self.usb_device_path = None
|
||||
if os.path.exists('/sys/class/tty/{}/device'.format(self.name)):
|
||||
self.device_path = os.path.realpath('/sys/class/tty/{}/device'.format(self.name))
|
||||
self.subsystem = os.path.basename(os.path.realpath(os.path.join(self.device_path, 'subsystem')))
|
||||
else:
|
||||
self.device_path = None
|
||||
self.subsystem = None
|
||||
# check device type
|
||||
if self.subsystem == 'usb-serial':
|
||||
self.usb_interface_path = os.path.dirname(self.device_path)
|
||||
elif self.subsystem == 'usb':
|
||||
self.usb_interface_path = self.device_path
|
||||
else:
|
||||
self.usb_interface_path = None
|
||||
# fill-in info for USB devices
|
||||
if self.usb_interface_path is not None:
|
||||
self.usb_device_path = os.path.dirname(self.usb_interface_path)
|
||||
|
||||
try:
|
||||
num_if = int(self.read_line(self.usb_device_path, 'bNumInterfaces'))
|
||||
except ValueError:
|
||||
num_if = 1
|
||||
|
||||
self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16)
|
||||
self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16)
|
||||
self.serial_number = self.read_line(self.usb_device_path, 'serial')
|
||||
if num_if > 1: # multi interface devices like FT4232
|
||||
self.location = os.path.basename(self.usb_interface_path)
|
||||
else:
|
||||
self.location = os.path.basename(self.usb_device_path)
|
||||
|
||||
self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer')
|
||||
self.product = self.read_line(self.usb_device_path, 'product')
|
||||
self.interface = self.read_line(self.usb_interface_path, 'interface')
|
||||
|
||||
if self.subsystem in ('usb', 'usb-serial'):
|
||||
self.apply_usb_info()
|
||||
#~ elif self.subsystem in ('pnp', 'amba'): # PCI based devices, raspi
|
||||
elif self.subsystem == 'pnp': # PCI based devices
|
||||
self.description = self.name
|
||||
self.hwid = self.read_line(self.device_path, 'id')
|
||||
elif self.subsystem == 'amba': # raspi
|
||||
self.description = self.name
|
||||
self.hwid = os.path.basename(self.device_path)
|
||||
|
||||
if is_link:
|
||||
self.hwid += ' LINK={}'.format(device)
|
||||
|
||||
def read_line(self, *args):
|
||||
"""\
|
||||
Helper function to read a single line from a file.
|
||||
One or more parameters are allowed, they are joined with os.path.join.
|
||||
Returns None on errors..
|
||||
"""
|
||||
try:
|
||||
with open(os.path.join(*args)) as f:
|
||||
line = f.readline().strip()
|
||||
return line
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
|
||||
def comports(include_links=False):
|
||||
devices = 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))
|
||||
@@ -1,299 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports including details on OSX
|
||||
#
|
||||
# code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools
|
||||
# with contributions from cibomahto, dgs3, FarMcKon, tedbrandston
|
||||
# and modifications by cliechti, hoihu, hardkrash
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2013-2020
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
|
||||
# List all of the callout devices in OS/X by querying IOKit.
|
||||
|
||||
# See the following for a reference of how to do this:
|
||||
# http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD
|
||||
|
||||
# More help from darwin_hid.py
|
||||
|
||||
# Also see the 'IORegistryExplorer' for an idea of what we are actually searching
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import ctypes
|
||||
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
|
||||
cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')
|
||||
|
||||
# kIOMasterPortDefault is no longer exported in BigSur but no biggie, using NULL works just the same
|
||||
kIOMasterPortDefault = 0 # WAS: ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
|
||||
kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
|
||||
|
||||
kCFStringEncodingMacRoman = 0
|
||||
kCFStringEncodingUTF8 = 0x08000100
|
||||
|
||||
# defined in `IOKit/usb/USBSpec.h`
|
||||
kUSBVendorString = 'USB Vendor Name'
|
||||
kUSBSerialNumberString = 'USB Serial Number'
|
||||
|
||||
# `io_name_t` defined as `typedef char io_name_t[128];`
|
||||
# in `device/device_types.h`
|
||||
io_name_size = 128
|
||||
|
||||
# defined in `mach/kern_return.h`
|
||||
KERN_SUCCESS = 0
|
||||
# kern_return_t defined as `typedef int kern_return_t;` in `mach/i386/kern_return.h`
|
||||
kern_return_t = ctypes.c_int
|
||||
|
||||
iokit.IOServiceMatching.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IOServiceGetMatchingServices.restype = kern_return_t
|
||||
|
||||
iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IOServiceGetMatchingServices.restype = kern_return_t
|
||||
|
||||
iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
|
||||
iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IORegistryEntryGetPath.restype = kern_return_t
|
||||
|
||||
iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IORegistryEntryGetName.restype = kern_return_t
|
||||
|
||||
iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IOObjectGetClass.restype = kern_return_t
|
||||
|
||||
iokit.IOObjectRelease.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32]
|
||||
cf.CFStringCreateWithCString.restype = ctypes.c_void_p
|
||||
|
||||
cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
|
||||
cf.CFStringGetCStringPtr.restype = ctypes.c_char_p
|
||||
|
||||
cf.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_uint32]
|
||||
cf.CFStringGetCString.restype = ctypes.c_bool
|
||||
|
||||
cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p]
|
||||
cf.CFNumberGetValue.restype = ctypes.c_void_p
|
||||
|
||||
# void CFRelease ( CFTypeRef cf );
|
||||
cf.CFRelease.argtypes = [ctypes.c_void_p]
|
||||
cf.CFRelease.restype = None
|
||||
|
||||
# CFNumber type defines
|
||||
kCFNumberSInt8Type = 1
|
||||
kCFNumberSInt16Type = 2
|
||||
kCFNumberSInt32Type = 3
|
||||
kCFNumberSInt64Type = 4
|
||||
|
||||
|
||||
def get_string_property(device_type, property):
|
||||
"""
|
||||
Search the given device for the specified string property
|
||||
|
||||
@param device_type Type of Device
|
||||
@param property String to search for
|
||||
@return Python string containing the value, or None if not found.
|
||||
"""
|
||||
key = cf.CFStringCreateWithCString(
|
||||
kCFAllocatorDefault,
|
||||
property.encode("utf-8"),
|
||||
kCFStringEncodingUTF8)
|
||||
|
||||
CFContainer = iokit.IORegistryEntryCreateCFProperty(
|
||||
device_type,
|
||||
key,
|
||||
kCFAllocatorDefault,
|
||||
0)
|
||||
output = None
|
||||
|
||||
if CFContainer:
|
||||
output = cf.CFStringGetCStringPtr(CFContainer, 0)
|
||||
if output is not None:
|
||||
output = output.decode('utf-8')
|
||||
else:
|
||||
buffer = ctypes.create_string_buffer(io_name_size);
|
||||
success = cf.CFStringGetCString(CFContainer, ctypes.byref(buffer), io_name_size, kCFStringEncodingUTF8)
|
||||
if success:
|
||||
output = buffer.value.decode('utf-8')
|
||||
cf.CFRelease(CFContainer)
|
||||
return output
|
||||
|
||||
|
||||
def get_int_property(device_type, property, cf_number_type):
|
||||
"""
|
||||
Search the given device for the specified string property
|
||||
|
||||
@param device_type Device to search
|
||||
@param property String to search for
|
||||
@param cf_number_type CFType number
|
||||
|
||||
@return Python string containing the value, or None if not found.
|
||||
"""
|
||||
key = cf.CFStringCreateWithCString(
|
||||
kCFAllocatorDefault,
|
||||
property.encode("utf-8"),
|
||||
kCFStringEncodingUTF8)
|
||||
|
||||
CFContainer = iokit.IORegistryEntryCreateCFProperty(
|
||||
device_type,
|
||||
key,
|
||||
kCFAllocatorDefault,
|
||||
0)
|
||||
|
||||
if CFContainer:
|
||||
if (cf_number_type == kCFNumberSInt32Type):
|
||||
number = ctypes.c_uint32()
|
||||
elif (cf_number_type == kCFNumberSInt16Type):
|
||||
number = ctypes.c_uint16()
|
||||
cf.CFNumberGetValue(CFContainer, cf_number_type, ctypes.byref(number))
|
||||
cf.CFRelease(CFContainer)
|
||||
return number.value
|
||||
return None
|
||||
|
||||
def IORegistryEntryGetName(device):
|
||||
devicename = ctypes.create_string_buffer(io_name_size);
|
||||
res = iokit.IORegistryEntryGetName(device, ctypes.byref(devicename))
|
||||
if res != KERN_SUCCESS:
|
||||
return None
|
||||
# this works in python2 but may not be valid. Also I don't know if
|
||||
# this encoding is guaranteed. It may be dependent on system locale.
|
||||
return devicename.value.decode('utf-8')
|
||||
|
||||
def IOObjectGetClass(device):
|
||||
classname = ctypes.create_string_buffer(io_name_size)
|
||||
iokit.IOObjectGetClass(device, ctypes.byref(classname))
|
||||
return classname.value
|
||||
|
||||
def GetParentDeviceByType(device, parent_type):
|
||||
""" Find the first parent of a device that implements the parent_type
|
||||
@param IOService Service to inspect
|
||||
@return Pointer to the parent type, or None if it was not found.
|
||||
"""
|
||||
# First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice.
|
||||
parent_type = parent_type.encode('utf-8')
|
||||
while IOObjectGetClass(device) != parent_type:
|
||||
parent = ctypes.c_void_p()
|
||||
response = iokit.IORegistryEntryGetParentEntry(
|
||||
device,
|
||||
"IOService".encode("utf-8"),
|
||||
ctypes.byref(parent))
|
||||
# If we weren't able to find a parent for the device, we're done.
|
||||
if response != KERN_SUCCESS:
|
||||
return None
|
||||
device = parent
|
||||
return device
|
||||
|
||||
|
||||
def GetIOServicesByType(service_type):
|
||||
"""
|
||||
returns iterator over specified service_type
|
||||
"""
|
||||
serial_port_iterator = ctypes.c_void_p()
|
||||
|
||||
iokit.IOServiceGetMatchingServices(
|
||||
kIOMasterPortDefault,
|
||||
iokit.IOServiceMatching(service_type.encode('utf-8')),
|
||||
ctypes.byref(serial_port_iterator))
|
||||
|
||||
services = []
|
||||
while iokit.IOIteratorIsValid(serial_port_iterator):
|
||||
service = iokit.IOIteratorNext(serial_port_iterator)
|
||||
if not service:
|
||||
break
|
||||
services.append(service)
|
||||
iokit.IOObjectRelease(serial_port_iterator)
|
||||
return services
|
||||
|
||||
|
||||
def location_to_string(locationID):
|
||||
"""
|
||||
helper to calculate port and bus number from locationID
|
||||
"""
|
||||
loc = ['{}-'.format(locationID >> 24)]
|
||||
while locationID & 0xf00000:
|
||||
if len(loc) > 1:
|
||||
loc.append('.')
|
||||
loc.append('{}'.format((locationID >> 20) & 0xf))
|
||||
locationID <<= 4
|
||||
return ''.join(loc)
|
||||
|
||||
|
||||
class SuitableSerialInterface(object):
|
||||
pass
|
||||
|
||||
|
||||
def scan_interfaces():
|
||||
"""
|
||||
helper function to scan USB interfaces
|
||||
returns a list of SuitableSerialInterface objects with name and id attributes
|
||||
"""
|
||||
interfaces = []
|
||||
for service in GetIOServicesByType('IOSerialBSDClient'):
|
||||
device = get_string_property(service, "IOCalloutDevice")
|
||||
if device:
|
||||
usb_device = GetParentDeviceByType(service, "IOUSBInterface")
|
||||
if usb_device:
|
||||
name = get_string_property(usb_device, "USB Interface Name") or None
|
||||
locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) or ''
|
||||
i = SuitableSerialInterface()
|
||||
i.id = locationID
|
||||
i.name = name
|
||||
interfaces.append(i)
|
||||
return interfaces
|
||||
|
||||
|
||||
def search_for_locationID_in_interfaces(serial_interfaces, locationID):
|
||||
for interface in serial_interfaces:
|
||||
if (interface.id == locationID):
|
||||
return interface.name
|
||||
return None
|
||||
|
||||
|
||||
def comports(include_links=False):
|
||||
# XXX include_links is currently ignored. are links in /dev even supported here?
|
||||
# Scan for all iokit serial ports
|
||||
services = GetIOServicesByType('IOSerialBSDClient')
|
||||
ports = []
|
||||
serial_interfaces = scan_interfaces()
|
||||
for service in services:
|
||||
# First, add the callout device file.
|
||||
device = get_string_property(service, "IOCalloutDevice")
|
||||
if device:
|
||||
info = list_ports_common.ListPortInfo(device)
|
||||
# If the serial port is implemented by IOUSBDevice
|
||||
# NOTE IOUSBDevice was deprecated as of 10.11 and finally on Apple Silicon
|
||||
# devices has been completely removed. Thanks to @oskay for this patch.
|
||||
usb_device = GetParentDeviceByType(service, "IOUSBHostDevice")
|
||||
if not usb_device:
|
||||
usb_device = GetParentDeviceByType(service, "IOUSBDevice")
|
||||
if usb_device:
|
||||
# fetch some useful 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))
|
||||
@@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports on POSIXy systems.
|
||||
# For some specific implementations, see also list_ports_linux, list_ports_osx
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
The ``comports`` function is expected to return an iterable that yields tuples
|
||||
of 3 strings: port name, human readable description and a hardware ID.
|
||||
|
||||
As currently no method is known to get the second two strings easily, they are
|
||||
currently just identical to the port name.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import glob
|
||||
import sys
|
||||
import os
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
# try to detect the OS so that a device can be selected...
|
||||
plat = sys.platform.lower()
|
||||
|
||||
if plat[:5] == 'linux': # Linux (confirmed) # noqa
|
||||
from serial.tools.list_ports_linux import comports
|
||||
|
||||
elif plat[:6] == 'darwin': # OS X (confirmed)
|
||||
from serial.tools.list_ports_osx import comports
|
||||
|
||||
elif plat == 'cygwin': # cygwin/win32
|
||||
# cygwin accepts /dev/com* in many contexts
|
||||
# (such as 'open' call, explicit 'ls'), but 'glob.glob'
|
||||
# and bare 'ls' do not; so use /dev/ttyS* instead
|
||||
def comports(include_links=False):
|
||||
devices = 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))
|
||||
@@ -1,427 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Enumerate serial ports on Windows including a human readable description
|
||||
# and hardware information.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# pylint: disable=invalid-name,too-few-public-methods
|
||||
import re
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL
|
||||
from ctypes.wintypes import HWND
|
||||
from ctypes.wintypes import DWORD
|
||||
from ctypes.wintypes import WORD
|
||||
from ctypes.wintypes import LONG
|
||||
from ctypes.wintypes import ULONG
|
||||
from ctypes.wintypes import HKEY
|
||||
from ctypes.wintypes import BYTE
|
||||
import serial
|
||||
from serial.win32 import ULONG_PTR
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
|
||||
def ValidHandle(value, func, arguments):
|
||||
if value == 0:
|
||||
raise ctypes.WinError()
|
||||
return value
|
||||
|
||||
|
||||
NULL = 0
|
||||
HDEVINFO = ctypes.c_void_p
|
||||
LPCTSTR = ctypes.c_wchar_p
|
||||
PCTSTR = ctypes.c_wchar_p
|
||||
PTSTR = ctypes.c_wchar_p
|
||||
LPDWORD = PDWORD = ctypes.POINTER(DWORD)
|
||||
#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE)
|
||||
LPBYTE = PBYTE = ctypes.c_void_p # XXX avoids error about types
|
||||
|
||||
ACCESS_MASK = DWORD
|
||||
REGSAM = ACCESS_MASK
|
||||
|
||||
|
||||
class GUID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Data1', DWORD),
|
||||
('Data2', WORD),
|
||||
('Data3', WORD),
|
||||
('Data4', BYTE * 8),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format(
|
||||
self.Data1,
|
||||
self.Data2,
|
||||
self.Data3,
|
||||
''.join(["{:02x}".format(d) for d in self.Data4[:2]]),
|
||||
''.join(["{:02x}".format(d) for d in self.Data4[2:]]),
|
||||
)
|
||||
|
||||
|
||||
class SP_DEVINFO_DATA(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('cbSize', DWORD),
|
||||
('ClassGuid', GUID),
|
||||
('DevInst', DWORD),
|
||||
('Reserved', ULONG_PTR),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "ClassGuid:{} DevInst:{}".format(self.ClassGuid, self.DevInst)
|
||||
|
||||
|
||||
PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA)
|
||||
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p
|
||||
|
||||
setupapi = ctypes.windll.LoadLibrary("setupapi")
|
||||
SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList
|
||||
SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO]
|
||||
SetupDiDestroyDeviceInfoList.restype = BOOL
|
||||
|
||||
SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameW
|
||||
SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD]
|
||||
SetupDiClassGuidsFromName.restype = BOOL
|
||||
|
||||
SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo
|
||||
SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA]
|
||||
SetupDiEnumDeviceInfo.restype = BOOL
|
||||
|
||||
SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW
|
||||
SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD]
|
||||
SetupDiGetClassDevs.restype = HDEVINFO
|
||||
SetupDiGetClassDevs.errcheck = ValidHandle
|
||||
|
||||
SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW
|
||||
SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD]
|
||||
SetupDiGetDeviceRegistryProperty.restype = BOOL
|
||||
|
||||
SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdW
|
||||
SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD]
|
||||
SetupDiGetDeviceInstanceId.restype = BOOL
|
||||
|
||||
SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey
|
||||
SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM]
|
||||
SetupDiOpenDevRegKey.restype = HKEY
|
||||
|
||||
advapi32 = ctypes.windll.LoadLibrary("Advapi32")
|
||||
RegCloseKey = advapi32.RegCloseKey
|
||||
RegCloseKey.argtypes = [HKEY]
|
||||
RegCloseKey.restype = LONG
|
||||
|
||||
RegQueryValueEx = advapi32.RegQueryValueExW
|
||||
RegQueryValueEx.argtypes = [HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD]
|
||||
RegQueryValueEx.restype = LONG
|
||||
|
||||
cfgmgr32 = ctypes.windll.LoadLibrary("Cfgmgr32")
|
||||
CM_Get_Parent = cfgmgr32.CM_Get_Parent
|
||||
CM_Get_Parent.argtypes = [PDWORD, DWORD, ULONG]
|
||||
CM_Get_Parent.restype = LONG
|
||||
|
||||
CM_Get_Device_IDW = cfgmgr32.CM_Get_Device_IDW
|
||||
CM_Get_Device_IDW.argtypes = [DWORD, PTSTR, ULONG, ULONG]
|
||||
CM_Get_Device_IDW.restype = LONG
|
||||
|
||||
CM_MapCrToWin32Err = cfgmgr32.CM_MapCrToWin32Err
|
||||
CM_MapCrToWin32Err.argtypes = [DWORD, DWORD]
|
||||
CM_MapCrToWin32Err.restype = DWORD
|
||||
|
||||
|
||||
DIGCF_PRESENT = 2
|
||||
DIGCF_DEVICEINTERFACE = 16
|
||||
INVALID_HANDLE_VALUE = 0
|
||||
ERROR_INSUFFICIENT_BUFFER = 122
|
||||
ERROR_NOT_FOUND = 1168
|
||||
SPDRP_HARDWAREID = 1
|
||||
SPDRP_FRIENDLYNAME = 12
|
||||
SPDRP_LOCATION_PATHS = 35
|
||||
SPDRP_MFG = 11
|
||||
DICS_FLAG_GLOBAL = 1
|
||||
DIREG_DEV = 0x00000001
|
||||
KEY_READ = 0x20019
|
||||
|
||||
|
||||
MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH = 5
|
||||
|
||||
|
||||
def get_parent_serial_number(child_devinst, child_vid, child_pid, depth=0, last_serial_number=None):
|
||||
""" Get the serial number of the parent of a device.
|
||||
|
||||
Args:
|
||||
child_devinst: The device instance handle to get the parent serial number of.
|
||||
child_vid: The vendor ID of the child device.
|
||||
child_pid: The product ID of the child device.
|
||||
depth: The current iteration depth of the USB device tree.
|
||||
"""
|
||||
|
||||
# If the traversal depth is beyond the max, abandon attempting to find the serial number.
|
||||
if depth > MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH:
|
||||
return '' if not last_serial_number else last_serial_number
|
||||
|
||||
# Get the parent device instance.
|
||||
devinst = DWORD()
|
||||
ret = CM_Get_Parent(ctypes.byref(devinst), child_devinst, 0)
|
||||
|
||||
if ret:
|
||||
win_error = CM_MapCrToWin32Err(DWORD(ret), DWORD(0))
|
||||
|
||||
# If there is no parent available, the child was the root device. We cannot traverse
|
||||
# further.
|
||||
if win_error == ERROR_NOT_FOUND:
|
||||
return '' if not last_serial_number else last_serial_number
|
||||
|
||||
raise ctypes.WinError(win_error)
|
||||
|
||||
# Get the ID of the parent device and parse it for vendor ID, product ID, and serial number.
|
||||
parentHardwareID = ctypes.create_unicode_buffer(250)
|
||||
|
||||
ret = CM_Get_Device_IDW(
|
||||
devinst,
|
||||
parentHardwareID,
|
||||
ctypes.sizeof(parentHardwareID) - 1,
|
||||
0)
|
||||
|
||||
if ret:
|
||||
raise ctypes.WinError(CM_MapCrToWin32Err(DWORD(ret), DWORD(0)))
|
||||
|
||||
parentHardwareID_str = parentHardwareID.value
|
||||
m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?',
|
||||
parentHardwareID_str,
|
||||
re.I)
|
||||
|
||||
# return early if we have no matches (likely malformed serial, traversed too far)
|
||||
if not m:
|
||||
return '' if not last_serial_number else last_serial_number
|
||||
|
||||
vid = None
|
||||
pid = None
|
||||
serial_number = None
|
||||
if m.group(1):
|
||||
vid = int(m.group(1), 16)
|
||||
if m.group(3):
|
||||
pid = int(m.group(3), 16)
|
||||
if m.group(7):
|
||||
serial_number = m.group(7)
|
||||
|
||||
# store what we found as a fallback for malformed serial values up the chain
|
||||
found_serial_number = serial_number
|
||||
|
||||
# Check that the USB serial number only contains 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))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,57 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a special URL handler that allows selecting an
|
||||
# alternate implementation provided by some backends.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: alt://port[?option[=value][&option[=value]]]
|
||||
# options:
|
||||
# - class=X used class named X instead of Serial
|
||||
#
|
||||
# example:
|
||||
# use poll based implementation on Posix (Linux):
|
||||
# python -m serial.tools.miniterm alt:///dev/ttyUSB0?class=PosixPollSerial
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
import serial
|
||||
|
||||
|
||||
def serial_class_for_url(url):
|
||||
"""extract host and port from an URL string"""
|
||||
parts = urlparse.urlsplit(url)
|
||||
if parts.scheme != 'alt':
|
||||
raise serial.SerialException(
|
||||
'expected a string in the form "alt://port[?option[=value][&option[=value]]]": '
|
||||
'not starting with alt:// ({!r})'.format(parts.scheme))
|
||||
class_name = 'Serial'
|
||||
try:
|
||||
for option, values in urlparse.parse_qs(parts.query, True).items():
|
||||
if option == 'class':
|
||||
class_name = values[0]
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
except ValueError as e:
|
||||
raise serial.SerialException(
|
||||
'expected a string in the form '
|
||||
'"alt://port[?option[=value][&option[=value]]]": {!r}'.format(e))
|
||||
if not hasattr(serial, class_name):
|
||||
raise ValueError('unknown class: {!r}'.format(class_name))
|
||||
cls = getattr(serial, class_name)
|
||||
if not issubclass(cls, serial.Serial):
|
||||
raise ValueError('class {!r} is not an instance of Serial'.format(class_name))
|
||||
return (''.join([parts.netloc, parts.path]), cls)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
s = serial.serial_for_url('alt:///dev/ttyS0?class=PosixPollSerial')
|
||||
print(s)
|
||||
@@ -1,258 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Backend for Silicon Labs CP2110/4 HID-to-UART devices.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
# (C) 2019 Google LLC
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# This backend implements support for HID-to-UART devices manufactured
|
||||
# by Silicon Labs and marketed as CP2110 and CP2114. The
|
||||
# implementation is (mostly) OS-independent and in userland. It relies
|
||||
# on cython-hidapi (https://github.com/trezor/cython-hidapi).
|
||||
|
||||
# The HID-to-UART protocol implemented by CP2110/4 is described in the
|
||||
# AN434 document from Silicon Labs:
|
||||
# https://www.silabs.com/documents/public/application-notes/AN434-CP2110-4-Interface-Specification.pdf
|
||||
|
||||
# TODO items:
|
||||
|
||||
# - rtscts support is configured for hardware flow control, but the
|
||||
# signaling is missing (AN434 suggests this is done through GPIO).
|
||||
# - Cancelling reads and writes is not supported.
|
||||
# - Baudrate validation is not implemented, as it depends on model and configuration.
|
||||
|
||||
import struct
|
||||
import threading
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
try:
|
||||
import Queue
|
||||
except ImportError:
|
||||
import queue as Queue
|
||||
|
||||
import hid # hidapi
|
||||
|
||||
import serial
|
||||
from serial.serialutil import SerialBase, SerialException, PortNotOpenError, to_bytes, Timeout
|
||||
|
||||
|
||||
# Report IDs and related constant
|
||||
_REPORT_GETSET_UART_ENABLE = 0x41
|
||||
_DISABLE_UART = 0x00
|
||||
_ENABLE_UART = 0x01
|
||||
|
||||
_REPORT_SET_PURGE_FIFOS = 0x43
|
||||
_PURGE_TX_FIFO = 0x01
|
||||
_PURGE_RX_FIFO = 0x02
|
||||
|
||||
_REPORT_GETSET_UART_CONFIG = 0x50
|
||||
|
||||
_REPORT_SET_TRANSMIT_LINE_BREAK = 0x51
|
||||
_REPORT_SET_STOP_LINE_BREAK = 0x52
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
# This is not quite correct. AN343 specifies that the minimum
|
||||
# baudrate is different between CP2110 and CP2114, and it's halved
|
||||
# when using non-8-bit symbols.
|
||||
BAUDRATES = (300, 375, 600, 1200, 1800, 2400, 4800, 9600, 19200,
|
||||
38400, 57600, 115200, 230400, 460800, 500000, 576000,
|
||||
921600, 1000000)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._hid_handle = None
|
||||
self._read_buffer = None
|
||||
self._thread = None
|
||||
super(Serial, self).__init__(*args, **kwargs)
|
||||
|
||||
def open(self):
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
|
||||
self._read_buffer = Queue.Queue()
|
||||
|
||||
self._hid_handle = hid.device()
|
||||
try:
|
||||
portpath = self.from_url(self.portstr)
|
||||
self._hid_handle.open_path(portpath)
|
||||
except OSError as msg:
|
||||
raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
|
||||
|
||||
try:
|
||||
self._reconfigure_port()
|
||||
except:
|
||||
try:
|
||||
self._hid_handle.close()
|
||||
except:
|
||||
pass
|
||||
self._hid_handle = None
|
||||
raise
|
||||
else:
|
||||
self.is_open = True
|
||||
self._thread = threading.Thread(target=self._hid_read_loop)
|
||||
self._thread.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
|
||||
@@ -1,91 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a special URL handler that uses the port listing to
|
||||
# find ports by searching the string descriptions.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: hwgrep://<regexp>&<option>
|
||||
#
|
||||
# where <regexp> is a Python regexp according to the re module
|
||||
#
|
||||
# violating the normal definition for URLs, the charachter `&` is used to
|
||||
# separate parameters from the arguments (instead of `?`, but the question mark
|
||||
# is heavily used in regexp'es)
|
||||
#
|
||||
# options:
|
||||
# n=<N> pick the N'th entry instead of the first one (numbering starts at 1)
|
||||
# skip_busy tries to open port to check if it is busy, fails on posix as ports are not locked!
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
basestring = str # python 3 pylint: disable=redefined-builtin
|
||||
|
||||
|
||||
class Serial(serial.Serial):
|
||||
"""Just inherit the native Serial port implementation and patch the port property."""
|
||||
# pylint: disable=no-member
|
||||
|
||||
@serial.Serial.port.setter
|
||||
def port(self, value):
|
||||
"""translate port name before storing it"""
|
||||
if isinstance(value, basestring) and value.startswith('hwgrep://'):
|
||||
serial.Serial.port.__set__(self, self.from_url(value))
|
||||
else:
|
||||
serial.Serial.port.__set__(self, value)
|
||||
|
||||
def from_url(self, url):
|
||||
"""extract host and port from an URL string"""
|
||||
if url.lower().startswith("hwgrep://"):
|
||||
url = url[9:]
|
||||
n = 0
|
||||
test_open = False
|
||||
args = url.split('&')
|
||||
regexp = args.pop(0)
|
||||
for arg in args:
|
||||
if '=' in arg:
|
||||
option, value = arg.split('=', 1)
|
||||
else:
|
||||
option = arg
|
||||
value = None
|
||||
if option == 'n':
|
||||
# pick n'th element
|
||||
n = int(value) - 1
|
||||
if n < 1:
|
||||
raise ValueError('option "n" expects a positive integer larger than 1: {!r}'.format(value))
|
||||
elif option == 'skip_busy':
|
||||
# open to test if port is available. not the nicest way..
|
||||
test_open = True
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
# use a for loop to get the 1st element from the generator
|
||||
for port, desc, hwid in sorted(serial.tools.list_ports.grep(regexp)):
|
||||
if test_open:
|
||||
try:
|
||||
s = serial.Serial(port)
|
||||
except serial.SerialException:
|
||||
# it has some error, skip this one
|
||||
continue
|
||||
else:
|
||||
s.close()
|
||||
if n:
|
||||
n -= 1
|
||||
continue
|
||||
return port
|
||||
else:
|
||||
raise serial.SerialException('no ports found matching regexp {!r}'.format(url))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
s = Serial(None)
|
||||
s.port = 'hwgrep://ttyS0'
|
||||
print(s)
|
||||
@@ -1,308 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a loop back connection receiving itself what it sent.
|
||||
#
|
||||
# The purpose of this module is.. well... You can run the unit tests with it.
|
||||
# and it was so easy to implement ;-)
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: loop://[option[/option...]]
|
||||
# options:
|
||||
# - "debug" print diagnostic messages
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import numbers
|
||||
import time
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
try:
|
||||
import queue
|
||||
except ImportError:
|
||||
import Queue as queue
|
||||
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, iterbytes, SerialTimeoutException, PortNotOpenError
|
||||
|
||||
# map log level names to constants. used in from_url()
|
||||
LOGGER_LEVELS = {
|
||||
'debug': logging.DEBUG,
|
||||
'info': logging.INFO,
|
||||
'warning': logging.WARNING,
|
||||
'error': logging.ERROR,
|
||||
}
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation that simulates a loop back connection in plain software."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.buffer_size = 4096
|
||||
self.queue = None
|
||||
self.logger = None
|
||||
self._cancel_write = False
|
||||
super(Serial, self).__init__(*args, **kwargs)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
self.logger = None
|
||||
self.queue = queue.Queue(self.buffer_size)
|
||||
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
# not that there is anything to open, but the function applies the
|
||||
# options found in the URL
|
||||
self.from_url(self.port)
|
||||
|
||||
# not that there anything to configure...
|
||||
self._reconfigure_port()
|
||||
# all things set up get, now a clean start
|
||||
self.is_open = True
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
self.reset_input_buffer()
|
||||
self.reset_output_buffer()
|
||||
|
||||
def close(self):
|
||||
if self.is_open:
|
||||
self.is_open = False
|
||||
try:
|
||||
self.queue.put_nowait(None)
|
||||
except queue.Full:
|
||||
pass
|
||||
super(Serial, self).close()
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""\
|
||||
Set communication parameters on opened port. For the loop://
|
||||
protocol all settings are ignored!
|
||||
"""
|
||||
# not that's it of any real use, but it helps in the unit tests
|
||||
if not isinstance(self._baudrate, numbers.Integral) or not 0 < self._baudrate < 2 ** 32:
|
||||
raise ValueError("invalid baudrate: {!r}".format(self._baudrate))
|
||||
if self.logger:
|
||||
self.logger.info('_reconfigure_port()')
|
||||
|
||||
def from_url(self, url):
|
||||
"""extract host and port from an URL string"""
|
||||
parts = urlparse.urlsplit(url)
|
||||
if parts.scheme != "loop":
|
||||
raise SerialException(
|
||||
'expected a string in the form '
|
||||
'"loop://[?logging={debug|info|warning|error}]": not starting '
|
||||
'with loop:// ({!r})'.format(parts.scheme))
|
||||
try:
|
||||
# process options now, directly altering self
|
||||
for option, values in urlparse.parse_qs(parts.query, True).items():
|
||||
if option == 'logging':
|
||||
logging.basicConfig() # XXX is that good to call it here?
|
||||
self.logger = logging.getLogger('pySerial.loop')
|
||||
self.logger.setLevel(LOGGER_LEVELS[values[0]])
|
||||
self.logger.debug('enabled logging')
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
except ValueError as e:
|
||||
raise SerialException(
|
||||
'expected a string in the form '
|
||||
'"loop://[?logging={debug|info|warning|error}]": {}'.format(e))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
# attention the logged value can differ from return value in
|
||||
# threaded environments...
|
||||
self.logger.debug('in_waiting -> {:d}'.format(self.queue.qsize()))
|
||||
return self.queue.qsize()
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self._timeout is not None and self._timeout != 0:
|
||||
timeout = time.time() + self._timeout
|
||||
else:
|
||||
timeout = None
|
||||
data = bytearray()
|
||||
while size > 0 and self.is_open:
|
||||
try:
|
||||
b = self.queue.get(timeout=self._timeout) # XXX inter char timeout
|
||||
except queue.Empty:
|
||||
if self._timeout == 0:
|
||||
break
|
||||
else:
|
||||
if b is not None:
|
||||
data += b
|
||||
size -= 1
|
||||
else:
|
||||
break
|
||||
# check for timeout now, after data has been read.
|
||||
# useful for timeout = 0 (non blocking) read
|
||||
if timeout and time.time() > timeout:
|
||||
if self.logger:
|
||||
self.logger.info('read timeout')
|
||||
break
|
||||
return bytes(data)
|
||||
|
||||
def cancel_read(self):
|
||||
self.queue.put_nowait(None)
|
||||
|
||||
def cancel_write(self):
|
||||
self._cancel_write = True
|
||||
|
||||
def write(self, data):
|
||||
"""\
|
||||
Output the given byte string over the serial port. Can block if the
|
||||
connection is blocked. May raise SerialException if the connection is
|
||||
closed.
|
||||
"""
|
||||
self._cancel_write = False
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
data = to_bytes(data)
|
||||
# calculate aprox time that would be used to send the data
|
||||
time_used_to_send = 10.0 * len(data) / self._baudrate
|
||||
# when a write timeout is configured check if we would be successful
|
||||
# (not sending anything, not even the part that would have time)
|
||||
if self._write_timeout is not None and time_used_to_send > self._write_timeout:
|
||||
# must wait so that unit test succeeds
|
||||
time_left = self._write_timeout
|
||||
while time_left > 0 and not self._cancel_write:
|
||||
time.sleep(min(time_left, 0.5))
|
||||
time_left -= 0.5
|
||||
if self._cancel_write:
|
||||
return 0 # XXX
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
for byte in iterbytes(data):
|
||||
self.queue.put(byte, timeout=self._write_timeout)
|
||||
return len(data)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('reset_input_buffer()')
|
||||
try:
|
||||
while self.queue.qsize():
|
||||
self.queue.get_nowait()
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('reset_output_buffer()')
|
||||
try:
|
||||
while self.queue.qsize():
|
||||
self.queue.get_nowait()
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
@property
|
||||
def out_waiting(self):
|
||||
"""Return how many bytes the in the outgoing buffer"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
# attention the logged value can differ from return value in
|
||||
# threaded environments...
|
||||
self.logger.debug('out_waiting -> {:d}'.format(self.queue.qsize()))
|
||||
return self.queue.qsize()
|
||||
|
||||
def _update_break_state(self):
|
||||
"""\
|
||||
Set break: Controls TXD. When active, to transmitting is
|
||||
possible.
|
||||
"""
|
||||
if self.logger:
|
||||
self.logger.info('_update_break_state({!r})'.format(self._break_state))
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self.logger:
|
||||
self.logger.info('_update_rts_state({!r}) -> state of CTS'.format(self._rts_state))
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self.logger:
|
||||
self.logger.info('_update_dtr_state({!r}) -> state of DSR'.format(self._dtr_state))
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('CTS -> state of RTS ({!r})'.format(self._rts_state))
|
||||
return self._rts_state
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if self.logger:
|
||||
self.logger.info('DSR -> state of DTR ({!r})'.format(self._dtr_state))
|
||||
return self._dtr_state
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for RI')
|
||||
return False
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for CD')
|
||||
return True
|
||||
|
||||
# - - - platform specific - - -
|
||||
# None so far
|
||||
|
||||
|
||||
# simple client test
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
s = Serial('loop://')
|
||||
sys.stdout.write('{}\n'.format(s))
|
||||
|
||||
sys.stdout.write("write...\n")
|
||||
s.write("hello\n")
|
||||
s.flush()
|
||||
sys.stdout.write("read: {!r}\n".format(s.read(5)))
|
||||
|
||||
s.close()
|
||||
@@ -1,12 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This is a thin wrapper to load the rfc2217 implementation.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from serial.rfc2217 import Serial # noqa
|
||||
@@ -1,359 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a simple socket based client.
|
||||
# It does not support changing any port parameters and will silently ignore any
|
||||
# requests to do so.
|
||||
#
|
||||
# The purpose of this module is that applications using pySerial can connect to
|
||||
# TCP/IP to serial port converters that do not support RFC 2217.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: socket://<host>:<port>[/option[/option...]]
|
||||
# options:
|
||||
# - "debug" print diagnostic messages
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, \
|
||||
PortNotOpenError, SerialTimeoutException, Timeout
|
||||
|
||||
# map log level names to constants. used in from_url()
|
||||
LOGGER_LEVELS = {
|
||||
'debug': logging.DEBUG,
|
||||
'info': logging.INFO,
|
||||
'warning': logging.WARNING,
|
||||
'error': logging.ERROR,
|
||||
}
|
||||
|
||||
POLL_TIMEOUT = 5
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation for plain sockets."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
self.logger = None
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
try:
|
||||
# timeout is used for write timeout support :/ and to get an initial connection timeout
|
||||
self._socket = socket.create_connection(self.from_url(self.portstr), timeout=POLL_TIMEOUT)
|
||||
except Exception as msg:
|
||||
self._socket = None
|
||||
raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
|
||||
# after connecting, switch to non-blocking, we're using select
|
||||
self._socket.setblocking(False)
|
||||
|
||||
# not that there is anything to configure...
|
||||
self._reconfigure_port()
|
||||
# all things set up get, now a clean start
|
||||
self.is_open = True
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
self.reset_input_buffer()
|
||||
self.reset_output_buffer()
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""\
|
||||
Set communication parameters on opened port. For the socket://
|
||||
protocol all settings are ignored!
|
||||
"""
|
||||
if self._socket is None:
|
||||
raise SerialException("Can only operate on open ports")
|
||||
if self.logger:
|
||||
self.logger.info('ignored port configuration change')
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self._socket:
|
||||
try:
|
||||
self._socket.shutdown(socket.SHUT_RDWR)
|
||||
self._socket.close()
|
||||
except:
|
||||
# ignore errors.
|
||||
pass
|
||||
self._socket = None
|
||||
self.is_open = False
|
||||
# in case of quick reconnects, give the server some time
|
||||
time.sleep(0.3)
|
||||
|
||||
def from_url(self, url):
|
||||
"""extract host and port from an URL string"""
|
||||
parts = urlparse.urlsplit(url)
|
||||
if parts.scheme != "socket":
|
||||
raise SerialException(
|
||||
'expected a string in the form '
|
||||
'"socket://<host>:<port>[?logging={debug|info|warning|error}]": '
|
||||
'not starting with socket:// ({!r})'.format(parts.scheme))
|
||||
try:
|
||||
# process options now, directly altering self
|
||||
for option, values in urlparse.parse_qs(parts.query, True).items():
|
||||
if option == 'logging':
|
||||
logging.basicConfig() # XXX is that good to call it here?
|
||||
self.logger = logging.getLogger('pySerial.socket')
|
||||
self.logger.setLevel(LOGGER_LEVELS[values[0]])
|
||||
self.logger.debug('enabled logging')
|
||||
else:
|
||||
raise ValueError('unknown option: {!r}'.format(option))
|
||||
if not 0 <= parts.port < 65536:
|
||||
raise ValueError("port not in range 0...65535")
|
||||
except ValueError as e:
|
||||
raise SerialException(
|
||||
'expected a string in the form '
|
||||
'"socket://<host>:<port>[?logging={debug|info|warning|error}]": {}'.format(e))
|
||||
|
||||
return (parts.hostname, parts.port)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
# Poll the socket to see if it is ready for reading.
|
||||
# If ready, at least one byte will be to read.
|
||||
lr, lw, lx = select.select([self._socket], [], [], 0)
|
||||
return len(lr)
|
||||
|
||||
# select based implementation, similar to posix, but only using socket API
|
||||
# to be portable, additionally handle socket timeout which is used to
|
||||
# emulate write timeouts
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
read = bytearray()
|
||||
timeout = Timeout(self._timeout)
|
||||
while len(read) < size:
|
||||
try:
|
||||
ready, _, _ = select.select([self._socket], [], [], timeout.time_left())
|
||||
# If select was used with a timeout, and the timeout occurs, it
|
||||
# returns with empty lists -> thus abort read operation.
|
||||
# For timeout == 0 (non-blocking operation) also abort when
|
||||
# there is nothing to read.
|
||||
if not ready:
|
||||
break # timeout
|
||||
buf = self._socket.recv(size - len(read))
|
||||
# read should always return some data as select reported it was
|
||||
# ready to read when we get to this point, unless it is EOF
|
||||
if not buf:
|
||||
raise SerialException('socket disconnected')
|
||||
read.extend(buf)
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
except (select.error, socket.error) as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
if timeout.expired():
|
||||
break
|
||||
return bytes(read)
|
||||
|
||||
def write(self, data):
|
||||
"""\
|
||||
Output the given byte string over the serial port. Can block if the
|
||||
connection is blocked. May raise SerialException if the connection is
|
||||
closed.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
|
||||
d = to_bytes(data)
|
||||
tx_len = length = len(d)
|
||||
timeout = Timeout(self._write_timeout)
|
||||
while tx_len > 0:
|
||||
try:
|
||||
n = self._socket.send(d)
|
||||
if timeout.is_non_blocking:
|
||||
# Zero timeout indicates non-blocking - simply return the
|
||||
# number of bytes of data actually written
|
||||
return n
|
||||
elif not timeout.is_infinite:
|
||||
# when timeout is set, use select to wait for being ready
|
||||
# with the time left as timeout
|
||||
if timeout.expired():
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
_, ready, _ = select.select([], [self._socket], [], timeout.time_left())
|
||||
if not ready:
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
else:
|
||||
assert timeout.time_left() is None
|
||||
# wait for write operation
|
||||
_, ready, _ = select.select([], [self._socket], [], None)
|
||||
if not ready:
|
||||
raise SerialException('write failed (select)')
|
||||
d = d[n:]
|
||||
tx_len -= n
|
||||
except SerialException:
|
||||
raise
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('write failed: {}'.format(e))
|
||||
except select.error as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('write failed: {}'.format(e))
|
||||
if not timeout.is_non_blocking and timeout.expired():
|
||||
raise SerialTimeoutException('Write timeout')
|
||||
return length - len(d)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
|
||||
# just use recv to remove input, while there is some
|
||||
ready = True
|
||||
while ready:
|
||||
ready, _, _ = select.select([self._socket], [], [], 0)
|
||||
try:
|
||||
if ready:
|
||||
ready = self._socket.recv(4096)
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
except (select.error, socket.error) as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored reset_output_buffer')
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""\
|
||||
Send break condition. Timed, returns to idle state after given
|
||||
duration.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('ignored send_break({!r})'.format(duration))
|
||||
|
||||
def _update_break_state(self):
|
||||
"""Set break: Controls TXD. When active, to transmitting is
|
||||
possible."""
|
||||
if self.logger:
|
||||
self.logger.info('ignored _update_break_state({!r})'.format(self._break_state))
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self.logger:
|
||||
self.logger.info('ignored _update_rts_state({!r})'.format(self._rts_state))
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self.logger:
|
||||
self.logger.info('ignored _update_dtr_state({!r})'.format(self._dtr_state))
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for cts')
|
||||
return True
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for dsr')
|
||||
return True
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for ri')
|
||||
return False
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise PortNotOpenError()
|
||||
if self.logger:
|
||||
self.logger.info('returning dummy for cd)')
|
||||
return True
|
||||
|
||||
# - - - platform specific - - -
|
||||
|
||||
# works on Linux and probably all the other POSIX systems
|
||||
def fileno(self):
|
||||
"""Get the file handle of the underlying socket for use with select"""
|
||||
return self._socket.fileno()
|
||||
|
||||
|
||||
#
|
||||
# simple client test
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
s = Serial('socket://localhost:7000')
|
||||
sys.stdout.write('{}\n'.format(s))
|
||||
|
||||
sys.stdout.write("write...\n")
|
||||
s.write(b"hello\n")
|
||||
s.flush()
|
||||
sys.stdout.write("read: {}\n".format(s.read(5)))
|
||||
|
||||
s.close()
|
||||
@@ -1,290 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# This module implements a special URL handler that wraps an other port,
|
||||
# print the traffic for debugging purposes. With this, it is possible
|
||||
# to debug the serial port traffic on every application that uses
|
||||
# serial_for_url.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# URL format: spy://port[?option[=value][&option[=value]]]
|
||||
# options:
|
||||
# - dev=X a file or device to write to
|
||||
# - color use escape code to colorize output
|
||||
# - raw forward raw bytes instead of hexdump
|
||||
#
|
||||
# example:
|
||||
# redirect output to an other terminal window on Posix (Linux):
|
||||
# python -m serial.tools.miniterm spy:///dev/ttyUSB0?dev=/dev/pts/14\&color
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
import serial
|
||||
from serial.serialutil import to_bytes
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
|
||||
def sixteen(data):
|
||||
"""\
|
||||
yield tuples of hex and ASCII display in multiples of 16. Includes a
|
||||
space after 8 bytes and (None, None) after 16 bytes and at the end.
|
||||
"""
|
||||
n = 0
|
||||
for b in serial.iterbytes(data):
|
||||
yield ('{:02X} '.format(ord(b)), b.decode('ascii') if b' ' <= b < b'\x7f' else '.')
|
||||
n += 1
|
||||
if n == 8:
|
||||
yield (' ', '')
|
||||
elif n >= 16:
|
||||
yield (None, None)
|
||||
n = 0
|
||||
if n > 0:
|
||||
while n < 16:
|
||||
n += 1
|
||||
if n == 8:
|
||||
yield (' ', '')
|
||||
yield (' ', ' ')
|
||||
yield (None, None)
|
||||
|
||||
|
||||
def hexdump(data):
|
||||
"""yield lines with hexdump of data"""
|
||||
values = []
|
||||
ascii = []
|
||||
offset = 0
|
||||
for h, a in sixteen(data):
|
||||
if h is None:
|
||||
yield (offset, ' '.join([''.join(values), ''.join(ascii)]))
|
||||
del values[:]
|
||||
del ascii[:]
|
||||
offset += 0x10
|
||||
else:
|
||||
values.append(h)
|
||||
ascii.append(a)
|
||||
|
||||
|
||||
class FormatRaw(object):
|
||||
"""Forward only RX and TX data to output."""
|
||||
|
||||
def __init__(self, output, color):
|
||||
self.output = output
|
||||
self.color = color
|
||||
self.rx_color = '\x1b[32m'
|
||||
self.tx_color = '\x1b[31m'
|
||||
|
||||
def rx(self, data):
|
||||
"""show received data"""
|
||||
if self.color:
|
||||
self.output.write(self.rx_color)
|
||||
self.output.write(data)
|
||||
self.output.flush()
|
||||
|
||||
def tx(self, data):
|
||||
"""show transmitted data"""
|
||||
if self.color:
|
||||
self.output.write(self.tx_color)
|
||||
self.output.write(data)
|
||||
self.output.flush()
|
||||
|
||||
def control(self, name, value):
|
||||
"""(do not) show control calls"""
|
||||
pass
|
||||
|
||||
|
||||
class FormatHexdump(object):
|
||||
"""\
|
||||
Create a hex dump of RX ad TX data, show when control lines are read or
|
||||
written.
|
||||
|
||||
output example::
|
||||
|
||||
000000.000 Q-RX flushInput
|
||||
000002.469 RTS inactive
|
||||
000002.773 RTS active
|
||||
000003.001 TX 48 45 4C 4C 4F HELLO
|
||||
000003.102 RX 48 45 4C 4C 4F HELLO
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, output, color):
|
||||
self.start_time = time.time()
|
||||
self.output = output
|
||||
self.color = color
|
||||
self.rx_color = '\x1b[32m'
|
||||
self.tx_color = '\x1b[31m'
|
||||
self.control_color = '\x1b[37m'
|
||||
|
||||
def write_line(self, timestamp, label, value, value2=''):
|
||||
self.output.write('{:010.3f} {:4} {}{}\n'.format(timestamp, label, value, value2))
|
||||
self.output.flush()
|
||||
|
||||
def rx(self, data):
|
||||
"""show received data as hex dump"""
|
||||
if self.color:
|
||||
self.output.write(self.rx_color)
|
||||
if data:
|
||||
for offset, row in hexdump(data):
|
||||
self.write_line(time.time() - self.start_time, 'RX', '{:04X} '.format(offset), row)
|
||||
else:
|
||||
self.write_line(time.time() - self.start_time, 'RX', '<empty>')
|
||||
|
||||
def tx(self, data):
|
||||
"""show transmitted data as hex dump"""
|
||||
if self.color:
|
||||
self.output.write(self.tx_color)
|
||||
for offset, row in hexdump(data):
|
||||
self.write_line(time.time() - self.start_time, 'TX', '{:04X} '.format(offset), row)
|
||||
|
||||
def control(self, name, value):
|
||||
"""show control calls"""
|
||||
if self.color:
|
||||
self.output.write(self.control_color)
|
||||
self.write_line(time.time() - self.start_time, name, value)
|
||||
|
||||
|
||||
class 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)
|
||||
@@ -1,366 +0,0 @@
|
||||
#! python
|
||||
#
|
||||
# Constants and types for use with Windows API, used by serialwin32.py
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# pylint: disable=invalid-name,too-few-public-methods,protected-access,too-many-instance-attributes
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from ctypes import c_ulong, c_void_p, c_int64, c_char, \
|
||||
WinDLL, sizeof, Structure, Union, POINTER
|
||||
from ctypes.wintypes import HANDLE
|
||||
from ctypes.wintypes import BOOL
|
||||
from ctypes.wintypes import LPCWSTR
|
||||
from ctypes.wintypes import DWORD
|
||||
from ctypes.wintypes import WORD
|
||||
from ctypes.wintypes import BYTE
|
||||
_stdcall_libraries = {}
|
||||
_stdcall_libraries['kernel32'] = WinDLL('kernel32')
|
||||
|
||||
INVALID_HANDLE_VALUE = HANDLE(-1).value
|
||||
|
||||
|
||||
# some details of the windows API differ between 32 and 64 bit systems..
|
||||
def is_64bit():
|
||||
"""Returns true when running on a 64 bit system"""
|
||||
return sizeof(c_ulong) != sizeof(c_void_p)
|
||||
|
||||
# ULONG_PTR is a an ordinary number, not a pointer and contrary to the name it
|
||||
# is either 32 or 64 bits, depending on the type of windows...
|
||||
# so test if this a 32 bit windows...
|
||||
if is_64bit():
|
||||
ULONG_PTR = c_int64
|
||||
else:
|
||||
ULONG_PTR = c_ulong
|
||||
|
||||
|
||||
class _SECURITY_ATTRIBUTES(Structure):
|
||||
pass
|
||||
LPSECURITY_ATTRIBUTES = POINTER(_SECURITY_ATTRIBUTES)
|
||||
|
||||
|
||||
try:
|
||||
CreateEventW = _stdcall_libraries['kernel32'].CreateEventW
|
||||
except AttributeError:
|
||||
# Fallback to non wide char version for old OS...
|
||||
from ctypes.wintypes import LPCSTR
|
||||
CreateEventA = _stdcall_libraries['kernel32'].CreateEventA
|
||||
CreateEventA.restype = HANDLE
|
||||
CreateEventA.argtypes = [LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCSTR]
|
||||
CreateEvent = CreateEventA
|
||||
|
||||
CreateFileA = _stdcall_libraries['kernel32'].CreateFileA
|
||||
CreateFileA.restype = HANDLE
|
||||
CreateFileA.argtypes = [LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE]
|
||||
CreateFile = CreateFileA
|
||||
else:
|
||||
CreateEventW.restype = HANDLE
|
||||
CreateEventW.argtypes = [LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCWSTR]
|
||||
CreateEvent = CreateEventW # alias
|
||||
|
||||
CreateFileW = _stdcall_libraries['kernel32'].CreateFileW
|
||||
CreateFileW.restype = HANDLE
|
||||
CreateFileW.argtypes = [LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE]
|
||||
CreateFile = CreateFileW # alias
|
||||
|
||||
|
||||
class _OVERLAPPED(Structure):
|
||||
pass
|
||||
|
||||
OVERLAPPED = _OVERLAPPED
|
||||
|
||||
|
||||
class _COMSTAT(Structure):
|
||||
pass
|
||||
|
||||
COMSTAT = _COMSTAT
|
||||
|
||||
|
||||
class _DCB(Structure):
|
||||
pass
|
||||
|
||||
DCB = _DCB
|
||||
|
||||
|
||||
class _COMMTIMEOUTS(Structure):
|
||||
pass
|
||||
|
||||
COMMTIMEOUTS = _COMMTIMEOUTS
|
||||
|
||||
GetLastError = _stdcall_libraries['kernel32'].GetLastError
|
||||
GetLastError.restype = DWORD
|
||||
GetLastError.argtypes = []
|
||||
|
||||
LPOVERLAPPED = POINTER(_OVERLAPPED)
|
||||
LPDWORD = POINTER(DWORD)
|
||||
|
||||
GetOverlappedResult = _stdcall_libraries['kernel32'].GetOverlappedResult
|
||||
GetOverlappedResult.restype = BOOL
|
||||
GetOverlappedResult.argtypes = [HANDLE, LPOVERLAPPED, LPDWORD, BOOL]
|
||||
|
||||
ResetEvent = _stdcall_libraries['kernel32'].ResetEvent
|
||||
ResetEvent.restype = BOOL
|
||||
ResetEvent.argtypes = [HANDLE]
|
||||
|
||||
LPCVOID = c_void_p
|
||||
|
||||
WriteFile = _stdcall_libraries['kernel32'].WriteFile
|
||||
WriteFile.restype = BOOL
|
||||
WriteFile.argtypes = [HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED]
|
||||
|
||||
LPVOID = c_void_p
|
||||
|
||||
ReadFile = _stdcall_libraries['kernel32'].ReadFile
|
||||
ReadFile.restype = BOOL
|
||||
ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED]
|
||||
|
||||
CloseHandle = _stdcall_libraries['kernel32'].CloseHandle
|
||||
CloseHandle.restype = BOOL
|
||||
CloseHandle.argtypes = [HANDLE]
|
||||
|
||||
ClearCommBreak = _stdcall_libraries['kernel32'].ClearCommBreak
|
||||
ClearCommBreak.restype = BOOL
|
||||
ClearCommBreak.argtypes = [HANDLE]
|
||||
|
||||
LPCOMSTAT = POINTER(_COMSTAT)
|
||||
|
||||
ClearCommError = _stdcall_libraries['kernel32'].ClearCommError
|
||||
ClearCommError.restype = BOOL
|
||||
ClearCommError.argtypes = [HANDLE, LPDWORD, LPCOMSTAT]
|
||||
|
||||
SetupComm = _stdcall_libraries['kernel32'].SetupComm
|
||||
SetupComm.restype = BOOL
|
||||
SetupComm.argtypes = [HANDLE, DWORD, DWORD]
|
||||
|
||||
EscapeCommFunction = _stdcall_libraries['kernel32'].EscapeCommFunction
|
||||
EscapeCommFunction.restype = BOOL
|
||||
EscapeCommFunction.argtypes = [HANDLE, DWORD]
|
||||
|
||||
GetCommModemStatus = _stdcall_libraries['kernel32'].GetCommModemStatus
|
||||
GetCommModemStatus.restype = BOOL
|
||||
GetCommModemStatus.argtypes = [HANDLE, LPDWORD]
|
||||
|
||||
LPDCB = POINTER(_DCB)
|
||||
|
||||
GetCommState = _stdcall_libraries['kernel32'].GetCommState
|
||||
GetCommState.restype = BOOL
|
||||
GetCommState.argtypes = [HANDLE, LPDCB]
|
||||
|
||||
LPCOMMTIMEOUTS = POINTER(_COMMTIMEOUTS)
|
||||
|
||||
GetCommTimeouts = _stdcall_libraries['kernel32'].GetCommTimeouts
|
||||
GetCommTimeouts.restype = BOOL
|
||||
GetCommTimeouts.argtypes = [HANDLE, LPCOMMTIMEOUTS]
|
||||
|
||||
PurgeComm = _stdcall_libraries['kernel32'].PurgeComm
|
||||
PurgeComm.restype = BOOL
|
||||
PurgeComm.argtypes = [HANDLE, DWORD]
|
||||
|
||||
SetCommBreak = _stdcall_libraries['kernel32'].SetCommBreak
|
||||
SetCommBreak.restype = BOOL
|
||||
SetCommBreak.argtypes = [HANDLE]
|
||||
|
||||
SetCommMask = _stdcall_libraries['kernel32'].SetCommMask
|
||||
SetCommMask.restype = BOOL
|
||||
SetCommMask.argtypes = [HANDLE, DWORD]
|
||||
|
||||
SetCommState = _stdcall_libraries['kernel32'].SetCommState
|
||||
SetCommState.restype = BOOL
|
||||
SetCommState.argtypes = [HANDLE, LPDCB]
|
||||
|
||||
SetCommTimeouts = _stdcall_libraries['kernel32'].SetCommTimeouts
|
||||
SetCommTimeouts.restype = BOOL
|
||||
SetCommTimeouts.argtypes = [HANDLE, LPCOMMTIMEOUTS]
|
||||
|
||||
WaitForSingleObject = _stdcall_libraries['kernel32'].WaitForSingleObject
|
||||
WaitForSingleObject.restype = DWORD
|
||||
WaitForSingleObject.argtypes = [HANDLE, DWORD]
|
||||
|
||||
WaitCommEvent = _stdcall_libraries['kernel32'].WaitCommEvent
|
||||
WaitCommEvent.restype = BOOL
|
||||
WaitCommEvent.argtypes = [HANDLE, LPDWORD, LPOVERLAPPED]
|
||||
|
||||
CancelIoEx = _stdcall_libraries['kernel32'].CancelIoEx
|
||||
CancelIoEx.restype = BOOL
|
||||
CancelIoEx.argtypes = [HANDLE, LPOVERLAPPED]
|
||||
|
||||
ONESTOPBIT = 0 # Variable c_int
|
||||
TWOSTOPBITS = 2 # Variable c_int
|
||||
ONE5STOPBITS = 1
|
||||
|
||||
NOPARITY = 0 # Variable c_int
|
||||
ODDPARITY = 1 # Variable c_int
|
||||
EVENPARITY = 2 # Variable c_int
|
||||
MARKPARITY = 3
|
||||
SPACEPARITY = 4
|
||||
|
||||
RTS_CONTROL_HANDSHAKE = 2 # Variable c_int
|
||||
RTS_CONTROL_DISABLE = 0 # Variable c_int
|
||||
RTS_CONTROL_ENABLE = 1 # Variable c_int
|
||||
RTS_CONTROL_TOGGLE = 3 # Variable c_int
|
||||
SETRTS = 3
|
||||
CLRRTS = 4
|
||||
|
||||
DTR_CONTROL_HANDSHAKE = 2 # Variable c_int
|
||||
DTR_CONTROL_DISABLE = 0 # Variable c_int
|
||||
DTR_CONTROL_ENABLE = 1 # Variable c_int
|
||||
SETDTR = 5
|
||||
CLRDTR = 6
|
||||
|
||||
MS_DSR_ON = 32 # Variable c_ulong
|
||||
EV_RING = 256 # Variable c_int
|
||||
EV_PERR = 512 # Variable c_int
|
||||
EV_ERR = 128 # Variable c_int
|
||||
SETXOFF = 1 # Variable c_int
|
||||
EV_RXCHAR = 1 # Variable c_int
|
||||
GENERIC_WRITE = 1073741824 # Variable c_long
|
||||
PURGE_TXCLEAR = 4 # Variable c_int
|
||||
FILE_FLAG_OVERLAPPED = 1073741824 # Variable c_int
|
||||
EV_DSR = 16 # Variable c_int
|
||||
MAXDWORD = 4294967295 # Variable c_uint
|
||||
EV_RLSD = 32 # Variable c_int
|
||||
|
||||
ERROR_SUCCESS = 0
|
||||
ERROR_NOT_ENOUGH_MEMORY = 8
|
||||
ERROR_OPERATION_ABORTED = 995
|
||||
ERROR_IO_INCOMPLETE = 996
|
||||
ERROR_IO_PENDING = 997 # Variable c_long
|
||||
ERROR_INVALID_USER_BUFFER = 1784
|
||||
|
||||
MS_CTS_ON = 16 # Variable c_ulong
|
||||
EV_EVENT1 = 2048 # Variable c_int
|
||||
EV_RX80FULL = 1024 # Variable c_int
|
||||
PURGE_RXABORT = 2 # Variable c_int
|
||||
FILE_ATTRIBUTE_NORMAL = 128 # Variable c_int
|
||||
PURGE_TXABORT = 1 # Variable c_int
|
||||
SETXON = 2 # Variable c_int
|
||||
OPEN_EXISTING = 3 # Variable c_int
|
||||
MS_RING_ON = 64 # Variable c_ulong
|
||||
EV_TXEMPTY = 4 # Variable c_int
|
||||
EV_RXFLAG = 2 # Variable c_int
|
||||
MS_RLSD_ON = 128 # Variable c_ulong
|
||||
GENERIC_READ = 2147483648 # Variable c_ulong
|
||||
EV_EVENT2 = 4096 # Variable c_int
|
||||
EV_CTS = 8 # Variable c_int
|
||||
EV_BREAK = 64 # Variable c_int
|
||||
PURGE_RXCLEAR = 8 # Variable c_int
|
||||
INFINITE = 0xFFFFFFFF
|
||||
|
||||
CE_RXOVER = 0x0001
|
||||
CE_OVERRUN = 0x0002
|
||||
CE_RXPARITY = 0x0004
|
||||
CE_FRAME = 0x0008
|
||||
CE_BREAK = 0x0010
|
||||
|
||||
|
||||
class N11_OVERLAPPED4DOLLAR_48E(Union):
|
||||
pass
|
||||
|
||||
|
||||
class N11_OVERLAPPED4DOLLAR_484DOLLAR_49E(Structure):
|
||||
pass
|
||||
|
||||
|
||||
N11_OVERLAPPED4DOLLAR_484DOLLAR_49E._fields_ = [
|
||||
('Offset', DWORD),
|
||||
('OffsetHigh', DWORD),
|
||||
]
|
||||
|
||||
PVOID = c_void_p
|
||||
|
||||
N11_OVERLAPPED4DOLLAR_48E._anonymous_ = ['_0']
|
||||
N11_OVERLAPPED4DOLLAR_48E._fields_ = [
|
||||
('_0', N11_OVERLAPPED4DOLLAR_484DOLLAR_49E),
|
||||
('Pointer', PVOID),
|
||||
]
|
||||
_OVERLAPPED._anonymous_ = ['_0']
|
||||
_OVERLAPPED._fields_ = [
|
||||
('Internal', ULONG_PTR),
|
||||
('InternalHigh', ULONG_PTR),
|
||||
('_0', N11_OVERLAPPED4DOLLAR_48E),
|
||||
('hEvent', HANDLE),
|
||||
]
|
||||
_SECURITY_ATTRIBUTES._fields_ = [
|
||||
('nLength', DWORD),
|
||||
('lpSecurityDescriptor', LPVOID),
|
||||
('bInheritHandle', BOOL),
|
||||
]
|
||||
_COMSTAT._fields_ = [
|
||||
('fCtsHold', DWORD, 1),
|
||||
('fDsrHold', DWORD, 1),
|
||||
('fRlsdHold', DWORD, 1),
|
||||
('fXoffHold', DWORD, 1),
|
||||
('fXoffSent', DWORD, 1),
|
||||
('fEof', DWORD, 1),
|
||||
('fTxim', DWORD, 1),
|
||||
('fReserved', DWORD, 25),
|
||||
('cbInQue', DWORD),
|
||||
('cbOutQue', DWORD),
|
||||
]
|
||||
_DCB._fields_ = [
|
||||
('DCBlength', DWORD),
|
||||
('BaudRate', DWORD),
|
||||
('fBinary', DWORD, 1),
|
||||
('fParity', DWORD, 1),
|
||||
('fOutxCtsFlow', DWORD, 1),
|
||||
('fOutxDsrFlow', DWORD, 1),
|
||||
('fDtrControl', DWORD, 2),
|
||||
('fDsrSensitivity', DWORD, 1),
|
||||
('fTXContinueOnXoff', DWORD, 1),
|
||||
('fOutX', DWORD, 1),
|
||||
('fInX', DWORD, 1),
|
||||
('fErrorChar', DWORD, 1),
|
||||
('fNull', DWORD, 1),
|
||||
('fRtsControl', DWORD, 2),
|
||||
('fAbortOnError', DWORD, 1),
|
||||
('fDummy2', DWORD, 17),
|
||||
('wReserved', WORD),
|
||||
('XonLim', WORD),
|
||||
('XoffLim', WORD),
|
||||
('ByteSize', BYTE),
|
||||
('Parity', BYTE),
|
||||
('StopBits', BYTE),
|
||||
('XonChar', c_char),
|
||||
('XoffChar', c_char),
|
||||
('ErrorChar', c_char),
|
||||
('EofChar', c_char),
|
||||
('EvtChar', c_char),
|
||||
('wReserved1', WORD),
|
||||
]
|
||||
_COMMTIMEOUTS._fields_ = [
|
||||
('ReadIntervalTimeout', DWORD),
|
||||
('ReadTotalTimeoutMultiplier', DWORD),
|
||||
('ReadTotalTimeoutConstant', DWORD),
|
||||
('WriteTotalTimeoutMultiplier', DWORD),
|
||||
('WriteTotalTimeoutConstant', DWORD),
|
||||
]
|
||||
__all__ = ['GetLastError', 'MS_CTS_ON', 'FILE_ATTRIBUTE_NORMAL',
|
||||
'DTR_CONTROL_ENABLE', '_COMSTAT', 'MS_RLSD_ON',
|
||||
'GetOverlappedResult', 'SETXON', 'PURGE_TXABORT',
|
||||
'PurgeComm', 'N11_OVERLAPPED4DOLLAR_48E', 'EV_RING',
|
||||
'ONESTOPBIT', 'SETXOFF', 'PURGE_RXABORT', 'GetCommState',
|
||||
'RTS_CONTROL_ENABLE', '_DCB', 'CreateEvent',
|
||||
'_COMMTIMEOUTS', '_SECURITY_ATTRIBUTES', 'EV_DSR',
|
||||
'EV_PERR', 'EV_RXFLAG', 'OPEN_EXISTING', 'DCB',
|
||||
'FILE_FLAG_OVERLAPPED', 'EV_CTS', 'SetupComm',
|
||||
'LPOVERLAPPED', 'EV_TXEMPTY', 'ClearCommBreak',
|
||||
'LPSECURITY_ATTRIBUTES', 'SetCommBreak', 'SetCommTimeouts',
|
||||
'COMMTIMEOUTS', 'ODDPARITY', 'EV_RLSD',
|
||||
'GetCommModemStatus', 'EV_EVENT2', 'PURGE_TXCLEAR',
|
||||
'EV_BREAK', 'EVENPARITY', 'LPCVOID', 'COMSTAT', 'ReadFile',
|
||||
'PVOID', '_OVERLAPPED', 'WriteFile', 'GetCommTimeouts',
|
||||
'ResetEvent', 'EV_RXCHAR', 'LPCOMSTAT', 'ClearCommError',
|
||||
'ERROR_IO_PENDING', 'EscapeCommFunction', 'GENERIC_READ',
|
||||
'RTS_CONTROL_HANDSHAKE', 'OVERLAPPED',
|
||||
'DTR_CONTROL_HANDSHAKE', 'PURGE_RXCLEAR', 'GENERIC_WRITE',
|
||||
'LPDCB', 'CreateEventW', 'SetCommMask', 'EV_EVENT1',
|
||||
'SetCommState', 'LPVOID', 'CreateFileW', 'LPDWORD',
|
||||
'EV_RX80FULL', 'TWOSTOPBITS', 'LPCOMMTIMEOUTS', 'MAXDWORD',
|
||||
'MS_DSR_ON', 'MS_RING_ON',
|
||||
'N11_OVERLAPPED4DOLLAR_484DOLLAR_49E', 'EV_ERR',
|
||||
'ULONG_PTR', 'CreateFile', 'NOPARITY', 'CloseHandle']
|
||||
24
tools/python/ampy_main.py
Normal file
24
tools/python/ampy_main.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import sys
|
||||
from ampy.cli import cli, _board
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
error_exit = False
|
||||
try:
|
||||
cli()
|
||||
except BaseException as e:
|
||||
if getattr(e, 'code', True):
|
||||
print('Error: {}'.format(e))
|
||||
error_exit = True
|
||||
finally:
|
||||
# Try to ensure the board serial connection is always gracefully closed.
|
||||
if _board is not None:
|
||||
try:
|
||||
_board.close()
|
||||
except:
|
||||
# Swallow errors when attempting to close as it's just a best effort
|
||||
# and shouldn't cause a new error or problem if the connection can't
|
||||
# be closed.
|
||||
pass
|
||||
if error_exit:
|
||||
sys.exit(1)
|
||||
@@ -2,7 +2,7 @@
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
__all__ = [
|
||||
"chip_id",
|
||||
"detect_chip",
|
||||
@@ -28,7 +28,7 @@ __all__ = [
|
||||
"write_mem",
|
||||
]
|
||||
|
||||
__version__ = "4.7.0"
|
||||
__version__ = "4.8.1"
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
@@ -38,8 +38,8 @@ import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from bin_image import intel_hex_to_bin
|
||||
from cmds import (
|
||||
from esptool.bin_image import intel_hex_to_bin
|
||||
from esptool.cmds import (
|
||||
DETECTED_FLASH_SIZES,
|
||||
chip_id,
|
||||
detect_chip,
|
||||
@@ -49,6 +49,7 @@ from cmds import (
|
||||
erase_flash,
|
||||
erase_region,
|
||||
flash_id,
|
||||
read_flash_sfdp,
|
||||
get_security_info,
|
||||
image_info,
|
||||
load_ram,
|
||||
@@ -65,15 +66,22 @@ from cmds import (
|
||||
write_flash_status,
|
||||
write_mem,
|
||||
)
|
||||
from config import load_config_file
|
||||
from loader import DEFAULT_CONNECT_ATTEMPTS, ESPLoader, list_ports
|
||||
from targets import CHIP_DEFS, CHIP_LIST, ESP32ROM
|
||||
from util import (
|
||||
from esptool.config import load_config_file
|
||||
from esptool.loader import (
|
||||
DEFAULT_CONNECT_ATTEMPTS,
|
||||
DEFAULT_OPEN_PORT_ATTEMPTS,
|
||||
StubFlasher,
|
||||
ESPLoader,
|
||||
list_ports,
|
||||
)
|
||||
from esptool.targets import CHIP_DEFS, CHIP_LIST, ESP32ROM
|
||||
from esptool.util import (
|
||||
FatalError,
|
||||
NotImplementedInROMError,
|
||||
flash_size_bytes,
|
||||
strip_chip_name,
|
||||
)
|
||||
from itertools import chain, cycle, repeat
|
||||
|
||||
import serial
|
||||
|
||||
@@ -123,6 +131,14 @@ def main(argv=None, esp=None):
|
||||
default=os.environ.get("ESPTOOL_BAUD", ESPLoader.ESP_ROM_BAUD),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--port-filter",
|
||||
action="append",
|
||||
help="Serial port device filter, can be vid=NUMBER, pid=NUMBER, name=SUBSTRING",
|
||||
type=str,
|
||||
default=[],
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--before",
|
||||
help="What to do before connecting to the chip",
|
||||
@@ -145,6 +161,15 @@ def main(argv=None, esp=None):
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
# --stub-version can be set with --no-stub so the tests wouldn't fail if this option is implied globally
|
||||
parser.add_argument(
|
||||
"--stub-version",
|
||||
default=os.environ.get("ESPTOOL_STUB_VERSION", StubFlasher.STUB_SUBDIRS[0]),
|
||||
choices=StubFlasher.STUB_SUBDIRS,
|
||||
# not a public option and is not subject to the semantic versioning policy
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--trace",
|
||||
"-t",
|
||||
@@ -217,7 +242,12 @@ def main(argv=None, esp=None):
|
||||
default="0xFFFFFFFF",
|
||||
)
|
||||
|
||||
def add_spi_flash_subparsers(parent, allow_keep, auto_detect):
|
||||
def add_spi_flash_subparsers(
|
||||
parent: argparse.ArgumentParser,
|
||||
allow_keep: bool,
|
||||
auto_detect: bool,
|
||||
size_only: bool = False,
|
||||
):
|
||||
"""Add common parser arguments for SPI flash properties"""
|
||||
extra_keep_args = ["keep"] if allow_keep else []
|
||||
|
||||
@@ -234,33 +264,35 @@ def main(argv=None, esp=None):
|
||||
extra_fs_message = ""
|
||||
flash_sizes = []
|
||||
|
||||
parent.add_argument(
|
||||
"--flash_freq",
|
||||
"-ff",
|
||||
help="SPI Flash frequency",
|
||||
choices=extra_keep_args
|
||||
+ [
|
||||
"80m",
|
||||
"60m",
|
||||
"48m",
|
||||
"40m",
|
||||
"30m",
|
||||
"26m",
|
||||
"24m",
|
||||
"20m",
|
||||
"16m",
|
||||
"15m",
|
||||
"12m",
|
||||
],
|
||||
default=os.environ.get("ESPTOOL_FF", "keep" if allow_keep else None),
|
||||
)
|
||||
parent.add_argument(
|
||||
"--flash_mode",
|
||||
"-fm",
|
||||
help="SPI Flash mode",
|
||||
choices=extra_keep_args + ["qio", "qout", "dio", "dout"],
|
||||
default=os.environ.get("ESPTOOL_FM", "keep" if allow_keep else "qio"),
|
||||
)
|
||||
if not size_only:
|
||||
parent.add_argument(
|
||||
"--flash_freq",
|
||||
"-ff",
|
||||
help="SPI Flash frequency",
|
||||
choices=extra_keep_args
|
||||
+ [
|
||||
"80m",
|
||||
"60m",
|
||||
"48m",
|
||||
"40m",
|
||||
"30m",
|
||||
"26m",
|
||||
"24m",
|
||||
"20m",
|
||||
"16m",
|
||||
"15m",
|
||||
"12m",
|
||||
],
|
||||
default=os.environ.get("ESPTOOL_FF", "keep" if allow_keep else None),
|
||||
)
|
||||
parent.add_argument(
|
||||
"--flash_mode",
|
||||
"-fm",
|
||||
help="SPI Flash mode",
|
||||
choices=extra_keep_args + ["qio", "qout", "dio", "dout"],
|
||||
default=os.environ.get("ESPTOOL_FM", "keep" if allow_keep else "qio"),
|
||||
)
|
||||
|
||||
parent.add_argument(
|
||||
"--flash_size",
|
||||
"-fs",
|
||||
@@ -468,7 +500,7 @@ def main(argv=None, esp=None):
|
||||
parser_elf2image.add_argument(
|
||||
"--use_segments",
|
||||
help="If set, ELF segments will be used instead of ELF sections "
|
||||
"to genereate the image.",
|
||||
"to generate the image.",
|
||||
action="store_true",
|
||||
)
|
||||
parser_elf2image.add_argument(
|
||||
@@ -489,7 +521,7 @@ def main(argv=None, esp=None):
|
||||
"quantity. This will make the other segments invisible to the ROM "
|
||||
"loader. Use this argument with care because the ROM loader will load "
|
||||
"only the RAM segments although the other segments being present in "
|
||||
"the output.",
|
||||
"the output. Implies --dont-append-digest",
|
||||
action="store_true",
|
||||
default=None,
|
||||
)
|
||||
@@ -540,7 +572,9 @@ def main(argv=None, esp=None):
|
||||
parser_read_flash = subparsers.add_parser(
|
||||
"read_flash", help="Read SPI flash content"
|
||||
)
|
||||
add_spi_connection_arg(parser_read_flash)
|
||||
add_spi_flash_subparsers(
|
||||
parser_read_flash, allow_keep=True, auto_detect=True, size_only=True
|
||||
)
|
||||
parser_read_flash.add_argument("address", help="Start address", type=arg_auto_int)
|
||||
parser_read_flash.add_argument(
|
||||
"size",
|
||||
@@ -594,6 +628,14 @@ def main(argv=None, esp=None):
|
||||
type=arg_auto_size,
|
||||
)
|
||||
|
||||
parser_read_flash_sfdp = subparsers.add_parser(
|
||||
"read_flash_sfdp",
|
||||
help="Read SPI flash SFDP (Serial Flash Discoverable Parameters)",
|
||||
)
|
||||
add_spi_flash_subparsers(parser_read_flash_sfdp, allow_keep=True, auto_detect=True)
|
||||
parser_read_flash_sfdp.add_argument("addr", type=arg_auto_int)
|
||||
parser_read_flash_sfdp.add_argument("bytes", type=int)
|
||||
|
||||
parser_merge_bin = subparsers.add_parser(
|
||||
"merge_bin",
|
||||
help="Merge multiple raw binary files into a single file for later flashing",
|
||||
@@ -664,12 +706,40 @@ def main(argv=None, esp=None):
|
||||
for operation in subparsers.choices.keys():
|
||||
assert operation in globals(), "%s should be a module function" % operation
|
||||
|
||||
# Enable argcomplete only on Unix-like systems
|
||||
if sys.platform != "win32":
|
||||
try:
|
||||
import argcomplete
|
||||
|
||||
argcomplete.autocomplete(parser)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
argv = expand_file_arguments(argv or sys.argv[1:])
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
print("esptool.py v%s" % __version__)
|
||||
load_config_file(verbose=True)
|
||||
|
||||
StubFlasher.set_preferred_stub_subdir(args.stub_version)
|
||||
|
||||
# Parse filter arguments into separate lists
|
||||
args.filterVids = []
|
||||
args.filterPids = []
|
||||
args.filterNames = []
|
||||
for f in args.port_filter:
|
||||
kvp = f.split("=")
|
||||
if len(kvp) != 2:
|
||||
raise FatalError("Option --port-filter argument must consist of key=value")
|
||||
if kvp[0] == "vid":
|
||||
args.filterVids.append(arg_auto_int(kvp[1]))
|
||||
elif kvp[0] == "pid":
|
||||
args.filterPids.append(arg_auto_int(kvp[1]))
|
||||
elif kvp[0] == "name":
|
||||
args.filterNames.append(kvp[1])
|
||||
else:
|
||||
raise FatalError("Option --port-filter argument key not recognized")
|
||||
|
||||
# operation function can take 1 arg (args), 2 args (esp, arg)
|
||||
# or be a member function of the ESPLoader class.
|
||||
|
||||
@@ -705,10 +775,31 @@ def main(argv=None, esp=None):
|
||||
initial_baud = args.baud
|
||||
|
||||
if args.port is None:
|
||||
ser_list = get_port_list()
|
||||
ser_list = get_port_list(args.filterVids, args.filterPids, args.filterNames)
|
||||
print("Found %d serial ports" % len(ser_list))
|
||||
else:
|
||||
ser_list = [args.port]
|
||||
open_port_attempts = os.environ.get(
|
||||
"ESPTOOL_OPEN_PORT_ATTEMPTS", DEFAULT_OPEN_PORT_ATTEMPTS
|
||||
)
|
||||
try:
|
||||
open_port_attempts = int(open_port_attempts)
|
||||
except ValueError:
|
||||
raise SystemExit("Invalid value for ESPTOOL_OPEN_PORT_ATTEMPTS")
|
||||
if open_port_attempts != 1:
|
||||
if args.port is None or args.chip == "auto":
|
||||
print(
|
||||
"WARNING: The ESPTOOL_OPEN_PORT_ATTEMPTS (open_port_attempts) option can only be used with --port and --chip arguments."
|
||||
)
|
||||
else:
|
||||
esp = esp or connect_loop(
|
||||
args.port,
|
||||
initial_baud,
|
||||
args.chip,
|
||||
open_port_attempts,
|
||||
args.trace,
|
||||
args.before,
|
||||
)
|
||||
esp = esp or get_default_connected_device(
|
||||
ser_list,
|
||||
port=args.port,
|
||||
@@ -770,6 +861,13 @@ def main(argv=None, esp=None):
|
||||
"Keeping initial baud rate %d" % initial_baud
|
||||
)
|
||||
|
||||
def _define_spi_conn(spi_connection):
|
||||
"""Prepare SPI configuration string and value for flash_spi_attach()"""
|
||||
clk, q, d, hd, cs = spi_connection
|
||||
spi_config_txt = f"CLK:{clk}, Q:{q}, D:{d}, HD:{hd}, CS:{cs}"
|
||||
value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
|
||||
return spi_config_txt, value
|
||||
|
||||
# Override the common SPI flash parameter stuff if configured to do so
|
||||
if hasattr(args, "spi_connection") and args.spi_connection is not None:
|
||||
spi_config = args.spi_connection
|
||||
@@ -781,15 +879,26 @@ def main(argv=None, esp=None):
|
||||
esp.check_spi_connection(args.spi_connection)
|
||||
# Encode the pin numbers as a 32-bit integer with packed 6-bit values,
|
||||
# the same way the ESP ROM takes them
|
||||
clk, q, d, hd, cs = args.spi_connection
|
||||
spi_config = f"CLK:{clk}, Q:{q}, D:{d}, HD:{hd}, CS:{cs}"
|
||||
value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
|
||||
spi_config, value = _define_spi_conn(args.spi_connection)
|
||||
print(f"Configuring SPI flash mode ({spi_config})...")
|
||||
esp.flash_spi_attach(value)
|
||||
elif args.no_stub:
|
||||
print("Enabling default SPI flash mode...")
|
||||
# ROM loader doesn't enable flash unless we explicitly do it
|
||||
esp.flash_spi_attach(0)
|
||||
if esp.CHIP_NAME != "ESP32" or esp.secure_download_mode:
|
||||
print("Enabling default SPI flash mode...")
|
||||
# ROM loader doesn't enable flash unless we explicitly do it
|
||||
esp.flash_spi_attach(0)
|
||||
else:
|
||||
# ROM doesn't attach in-package flash chips
|
||||
spi_chip_pads = esp.get_chip_spi_pads()
|
||||
spi_config_txt, value = _define_spi_conn(spi_chip_pads)
|
||||
if spi_chip_pads != (0, 0, 0, 0, 0):
|
||||
print(
|
||||
"Attaching flash from eFuses' SPI pads configuration"
|
||||
f"({spi_config_txt})..."
|
||||
)
|
||||
else:
|
||||
print("Enabling default SPI flash mode...")
|
||||
esp.flash_spi_attach(value)
|
||||
|
||||
# XMC chip startup sequence
|
||||
XMC_VENDOR_ID = 0x20
|
||||
@@ -879,6 +988,10 @@ def main(argv=None, esp=None):
|
||||
flash_size = detect_flash_size(esp, args)
|
||||
elif args.flash_size == "keep":
|
||||
flash_size = detect_flash_size(esp, args=None)
|
||||
if not esp.IS_STUB:
|
||||
print(
|
||||
"WARNING: In case of failure, please set a specific --flash_size."
|
||||
)
|
||||
else:
|
||||
flash_size = args.flash_size
|
||||
|
||||
@@ -968,14 +1081,29 @@ def arg_auto_chunk_size(string: str) -> int:
|
||||
return num
|
||||
|
||||
|
||||
def get_port_list():
|
||||
def get_port_list(vids=[], pids=[], names=[]):
|
||||
if list_ports is None:
|
||||
raise FatalError(
|
||||
"Listing all serial ports is currently not available. "
|
||||
"Please try to specify the port when running esptool.py or update "
|
||||
"the pyserial package to the latest version"
|
||||
)
|
||||
return sorted(ports.device for ports in list_ports.comports())
|
||||
ports = []
|
||||
for port in list_ports.comports():
|
||||
if sys.platform == "darwin" and port.device.endswith(
|
||||
("Bluetooth-Incoming-Port", "wlan-debug")
|
||||
):
|
||||
continue
|
||||
if vids and (port.vid is None or port.vid not in vids):
|
||||
continue
|
||||
if pids and (port.pid is None or port.pid not in pids):
|
||||
continue
|
||||
if names and (
|
||||
port.name is None or all(name not in port.name for name in names)
|
||||
):
|
||||
continue
|
||||
ports.append(port.device)
|
||||
return sorted(ports)
|
||||
|
||||
|
||||
def expand_file_arguments(argv):
|
||||
@@ -1001,6 +1129,53 @@ def expand_file_arguments(argv):
|
||||
return argv
|
||||
|
||||
|
||||
def connect_loop(
|
||||
port: str,
|
||||
initial_baud: int,
|
||||
chip: str,
|
||||
max_retries: int,
|
||||
trace: bool = False,
|
||||
before: str = "default_reset",
|
||||
):
|
||||
chip_class = CHIP_DEFS[chip]
|
||||
esp = None
|
||||
print(f"Serial port {port}")
|
||||
|
||||
first = True
|
||||
ten_cycle = cycle(chain(repeat(False, 9), (True,)))
|
||||
retry_loop = chain(
|
||||
repeat(False, max_retries - 1), (True,) if max_retries else cycle((False,))
|
||||
)
|
||||
|
||||
for last, every_tenth in zip(retry_loop, ten_cycle):
|
||||
try:
|
||||
esp = chip_class(port, initial_baud, trace)
|
||||
if not first:
|
||||
# break the retrying line
|
||||
print("")
|
||||
esp.connect(before)
|
||||
return esp
|
||||
except (
|
||||
FatalError,
|
||||
serial.serialutil.SerialException,
|
||||
IOError,
|
||||
OSError,
|
||||
) as err:
|
||||
if esp and esp._port:
|
||||
esp._port.close()
|
||||
esp = None
|
||||
if first:
|
||||
print(err)
|
||||
print("Retrying failed connection", end="", flush=True)
|
||||
first = False
|
||||
if last:
|
||||
raise err
|
||||
if every_tenth:
|
||||
# print a dot every second
|
||||
print(".", end="", flush=True)
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def get_default_connected_device(
|
||||
serial_list,
|
||||
port,
|
||||
|
||||
@@ -11,16 +11,19 @@ import os
|
||||
import re
|
||||
import struct
|
||||
import tempfile
|
||||
from typing import BinaryIO, Optional
|
||||
from typing import IO, Optional
|
||||
|
||||
from intelhex import IntelHex
|
||||
from intelhex import HexRecordError, IntelHex
|
||||
|
||||
from loader import ESPLoader
|
||||
from targets import (
|
||||
from .loader import ESPLoader
|
||||
from .targets import (
|
||||
ESP32C2ROM,
|
||||
ESP32C3ROM,
|
||||
ESP32C5ROM,
|
||||
ESP32C5BETA3ROM,
|
||||
ESP32C6BETAROM,
|
||||
ESP32C6ROM,
|
||||
ESP32C61ROM,
|
||||
ESP32H2BETA1ROM,
|
||||
ESP32H2BETA2ROM,
|
||||
ESP32H2ROM,
|
||||
@@ -31,7 +34,7 @@ from targets import (
|
||||
ESP32S3ROM,
|
||||
ESP8266ROM,
|
||||
)
|
||||
from util import FatalError, byte, pad_to
|
||||
from .util import FatalError, byte, pad_to
|
||||
|
||||
|
||||
def align_file_position(f, size):
|
||||
@@ -40,20 +43,24 @@ def align_file_position(f, size):
|
||||
f.seek(align, 1)
|
||||
|
||||
|
||||
def intel_hex_to_bin(file: BinaryIO, start_addr: Optional[int] = None) -> BinaryIO:
|
||||
def intel_hex_to_bin(file: IO[bytes], start_addr: Optional[int] = None) -> IO[bytes]:
|
||||
"""Convert IntelHex file to temp binary file with padding from start_addr
|
||||
If hex file was detected return temp bin file object; input file otherwise"""
|
||||
INTEL_HEX_MAGIC = b":"
|
||||
magic = file.read(1)
|
||||
file.seek(0)
|
||||
if magic == INTEL_HEX_MAGIC:
|
||||
ih = IntelHex()
|
||||
ih.loadhex(file.name)
|
||||
file.close()
|
||||
bin = tempfile.NamedTemporaryFile(suffix=".bin", delete=False)
|
||||
ih.tobinfile(bin, start=start_addr)
|
||||
return bin
|
||||
else:
|
||||
try:
|
||||
if magic == INTEL_HEX_MAGIC:
|
||||
ih = IntelHex()
|
||||
ih.loadhex(file.name)
|
||||
file.close()
|
||||
bin = tempfile.NamedTemporaryFile(suffix=".bin", delete=False)
|
||||
ih.tobinfile(bin, start=start_addr)
|
||||
return bin
|
||||
else:
|
||||
return file
|
||||
except (HexRecordError, UnicodeDecodeError):
|
||||
# file started with HEX magic but the rest was not according to the standard
|
||||
return file
|
||||
|
||||
|
||||
@@ -82,6 +89,9 @@ def LoadFirmwareImage(chip, image_file):
|
||||
"esp32h2beta2": ESP32H2BETA2FirmwareImage,
|
||||
"esp32c2": ESP32C2FirmwareImage,
|
||||
"esp32c6": ESP32C6FirmwareImage,
|
||||
"esp32c61": ESP32C61FirmwareImage,
|
||||
"esp32c5": ESP32C5FirmwareImage,
|
||||
"esp32c5beta3": ESP32C5BETA3FirmwareImage,
|
||||
"esp32h2": ESP32H2FirmwareImage,
|
||||
"esp32p4": ESP32P4FirmwareImage,
|
||||
}[chip](f)
|
||||
@@ -105,10 +115,11 @@ class ImageSegment(object):
|
||||
"""Wrapper class for a segment in an ESP image
|
||||
(very similar to a section in an ELFImage also)"""
|
||||
|
||||
def __init__(self, addr, data, file_offs=None):
|
||||
def __init__(self, addr, data, file_offs=None, flags=0):
|
||||
self.addr = addr
|
||||
self.data = data
|
||||
self.file_offs = file_offs
|
||||
self.flags = flags
|
||||
self.include_in_checksum = True
|
||||
if self.addr != 0:
|
||||
self.pad_to_alignment(
|
||||
@@ -157,8 +168,8 @@ class ELFSection(ImageSegment):
|
||||
"""Wrapper class for a section in an ELF image, has a section
|
||||
name as well as the common properties of an ImageSegment."""
|
||||
|
||||
def __init__(self, name, addr, data):
|
||||
super(ELFSection, self).__init__(addr, data)
|
||||
def __init__(self, name, addr, data, flags):
|
||||
super(ELFSection, self).__init__(addr, data, flags=flags)
|
||||
self.name = name.decode("utf-8")
|
||||
|
||||
def __repr__(self):
|
||||
@@ -168,6 +179,9 @@ class ELFSection(ImageSegment):
|
||||
class BaseFirmwareImage(object):
|
||||
SEG_HEADER_LEN = 8
|
||||
SHA256_DIGEST_LEN = 32
|
||||
ELF_FLAG_WRITE = 0x1
|
||||
ELF_FLAG_READ = 0x2
|
||||
ELF_FLAG_EXEC = 0x4
|
||||
|
||||
""" Base class with common firmware image functions """
|
||||
|
||||
@@ -342,6 +356,11 @@ class BaseFirmwareImage(object):
|
||||
irom_segment = self.get_irom_segment()
|
||||
return [s for s in self.segments if s != irom_segment]
|
||||
|
||||
def sort_segments(self):
|
||||
if not self.segments:
|
||||
return # nothing to sort
|
||||
self.segments = sorted(self.segments, key=lambda s: s.addr)
|
||||
|
||||
def merge_adjacent_segments(self):
|
||||
if not self.segments:
|
||||
return # nothing to merge
|
||||
@@ -358,6 +377,8 @@ class BaseFirmwareImage(object):
|
||||
elem.get_memory_type(self) == next_elem.get_memory_type(self),
|
||||
elem.include_in_checksum == next_elem.include_in_checksum,
|
||||
next_elem.addr == elem.addr + len(elem.data),
|
||||
next_elem.flags & self.ELF_FLAG_EXEC
|
||||
== elem.flags & self.ELF_FLAG_EXEC,
|
||||
)
|
||||
):
|
||||
# Merge any segment that ends where the next one starts,
|
||||
@@ -613,6 +634,7 @@ class ESP32FirmwareImage(BaseFirmwareImage):
|
||||
self.ram_only_header = ram_only_header
|
||||
|
||||
self.append_digest = append_digest
|
||||
self.data_length = None
|
||||
|
||||
if load_file is not None:
|
||||
start = load_file.tell()
|
||||
@@ -631,6 +653,7 @@ class ESP32FirmwareImage(BaseFirmwareImage):
|
||||
calc_digest = hashlib.sha256()
|
||||
calc_digest.update(load_file.read(end - start))
|
||||
self.calc_digest = calc_digest.digest() # TODO: decide what to do here?
|
||||
self.data_length = end - start
|
||||
|
||||
self.verify()
|
||||
|
||||
@@ -683,7 +706,10 @@ class ESP32FirmwareImage(BaseFirmwareImage):
|
||||
# So bootdesc will be at the very top of the binary at 0x20 offset
|
||||
# (in the first segment).
|
||||
for segment in ram_segments:
|
||||
if segment.name == ".dram0.bootdesc":
|
||||
if (
|
||||
isinstance(segment, ELFSection)
|
||||
and segment.name == ".dram0.bootdesc"
|
||||
):
|
||||
ram_segments.remove(segment)
|
||||
ram_segments.insert(0, segment)
|
||||
break
|
||||
@@ -736,15 +762,25 @@ class ESP32FirmwareImage(BaseFirmwareImage):
|
||||
flash_segments.reverse()
|
||||
for segment in flash_segments:
|
||||
pad_len = get_alignment_data_needed(segment)
|
||||
while pad_len > 0:
|
||||
pad_segment = ImageSegment(0, b"\x00" * pad_len, f.tell())
|
||||
self.save_segment(f, pad_segment)
|
||||
total_segments += 1
|
||||
pad_len = get_alignment_data_needed(segment)
|
||||
# write the flash segment
|
||||
assert (
|
||||
f.tell() + 8
|
||||
) % self.IROM_ALIGN == segment.addr % self.IROM_ALIGN
|
||||
# Some chips have a non-zero load offset (eg. 0x1000)
|
||||
# therefore we shift the ROM segments "-load_offset"
|
||||
# so it will be aligned properly after it is flashed
|
||||
align_min = (
|
||||
self.ROM_LOADER.BOOTLOADER_FLASH_OFFSET - self.SEG_HEADER_LEN
|
||||
)
|
||||
if pad_len < align_min:
|
||||
# in case pad_len does not fit minimum alignment,
|
||||
# pad it to next aligned boundary
|
||||
pad_len += self.IROM_ALIGN
|
||||
|
||||
pad_len -= self.ROM_LOADER.BOOTLOADER_FLASH_OFFSET
|
||||
pad_segment = ImageSegment(0, b"\x00" * pad_len, f.tell())
|
||||
self.save_segment(f, pad_segment)
|
||||
total_segments += 1
|
||||
# check the alignment
|
||||
assert (f.tell() + 8 + self.ROM_LOADER.BOOTLOADER_FLASH_OFFSET) % (
|
||||
self.IROM_ALIGN
|
||||
) == segment.addr % self.IROM_ALIGN
|
||||
# save the flash segment but not saving its checksum neither
|
||||
# saving the number of flash segments, since ROM bootloader
|
||||
# should "not see" them
|
||||
@@ -952,7 +988,7 @@ class ESP8266V3FirmwareImage(ESP32FirmwareImage):
|
||||
while len(flash_segments) > 0:
|
||||
segment = flash_segments[0]
|
||||
# remove 8 bytes empty data for insert segment header
|
||||
if segment.name == ".flash.rodata":
|
||||
if isinstance(segment, ELFSection) and segment.name == ".flash.rodata":
|
||||
segment.data = segment.data[8:]
|
||||
# write the flash segment
|
||||
checksum = self.save_segment(f, segment, checksum)
|
||||
@@ -1114,6 +1150,33 @@ class ESP32C6FirmwareImage(ESP32FirmwareImage):
|
||||
ESP32C6ROM.BOOTLOADER_IMAGE = ESP32C6FirmwareImage
|
||||
|
||||
|
||||
class ESP32C61FirmwareImage(ESP32C6FirmwareImage):
|
||||
"""ESP32C61 Firmware Image almost exactly the same as ESP32C6FirmwareImage"""
|
||||
|
||||
ROM_LOADER = ESP32C61ROM
|
||||
|
||||
|
||||
ESP32C61ROM.BOOTLOADER_IMAGE = ESP32C61FirmwareImage
|
||||
|
||||
|
||||
class ESP32C5FirmwareImage(ESP32C6FirmwareImage):
|
||||
"""ESP32C5 Firmware Image almost exactly the same as ESP32C6FirmwareImage"""
|
||||
|
||||
ROM_LOADER = ESP32C5ROM
|
||||
|
||||
|
||||
ESP32C5ROM.BOOTLOADER_IMAGE = ESP32C5FirmwareImage
|
||||
|
||||
|
||||
class ESP32C5BETA3FirmwareImage(ESP32C6FirmwareImage):
|
||||
"""ESP32C5BETA3 Firmware Image almost exactly the same as ESP32C6FirmwareImage"""
|
||||
|
||||
ROM_LOADER = ESP32C5BETA3ROM
|
||||
|
||||
|
||||
ESP32C5BETA3ROM.BOOTLOADER_IMAGE = ESP32C5BETA3FirmwareImage
|
||||
|
||||
|
||||
class ESP32P4FirmwareImage(ESP32FirmwareImage):
|
||||
"""ESP32P4 Firmware Image almost exactly the same as ESP32FirmwareImage"""
|
||||
|
||||
@@ -1222,16 +1285,16 @@ class ELFFile(object):
|
||||
name_offs, sec_type, _flags, lma, sec_offs, size = struct.unpack_from(
|
||||
"<LLLLLL", section_header[offs:]
|
||||
)
|
||||
return (name_offs, sec_type, lma, size, sec_offs)
|
||||
return (name_offs, sec_type, lma, size, sec_offs, _flags)
|
||||
|
||||
all_sections = [read_section_header(offs) for offs in section_header_offsets]
|
||||
prog_sections = [s for s in all_sections if s[1] in ELFFile.PROG_SEC_TYPES]
|
||||
nobits_secitons = [s for s in all_sections if s[1] == ELFFile.SEC_TYPE_NOBITS]
|
||||
|
||||
# search for the string table section
|
||||
if not (shstrndx * self.LEN_SEC_HEADER) in section_header_offsets:
|
||||
if (shstrndx * self.LEN_SEC_HEADER) not in section_header_offsets:
|
||||
raise FatalError("ELF file has no STRTAB section at shstrndx %d" % shstrndx)
|
||||
_, sec_type, _, sec_size, sec_offs = read_section_header(
|
||||
_, sec_type, _, sec_size, sec_offs, _ = read_section_header(
|
||||
shstrndx * self.LEN_SEC_HEADER
|
||||
)
|
||||
if sec_type != ELFFile.SEC_TYPE_STRTAB:
|
||||
@@ -1253,14 +1316,14 @@ class ELFFile(object):
|
||||
return f.read(size)
|
||||
|
||||
prog_sections = [
|
||||
ELFSection(lookup_string(n_offs), lma, read_data(offs, size))
|
||||
for (n_offs, _type, lma, size, offs) in prog_sections
|
||||
ELFSection(lookup_string(n_offs), lma, read_data(offs, size), flags=_flags)
|
||||
for (n_offs, _type, lma, size, offs, _flags) in prog_sections
|
||||
if lma != 0 and size > 0
|
||||
]
|
||||
self.sections = prog_sections
|
||||
self.nobits_sections = [
|
||||
ELFSection(lookup_string(n_offs), lma, b"")
|
||||
for (n_offs, _type, lma, size, offs) in nobits_secitons
|
||||
ELFSection(lookup_string(n_offs), lma, b"", flags=_flags)
|
||||
for (n_offs, _type, lma, size, offs, _flags) in nobits_secitons
|
||||
if lma != 0 and size > 0
|
||||
]
|
||||
|
||||
@@ -1293,7 +1356,7 @@ class ELFFile(object):
|
||||
_flags,
|
||||
_align,
|
||||
) = struct.unpack_from("<LLLLLLLL", segment_header[offs:])
|
||||
return (seg_type, lma, size, seg_offs)
|
||||
return (seg_type, lma, size, seg_offs, _flags)
|
||||
|
||||
all_segments = [read_segment_header(offs) for offs in segment_header_offsets]
|
||||
prog_segments = [s for s in all_segments if s[0] == ELFFile.SEG_TYPE_LOAD]
|
||||
@@ -1303,8 +1366,8 @@ class ELFFile(object):
|
||||
return f.read(size)
|
||||
|
||||
prog_segments = [
|
||||
ELFSection(b"PHDR", lma, read_data(offs, size))
|
||||
for (_type, lma, size, offs) in prog_segments
|
||||
ELFSection(b"PHDR", lma, read_data(offs, size), flags=_flags)
|
||||
for (_type, lma, size, offs, _flags) in prog_segments
|
||||
if lma != 0 and size > 0
|
||||
]
|
||||
self.segments = prog_segments
|
||||
|
||||
@@ -10,31 +10,33 @@ import struct
|
||||
import sys
|
||||
import time
|
||||
import zlib
|
||||
import itertools
|
||||
|
||||
from intelhex import IntelHex
|
||||
from serial import SerialException
|
||||
|
||||
from bin_image import ELFFile, ImageSegment, LoadFirmwareImage
|
||||
from bin_image import (
|
||||
from .bin_image import ELFFile, ImageSegment, LoadFirmwareImage
|
||||
from .bin_image import (
|
||||
ESP8266ROMFirmwareImage,
|
||||
ESP8266V2FirmwareImage,
|
||||
ESP8266V3FirmwareImage,
|
||||
)
|
||||
from loader import (
|
||||
from .loader import (
|
||||
DEFAULT_CONNECT_ATTEMPTS,
|
||||
DEFAULT_TIMEOUT,
|
||||
ERASE_WRITE_TIMEOUT_PER_MB,
|
||||
ESPLoader,
|
||||
timeout_per_mb,
|
||||
)
|
||||
from targets import CHIP_DEFS, CHIP_LIST, ROM_LIST
|
||||
from uf2_writer import UF2Writer
|
||||
from util import (
|
||||
from .targets import CHIP_DEFS, CHIP_LIST, ROM_LIST
|
||||
from .uf2_writer import UF2Writer
|
||||
from .util import (
|
||||
FatalError,
|
||||
NotImplementedInROMError,
|
||||
NotSupportedError,
|
||||
UnsupportedCommandError,
|
||||
)
|
||||
from util import (
|
||||
from .util import (
|
||||
div_roundup,
|
||||
flash_size_bytes,
|
||||
get_file_size,
|
||||
@@ -115,7 +117,7 @@ def detect_chip(
|
||||
else:
|
||||
err_msg = f"Unexpected chip ID value {chip_id}."
|
||||
except (UnsupportedCommandError, struct.error, FatalError) as e:
|
||||
# UnsupportedCommmanddError: ESP8266/ESP32 ROM
|
||||
# UnsupportedCommandError: ESP8266/ESP32 ROM
|
||||
# struct.error: ESP32-S2
|
||||
# FatalError: ESP8266/ESP32 STUB
|
||||
print(" Unsupported detection protocol, switching and trying again...")
|
||||
@@ -229,7 +231,7 @@ def detect_flash_size(esp, args=None):
|
||||
if flash_size is None:
|
||||
flash_size = "4MB"
|
||||
print(
|
||||
"Warning: Could not auto-detect Flash size "
|
||||
"WARNING: Could not auto-detect Flash size "
|
||||
f"(FlashID={flash_id:#x}, SizeID={size_id:#x}), defaulting to 4MB"
|
||||
)
|
||||
else:
|
||||
@@ -276,49 +278,60 @@ def _update_image_flash_params(esp, address, args, image):
|
||||
|
||||
# After the 8-byte header comes the extended header for chips others than ESP8266.
|
||||
# The 15th byte of the extended header indicates if the image is protected by
|
||||
# a SHA256 checksum. In that case we should not modify the header because
|
||||
# the checksum check would fail.
|
||||
sha_implies_keep = args.chip != "esp8266" and image[8 + 15] == 1
|
||||
|
||||
def print_keep_warning(arg_to_keep, arg_used):
|
||||
print(
|
||||
"Warning: Image file at {addr} is protected with a hash checksum, "
|
||||
"so not changing the flash {arg} setting. "
|
||||
"Use the --flash_{arg}=keep option instead of --flash_{arg}={arg_orig} "
|
||||
"in order to remove this warning, or use the --dont-append-digest option "
|
||||
"for the elf2image command in order to generate an image file "
|
||||
"without a hash checksum".format(
|
||||
addr=hex(address), arg=arg_to_keep, arg_orig=arg_used
|
||||
)
|
||||
)
|
||||
# a SHA256 checksum. In that case we recalculate the SHA digest after modifying the header.
|
||||
sha_appended = args.chip != "esp8266" and image[8 + 15] == 1
|
||||
|
||||
if args.flash_mode != "keep":
|
||||
new_flash_mode = FLASH_MODES[args.flash_mode]
|
||||
if flash_mode != new_flash_mode and sha_implies_keep:
|
||||
print_keep_warning("mode", args.flash_mode)
|
||||
else:
|
||||
flash_mode = new_flash_mode
|
||||
flash_mode = FLASH_MODES[args.flash_mode]
|
||||
|
||||
flash_freq = flash_size_freq & 0x0F
|
||||
if args.flash_freq != "keep":
|
||||
new_flash_freq = esp.parse_flash_freq_arg(args.flash_freq)
|
||||
if flash_freq != new_flash_freq and sha_implies_keep:
|
||||
print_keep_warning("frequency", args.flash_freq)
|
||||
else:
|
||||
flash_freq = new_flash_freq
|
||||
flash_freq = esp.parse_flash_freq_arg(args.flash_freq)
|
||||
|
||||
flash_size = flash_size_freq & 0xF0
|
||||
if args.flash_size != "keep":
|
||||
new_flash_size = esp.parse_flash_size_arg(args.flash_size)
|
||||
if flash_size != new_flash_size and sha_implies_keep:
|
||||
print_keep_warning("size", args.flash_size)
|
||||
else:
|
||||
flash_size = new_flash_size
|
||||
flash_size = esp.parse_flash_size_arg(args.flash_size)
|
||||
|
||||
flash_params = struct.pack(b"BB", flash_mode, flash_size + flash_freq)
|
||||
if flash_params != image[2:4]:
|
||||
print("Flash params set to 0x%04x" % struct.unpack(">H", flash_params))
|
||||
image = image[0:2] + flash_params + image[4:]
|
||||
|
||||
# recalculate the SHA digest if it was appended
|
||||
if sha_appended:
|
||||
# Since the changes are only made for images located in the bootloader offset,
|
||||
# we can assume that the image is always a bootloader image.
|
||||
# For merged binaries, we check the bootloader SHA when parameters are changed.
|
||||
image_object = esp.BOOTLOADER_IMAGE(io.BytesIO(image))
|
||||
# get the image header, extended header (if present) and data
|
||||
image_data_before_sha = image[: image_object.data_length]
|
||||
# get the image data after the SHA digest (primary for merged binaries)
|
||||
image_data_after_sha = image[
|
||||
(image_object.data_length + image_object.SHA256_DIGEST_LEN) :
|
||||
]
|
||||
|
||||
sha_digest_calculated = hashlib.sha256(image_data_before_sha).digest()
|
||||
image = bytes(
|
||||
itertools.chain(
|
||||
image_data_before_sha, sha_digest_calculated, image_data_after_sha
|
||||
)
|
||||
)
|
||||
|
||||
# get the SHA digest newly stored in the image and compare it to the calculated one
|
||||
image_stored_sha = image[
|
||||
image_object.data_length : image_object.data_length
|
||||
+ image_object.SHA256_DIGEST_LEN
|
||||
]
|
||||
|
||||
if hexify(sha_digest_calculated) == hexify(image_stored_sha):
|
||||
print("SHA digest in image updated")
|
||||
else:
|
||||
print(
|
||||
"WARNING: SHA recalculation for binary failed!\n"
|
||||
f"\tExpected calculated SHA: {hexify(sha_digest_calculated)}\n"
|
||||
f"\tSHA stored in binary: {hexify(image_stored_sha)}"
|
||||
)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
@@ -465,19 +478,32 @@ def write_flash(esp, args):
|
||||
"Use --force to override the warning."
|
||||
)
|
||||
|
||||
# verify file sizes fit in flash
|
||||
flash_end = flash_size_bytes(
|
||||
detect_flash_size(esp) if args.flash_size == "keep" else args.flash_size
|
||||
set_flash_size = (
|
||||
flash_size_bytes(args.flash_size)
|
||||
if args.flash_size not in ["detect", "keep"]
|
||||
else None
|
||||
)
|
||||
if flash_end is not None: # Not in secure download mode
|
||||
if esp.secure_download_mode:
|
||||
flash_end = set_flash_size
|
||||
else: # Check against real flash chip size if not in SDM
|
||||
flash_end_str = detect_flash_size(esp)
|
||||
flash_end = flash_size_bytes(flash_end_str)
|
||||
if set_flash_size and set_flash_size > flash_end:
|
||||
print(
|
||||
f"WARNING: Set --flash_size {args.flash_size} "
|
||||
f"is larger than the available flash size of {flash_end_str}."
|
||||
)
|
||||
|
||||
# Verify file sizes fit in the set --flash_size, or real flash size if smaller
|
||||
flash_end = min(set_flash_size, flash_end) if set_flash_size else flash_end
|
||||
if flash_end is not None:
|
||||
for address, argfile in args.addr_filename:
|
||||
argfile.seek(0, os.SEEK_END)
|
||||
if address + argfile.tell() > flash_end:
|
||||
raise FatalError(
|
||||
"File %s (length %d) at offset %d "
|
||||
"will not fit in %d bytes of flash. "
|
||||
"Use --flash_size argument, or change flashing address."
|
||||
% (argfile.name, argfile.tell(), address, flash_end)
|
||||
f"File {argfile.name} (length {argfile.tell()}) at offset "
|
||||
f"{address} will not fit in {flash_end} bytes of flash. "
|
||||
"Change the --flash_size argument, or flashing address."
|
||||
)
|
||||
argfile.seek(0)
|
||||
|
||||
@@ -547,70 +573,118 @@ def write_flash(esp, args):
|
||||
print("Will flash %s uncompressed" % argfile.name)
|
||||
compress = False
|
||||
|
||||
if args.no_stub:
|
||||
print("Erasing flash...")
|
||||
image = pad_to(
|
||||
argfile.read(), esp.FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4
|
||||
)
|
||||
image = argfile.read()
|
||||
|
||||
if len(image) == 0:
|
||||
print("WARNING: File %s is empty" % argfile.name)
|
||||
continue
|
||||
image = _update_image_flash_params(esp, address, args, image)
|
||||
|
||||
image = pad_to(image, esp.FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4)
|
||||
|
||||
if args.no_stub:
|
||||
print("Erasing flash...")
|
||||
|
||||
# It is not possible to write to not aligned addresses without stub,
|
||||
# so there are added 0xFF (erase) bytes at the beginning of the image
|
||||
# to align it.
|
||||
bytes_over = address % esp.FLASH_SECTOR_SIZE
|
||||
address -= bytes_over
|
||||
image = b"\xFF" * bytes_over + image
|
||||
|
||||
if not esp.secure_download_mode and not esp.get_secure_boot_enabled():
|
||||
image = _update_image_flash_params(esp, address, args, image)
|
||||
else:
|
||||
print(
|
||||
"WARNING: Security features enabled, so not changing any flash settings."
|
||||
)
|
||||
calcmd5 = hashlib.md5(image).hexdigest()
|
||||
uncsize = len(image)
|
||||
if compress:
|
||||
uncimage = image
|
||||
image = zlib.compress(uncimage, 9)
|
||||
# Decompress the compressed binary a block at a time,
|
||||
# to dynamically calculate the timeout based on the real write size
|
||||
decompress = zlib.decompressobj()
|
||||
blocks = esp.flash_defl_begin(uncsize, len(image), address)
|
||||
else:
|
||||
blocks = esp.flash_begin(uncsize, address, begin_rom_encrypted=encrypted)
|
||||
argfile.seek(0) # in case we need it again
|
||||
seq = 0
|
||||
bytes_sent = 0 # bytes sent on wire
|
||||
bytes_written = 0 # bytes written to flash
|
||||
t = time.time()
|
||||
|
||||
timeout = DEFAULT_TIMEOUT
|
||||
|
||||
while len(image) > 0:
|
||||
print_overwrite(
|
||||
"Writing at 0x%08x... (%d %%)"
|
||||
% (address + bytes_written, 100 * (seq + 1) // blocks)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
block = image[0 : esp.FLASH_WRITE_SIZE]
|
||||
if compress:
|
||||
# feeding each compressed block into the decompressor lets us
|
||||
# see block-by-block how much will be written
|
||||
block_uncompressed = len(decompress.decompress(block))
|
||||
bytes_written += block_uncompressed
|
||||
block_timeout = max(
|
||||
DEFAULT_TIMEOUT,
|
||||
timeout_per_mb(ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed),
|
||||
)
|
||||
if not esp.IS_STUB:
|
||||
timeout = (
|
||||
block_timeout # ROM code writes block to flash before ACKing
|
||||
)
|
||||
esp.flash_defl_block(block, seq, timeout=timeout)
|
||||
if esp.IS_STUB:
|
||||
# Stub ACKs when block is received,
|
||||
# then writes to flash while receiving the block after it
|
||||
timeout = block_timeout
|
||||
else:
|
||||
# Pad the last block
|
||||
block = block + b"\xff" * (esp.FLASH_WRITE_SIZE - len(block))
|
||||
if encrypted:
|
||||
esp.flash_encrypt_block(block, seq)
|
||||
original_image = image # Save the whole image in case retry is needed
|
||||
# Try again if reconnect was successful
|
||||
for attempt in range(1, esp.WRITE_FLASH_ATTEMPTS + 1):
|
||||
try:
|
||||
if compress:
|
||||
# Decompress the compressed binary a block at a time,
|
||||
# to dynamically calculate the timeout based on the real write size
|
||||
decompress = zlib.decompressobj()
|
||||
blocks = esp.flash_defl_begin(uncsize, len(image), address)
|
||||
else:
|
||||
esp.flash_block(block, seq)
|
||||
bytes_written += len(block)
|
||||
bytes_sent += len(block)
|
||||
image = image[esp.FLASH_WRITE_SIZE :]
|
||||
seq += 1
|
||||
blocks = esp.flash_begin(
|
||||
uncsize, address, begin_rom_encrypted=encrypted
|
||||
)
|
||||
argfile.seek(0) # in case we need it again
|
||||
seq = 0
|
||||
bytes_sent = 0 # bytes sent on wire
|
||||
bytes_written = 0 # bytes written to flash
|
||||
t = time.time()
|
||||
|
||||
timeout = DEFAULT_TIMEOUT
|
||||
|
||||
while len(image) > 0:
|
||||
print_overwrite(
|
||||
"Writing at 0x%08x... (%d %%)"
|
||||
% (address + bytes_written, 100 * (seq + 1) // blocks)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
block = image[0 : esp.FLASH_WRITE_SIZE]
|
||||
if compress:
|
||||
# feeding each compressed block into the decompressor lets us
|
||||
# see block-by-block how much will be written
|
||||
block_uncompressed = len(decompress.decompress(block))
|
||||
bytes_written += block_uncompressed
|
||||
block_timeout = max(
|
||||
DEFAULT_TIMEOUT,
|
||||
timeout_per_mb(
|
||||
ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed
|
||||
),
|
||||
)
|
||||
if not esp.IS_STUB:
|
||||
timeout = block_timeout # ROM code writes block to flash before ACKing
|
||||
esp.flash_defl_block(block, seq, timeout=timeout)
|
||||
if esp.IS_STUB:
|
||||
# Stub ACKs when block is received,
|
||||
# then writes to flash while receiving the block after it
|
||||
timeout = block_timeout
|
||||
else:
|
||||
# Pad the last block
|
||||
block = block + b"\xff" * (esp.FLASH_WRITE_SIZE - len(block))
|
||||
if encrypted:
|
||||
esp.flash_encrypt_block(block, seq)
|
||||
else:
|
||||
esp.flash_block(block, seq)
|
||||
bytes_written += len(block)
|
||||
bytes_sent += len(block)
|
||||
image = image[esp.FLASH_WRITE_SIZE :]
|
||||
seq += 1
|
||||
break
|
||||
except SerialException:
|
||||
if attempt == esp.WRITE_FLASH_ATTEMPTS or encrypted:
|
||||
# Already retried once or encrypted mode is disabled because of security reasons
|
||||
raise
|
||||
print("\nLost connection, retrying...")
|
||||
esp._port.close()
|
||||
print("Waiting for the chip to reconnect", end="")
|
||||
for _ in range(DEFAULT_CONNECT_ATTEMPTS):
|
||||
try:
|
||||
time.sleep(1)
|
||||
esp._port.open()
|
||||
print() # Print new line which was suppressed by print(".")
|
||||
esp.connect()
|
||||
if esp.IS_STUB:
|
||||
# Hack to bypass the stub overwrite check
|
||||
esp.IS_STUB = False
|
||||
# Reflash stub because chip was reset
|
||||
esp = esp.run_stub()
|
||||
image = original_image
|
||||
break
|
||||
except SerialException:
|
||||
print(".", end="")
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
raise # Reconnect limit reached
|
||||
|
||||
if esp.IS_STUB:
|
||||
# Stub only writes each block to flash after 'ack'ing the receive,
|
||||
@@ -645,7 +719,7 @@ def write_flash(esp, args):
|
||||
print("Flash md5: %s" % res)
|
||||
print(
|
||||
"MD5 of 0xFF is %s"
|
||||
% (hashlib.md5(b"\xFF" * uncsize).hexdigest())
|
||||
% (hashlib.md5(b"\xff" * uncsize).hexdigest())
|
||||
)
|
||||
raise FatalError("MD5 of file does not match data in flash!")
|
||||
else:
|
||||
@@ -793,7 +867,7 @@ def image_info(args):
|
||||
format_str = "{:7} {:#07x} {:#010x} {:#010x} {}"
|
||||
app_desc = None
|
||||
bootloader_desc = None
|
||||
for idx, seg in enumerate(image.segments, start=1):
|
||||
for idx, seg in enumerate(image.segments):
|
||||
segs = seg.get_memory_type(image)
|
||||
seg_name = ", ".join(segs)
|
||||
if "DROM" in segs: # The DROM segment starts with the esp_app_desc_t struct
|
||||
@@ -815,9 +889,11 @@ def image_info(args):
|
||||
print(
|
||||
"Checksum: {:#02x} ({})".format(
|
||||
image.checksum,
|
||||
"valid"
|
||||
if image.checksum == calc_checksum
|
||||
else "invalid - calculated {:02x}".format(calc_checksum),
|
||||
(
|
||||
"valid"
|
||||
if image.checksum == calc_checksum
|
||||
else "invalid - calculated {:02x}".format(calc_checksum)
|
||||
),
|
||||
)
|
||||
)
|
||||
try:
|
||||
@@ -892,8 +968,7 @@ def image_info(args):
|
||||
ESP8266V2FirmwareImage.IMAGE_V2_MAGIC,
|
||||
]:
|
||||
raise FatalError(
|
||||
"This is not a valid image "
|
||||
"(invalid magic number: {:#x})".format(magic)
|
||||
f"This is not a valid image (invalid magic number: {magic:#x})"
|
||||
)
|
||||
|
||||
if args.chip == "auto":
|
||||
@@ -940,9 +1015,11 @@ def image_info(args):
|
||||
print(
|
||||
"Checksum: {:02x} ({})".format(
|
||||
image.checksum,
|
||||
"valid"
|
||||
if image.checksum == calc_checksum
|
||||
else "invalid - calculated {:02x}".format(calc_checksum),
|
||||
(
|
||||
"valid"
|
||||
if image.checksum == calc_checksum
|
||||
else "invalid - calculated {:02x}".format(calc_checksum)
|
||||
),
|
||||
)
|
||||
)
|
||||
try:
|
||||
@@ -982,8 +1059,6 @@ def elf2image(args):
|
||||
args.chip = "esp8266"
|
||||
|
||||
print("Creating {} image...".format(args.chip))
|
||||
if args.ram_only_header:
|
||||
print("ROM segments hidden - only RAM segments are visible to the ROM loader!")
|
||||
|
||||
if args.chip != "esp8266":
|
||||
image = CHIP_DEFS[args.chip].BOOTLOADER_IMAGE()
|
||||
@@ -995,7 +1070,10 @@ def elf2image(args):
|
||||
image.min_rev_full = args.min_rev_full
|
||||
image.max_rev_full = args.max_rev_full
|
||||
image.ram_only_header = args.ram_only_header
|
||||
image.append_digest = args.append_digest
|
||||
if image.ram_only_header:
|
||||
image.append_digest = False
|
||||
else:
|
||||
image.append_digest = args.append_digest
|
||||
elif args.version == "1": # ESP8266
|
||||
image = ESP8266ROMFirmwareImage()
|
||||
elif args.version == "2":
|
||||
@@ -1019,6 +1097,13 @@ def elf2image(args):
|
||||
image.elf_sha256 = e.sha256()
|
||||
image.elf_sha256_offset = args.elf_sha256_offset
|
||||
|
||||
if args.ram_only_header:
|
||||
print(
|
||||
"Image has only RAM segments visible. "
|
||||
"ROM segments are hidden and SHA256 digest is not appended."
|
||||
)
|
||||
image.sort_segments()
|
||||
|
||||
before = len(image.segments)
|
||||
image.merge_adjacent_segments()
|
||||
if len(image.segments) != before:
|
||||
@@ -1090,7 +1175,7 @@ def run(esp, args):
|
||||
esp.run()
|
||||
|
||||
|
||||
def flash_id(esp, args):
|
||||
def detect_flash_id(esp):
|
||||
flash_id = esp.flash_id()
|
||||
print("Manufacturer: %02x" % (flash_id & 0xFF))
|
||||
flid_lowbyte = (flash_id >> 16) & 0xFF
|
||||
@@ -1098,11 +1183,27 @@ def flash_id(esp, args):
|
||||
print(
|
||||
"Detected flash size: %s" % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown"))
|
||||
)
|
||||
|
||||
|
||||
def flash_id(esp, args):
|
||||
detect_flash_id(esp)
|
||||
flash_type = esp.flash_type()
|
||||
flash_type_dict = {0: "quad (4 data lines)", 1: "octal (8 data lines)"}
|
||||
flash_type_str = flash_type_dict.get(flash_type)
|
||||
if flash_type_str:
|
||||
print(f"Flash type set in eFuse: {flash_type_str}")
|
||||
esp.get_flash_voltage()
|
||||
|
||||
|
||||
def read_flash_sfdp(esp, args):
|
||||
detect_flash_id(esp)
|
||||
|
||||
sfdp = esp.read_spiflash_sfdp(args.addr, args.bytes * 8)
|
||||
print(f"SFDP[{args.addr}..{args.addr+args.bytes-1}]: ", end="")
|
||||
for i in range(args.bytes):
|
||||
print(f"{sfdp&0xff:02X} ", end="")
|
||||
sfdp = sfdp >> 8
|
||||
print()
|
||||
|
||||
|
||||
def read_flash(esp, args):
|
||||
@@ -1218,7 +1319,16 @@ def get_security_info(esp, args):
|
||||
print(title)
|
||||
print("=" * len(title))
|
||||
print("Flags: {:#010x} ({})".format(si["flags"], bin(si["flags"])))
|
||||
print("Key Purposes: {}".format(si["key_purposes"]))
|
||||
if esp.KEY_PURPOSES:
|
||||
print(f"Key Purposes: {si['key_purposes']}")
|
||||
desc = "\n ".join(
|
||||
[
|
||||
f"BLOCK_KEY{key_num} - {esp.KEY_PURPOSES.get(purpose, 'UNKNOWN')}"
|
||||
for key_num, purpose in enumerate(si["key_purposes"])
|
||||
if key_num <= esp.EFUSE_MAX_KEY
|
||||
]
|
||||
)
|
||||
print(f" {desc}")
|
||||
if si["chip_id"] is not None and si["api_version"] is not None:
|
||||
print("Chip ID: {}".format(si["chip_id"]))
|
||||
print("API Version: {}".format(si["api_version"]))
|
||||
@@ -1270,7 +1380,7 @@ def get_security_info(esp, args):
|
||||
hard_dis_jtag = get_security_flag_status("HARD_DIS_JTAG", flags)
|
||||
soft_dis_jtag = get_security_flag_status("SOFT_DIS_JTAG", flags)
|
||||
if hard_dis_jtag:
|
||||
print("JTAG: Permenantly Disabled")
|
||||
print("JTAG: Permanently Disabled")
|
||||
elif soft_dis_jtag:
|
||||
print("JTAG: Software Access Disabled")
|
||||
if get_security_flag_status("DIS_USB", flags):
|
||||
@@ -1323,7 +1433,7 @@ def merge_bin(args):
|
||||
|
||||
def pad_to(flash_offs):
|
||||
# account for output file offset if there is any
|
||||
of.write(b"\xFF" * (flash_offs - args.target_offset - of.tell()))
|
||||
of.write(b"\xff" * (flash_offs - args.target_offset - of.tell()))
|
||||
|
||||
for addr, argfile in input_files:
|
||||
pad_to(addr)
|
||||
@@ -1338,6 +1448,13 @@ def merge_bin(args):
|
||||
)
|
||||
elif args.format == "hex":
|
||||
out = IntelHex()
|
||||
if len(input_files) == 1:
|
||||
print(
|
||||
"WARNING: Only one input file specified, output may include "
|
||||
"additional padding if input file was previously merged. "
|
||||
"Please refer to the documentation for more information: "
|
||||
"https://docs.espressif.com/projects/esptool/en/latest/esptool/basic-commands.html#hex-output-format" # noqa E501
|
||||
)
|
||||
for addr, argfile in input_files:
|
||||
ihex = IntelHex()
|
||||
image = argfile.read()
|
||||
|
||||
@@ -19,6 +19,7 @@ CONFIG_OPTIONS = [
|
||||
"connect_attempts",
|
||||
"write_block_attempts",
|
||||
"reset_delay",
|
||||
"open_port_attempts",
|
||||
"custom_reset_sequence",
|
||||
]
|
||||
|
||||
|
||||
@@ -13,9 +13,11 @@ import string
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from config import load_config_file
|
||||
from reset import (
|
||||
|
||||
from .config import load_config_file
|
||||
from .reset import (
|
||||
ClassicReset,
|
||||
CustomReset,
|
||||
DEFAULT_RESET_DELAY,
|
||||
@@ -23,8 +25,8 @@ from reset import (
|
||||
USBJTAGSerialReset,
|
||||
UnixTightReset,
|
||||
)
|
||||
from util import FatalError, NotImplementedInROMError, UnsupportedCommandError
|
||||
from util import byte, hexify, mask_to_shift, pad_to, strip_chip_name
|
||||
from .util import FatalError, NotImplementedInROMError, UnsupportedCommandError
|
||||
from .util import byte, hexify, mask_to_shift, pad_to, strip_chip_name
|
||||
|
||||
try:
|
||||
import serial
|
||||
@@ -59,7 +61,7 @@ except ImportError:
|
||||
print(
|
||||
"The installed version (%s) of pyserial appears to be too old for esptool.py "
|
||||
"(Python interpreter %s). Check the README for installation instructions."
|
||||
% (sys.VERSION, sys.executable)
|
||||
% (serial.VERSION, sys.executable)
|
||||
)
|
||||
raise
|
||||
except Exception:
|
||||
@@ -96,14 +98,8 @@ DEFAULT_SERIAL_WRITE_TIMEOUT = cfg.getfloat("serial_write_timeout", 10)
|
||||
DEFAULT_CONNECT_ATTEMPTS = cfg.getint("connect_attempts", 7)
|
||||
# Number of times to try writing a data block
|
||||
WRITE_BLOCK_ATTEMPTS = cfg.getint("write_block_attempts", 3)
|
||||
|
||||
STUBS_DIR = os.path.join(os.path.dirname(__file__), "targets", "stub_flasher")
|
||||
|
||||
|
||||
def get_stub_json_path(chip_name):
|
||||
chip_name = strip_chip_name(chip_name)
|
||||
chip_name = chip_name.replace("esp", "")
|
||||
return os.path.join(STUBS_DIR, f"stub_flasher_{chip_name}.json")
|
||||
# Number of times to try opening the serial port
|
||||
DEFAULT_OPEN_PORT_ATTEMPTS = cfg.getint("open_port_attempts", 1)
|
||||
|
||||
|
||||
def timeout_per_mb(seconds_per_mb, size_bytes):
|
||||
@@ -155,8 +151,12 @@ def esp32s3_or_newer_function_only(func):
|
||||
|
||||
|
||||
class StubFlasher:
|
||||
def __init__(self, json_path):
|
||||
with open(json_path) as json_file:
|
||||
STUB_DIR = os.path.join(os.path.dirname(__file__), "targets", "stub_flasher")
|
||||
# directories will be searched in the order of STUB_SUBDIRS
|
||||
STUB_SUBDIRS = ["1", "2"]
|
||||
|
||||
def __init__(self, chip_name):
|
||||
with open(self.get_json_path(chip_name)) as json_file:
|
||||
stub = json.load(json_file)
|
||||
|
||||
self.text = base64.b64decode(stub["text"])
|
||||
@@ -172,6 +172,26 @@ class StubFlasher:
|
||||
|
||||
self.bss_start = stub.get("bss_start")
|
||||
|
||||
def get_json_path(self, chip_name):
|
||||
chip_name = strip_chip_name(chip_name)
|
||||
for i, subdir in enumerate(self.STUB_SUBDIRS):
|
||||
json_path = os.path.join(self.STUB_DIR, subdir, f"{chip_name}.json")
|
||||
if os.path.exists(json_path):
|
||||
if i:
|
||||
print(
|
||||
f"Warning: Stub version {self.STUB_SUBDIRS[0]} doesn't exist, using {subdir} instead"
|
||||
)
|
||||
|
||||
return json_path
|
||||
else:
|
||||
raise FileNotFoundError(f"Stub flasher JSON file for {chip_name} not found")
|
||||
|
||||
@classmethod
|
||||
def set_preferred_stub_subdir(cls, subdir):
|
||||
if subdir in cls.STUB_SUBDIRS:
|
||||
cls.STUB_SUBDIRS.remove(subdir)
|
||||
cls.STUB_SUBDIRS.insert(0, subdir)
|
||||
|
||||
|
||||
class ESPLoader(object):
|
||||
"""Base class providing access to ESP ROM & software stub bootloaders.
|
||||
@@ -179,12 +199,15 @@ class ESPLoader(object):
|
||||
|
||||
Don't instantiate this base class directly, either instantiate a subclass or
|
||||
call cmds.detect_chip() which will interrogate the chip and return the
|
||||
appropriate subclass instance.
|
||||
appropriate subclass instance. You can also use a context manager as
|
||||
"with detect_chip() as esp:" to ensure the serial port is closed when done.
|
||||
|
||||
"""
|
||||
|
||||
CHIP_NAME = "Espressif device"
|
||||
IS_STUB = False
|
||||
STUB_CLASS: Optional[object] = None
|
||||
BOOTLOADER_IMAGE: Optional[object] = None
|
||||
|
||||
DEFAULT_PORT = "/dev/ttyUSB0"
|
||||
|
||||
@@ -201,7 +224,7 @@ class ESPLoader(object):
|
||||
ESP_WRITE_REG = 0x09
|
||||
ESP_READ_REG = 0x0A
|
||||
|
||||
# Some comands supported by ESP32 and later chips ROM bootloader (or -8266 w/ stub)
|
||||
# Some commands supported by ESP32 and later chips ROM bootloader (or -8266 w/ stub)
|
||||
ESP_SPI_SET_PARAMS = 0x0B
|
||||
ESP_SPI_ATTACH = 0x0D
|
||||
ESP_READ_FLASH_SLOW = 0x0E # ROM only, much slower than the stub flash read
|
||||
@@ -245,6 +268,9 @@ class ESPLoader(object):
|
||||
|
||||
UART_DATE_REG_ADDR = 0x60000078
|
||||
|
||||
# Whether the SPI peripheral sends from MSB of 32-bit register, or the MSB of valid LSB bits.
|
||||
SPI_ADDR_REG_MSB = True
|
||||
|
||||
# This ROM address has a different value on each chip model
|
||||
CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000
|
||||
|
||||
@@ -273,11 +299,15 @@ class ESPLoader(object):
|
||||
# Chip IDs that are no longer supported by esptool
|
||||
UNSUPPORTED_CHIPS = {6: "ESP32-S3(beta 3)"}
|
||||
|
||||
# Number of attempts to write flash data
|
||||
WRITE_FLASH_ATTEMPTS = 2
|
||||
|
||||
def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled=False):
|
||||
"""Base constructor for ESPLoader bootloader interaction
|
||||
|
||||
Don't call this constructor, either instantiate a specific
|
||||
ROM class directly, or use cmds.detect_chip().
|
||||
ROM class directly, or use cmds.detect_chip(). You can use the with
|
||||
statement to ensure the serial port is closed when done.
|
||||
|
||||
This base class has all of the instance methods for bootloader
|
||||
functionality supported across various chips & stub
|
||||
@@ -300,7 +330,17 @@ class ESPLoader(object):
|
||||
|
||||
if isinstance(port, str):
|
||||
try:
|
||||
self._port = serial.serial_for_url(port)
|
||||
self._port = serial.serial_for_url(
|
||||
port, exclusive=True, do_not_open=True
|
||||
)
|
||||
if sys.platform == "win32":
|
||||
# When opening a port on Windows,
|
||||
# the RTS/DTR (active low) lines
|
||||
# need to be set to False (pulled high)
|
||||
# to avoid unwanted chip reset
|
||||
self._port.rts = False
|
||||
self._port.dtr = False
|
||||
self._port.open()
|
||||
except serial.serialutil.SerialException as e:
|
||||
port_issues = [
|
||||
[ # does not exist error
|
||||
@@ -316,10 +356,7 @@ class ESPLoader(object):
|
||||
port_issues.append(
|
||||
[ # permission denied error
|
||||
re.compile(r"Permission denied", re.IGNORECASE),
|
||||
(
|
||||
"Try to add user into dialout group: "
|
||||
"sudo usermod -a -G dialout $USER"
|
||||
),
|
||||
("Try to add user into dialout or uucp group."),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -351,6 +388,12 @@ class ESPLoader(object):
|
||||
# need to set the property back to None or it will continue to fail
|
||||
self._port.write_timeout = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self._port.close()
|
||||
|
||||
@property
|
||||
def serial_port(self):
|
||||
return self._port.port
|
||||
@@ -499,7 +542,7 @@ class ESPLoader(object):
|
||||
|
||||
# ROM bootloaders send some non-zero "val" response. The flasher stub sends 0.
|
||||
# If we receive 0 then it probably indicates that the chip wasn't or couldn't be
|
||||
# reseted properly and esptool is talking to the flasher stub.
|
||||
# reset properly and esptool is talking to the flasher stub.
|
||||
self.sync_stub_detected = val == 0
|
||||
|
||||
for _ in range(7):
|
||||
@@ -665,6 +708,15 @@ class ESPLoader(object):
|
||||
"Connection may fail if the chip is not in bootloader "
|
||||
"or flasher stub mode.",
|
||||
)
|
||||
|
||||
if self._port.name.startswith("socket:"):
|
||||
mode = "no_reset" # not possible to toggle DTR/RTS over a TCP socket
|
||||
print(
|
||||
"Note: It's not possible to reset the chip over a TCP socket. "
|
||||
"Automatic resetting to bootloader has been disabled, "
|
||||
"reset the chip manually."
|
||||
)
|
||||
|
||||
print("Connecting...", end="")
|
||||
sys.stdout.flush()
|
||||
last_error = None
|
||||
@@ -701,7 +753,7 @@ class ESPLoader(object):
|
||||
|
||||
if not detecting:
|
||||
try:
|
||||
from targets import ROM_LIST
|
||||
from .targets import ROM_LIST
|
||||
|
||||
# check the date code registers match what we expect to see
|
||||
chip_magic_value = self.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR)
|
||||
@@ -788,11 +840,14 @@ class ESPLoader(object):
|
||||
"""Start downloading an application image to RAM"""
|
||||
# check we're not going to overwrite a running stub with this data
|
||||
if self.IS_STUB:
|
||||
stub = StubFlasher(get_stub_json_path(self.CHIP_NAME))
|
||||
stub = StubFlasher(self.CHIP_NAME)
|
||||
load_start = offset
|
||||
load_end = offset + size
|
||||
for stub_start, stub_end in [
|
||||
(stub.bss_start, stub.data_start + len(stub.data)), # DRAM = bss+data
|
||||
(
|
||||
stub.bss_start or stub.data_start,
|
||||
stub.data_start + len(stub.data),
|
||||
), # DRAM = bss+data
|
||||
(stub.text_start, stub.text_start + len(stub.text)), # IRAM
|
||||
]:
|
||||
if load_start < stub_end and load_end > stub_start:
|
||||
@@ -993,7 +1048,7 @@ class ESPLoader(object):
|
||||
|
||||
def run_stub(self, stub=None):
|
||||
if stub is None:
|
||||
stub = StubFlasher(get_stub_json_path(self.CHIP_NAME))
|
||||
stub = StubFlasher(self.CHIP_NAME)
|
||||
|
||||
if self.sync_stub_detected:
|
||||
print("Stub is already running. No upload is necessary.")
|
||||
@@ -1349,7 +1404,9 @@ class ESPLoader(object):
|
||||
self.write_reg(
|
||||
SPI_USR2_REG, (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflash_command
|
||||
)
|
||||
if addr and addr_len > 0:
|
||||
if addr_len > 0:
|
||||
if self.SPI_ADDR_REG_MSB:
|
||||
addr = addr << (32 - addr_len)
|
||||
self.write_reg(SPI_ADDR_REG, addr)
|
||||
if data_bits == 0:
|
||||
self.write_reg(SPI_W0_REG, 0) # clear data register before we read it
|
||||
@@ -1454,7 +1511,12 @@ class ESPLoader(object):
|
||||
# See the self.XTAL_CLK_DIVIDER parameter for this factor.
|
||||
uart_div = self.read_reg(self.UART_CLKDIV_REG) & self.UART_CLKDIV_MASK
|
||||
est_xtal = (self._port.baudrate * uart_div) / 1e6 / self.XTAL_CLK_DIVIDER
|
||||
norm_xtal = 40 if est_xtal > 33 else 26
|
||||
if est_xtal > 45:
|
||||
norm_xtal = 48
|
||||
elif est_xtal > 33:
|
||||
norm_xtal = 40
|
||||
else:
|
||||
norm_xtal = 26
|
||||
if abs(norm_xtal - est_xtal) > 1:
|
||||
print(
|
||||
"WARNING: Detected crystal freq %.2fMHz is quite different to "
|
||||
@@ -1636,12 +1698,14 @@ class HexFormatter(object):
|
||||
while len(s) > 0:
|
||||
line = s[:16]
|
||||
ascii_line = "".join(
|
||||
c
|
||||
if (
|
||||
c == " "
|
||||
or (c in string.printable and c not in string.whitespace)
|
||||
(
|
||||
c
|
||||
if (
|
||||
c == " "
|
||||
or (c in string.printable and c not in string.whitespace)
|
||||
)
|
||||
else "."
|
||||
)
|
||||
else "."
|
||||
for c in line.decode("ascii", "replace")
|
||||
)
|
||||
s = s[16:]
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import errno
|
||||
import os
|
||||
import struct
|
||||
import time
|
||||
|
||||
from util import FatalError
|
||||
from .util import FatalError, PrintOnce
|
||||
|
||||
# Used for resetting into bootloader on Unix-like systems
|
||||
if os.name != "nt":
|
||||
@@ -26,11 +27,41 @@ DEFAULT_RESET_DELAY = 0.05 # default time to wait before releasing boot pin aft
|
||||
|
||||
|
||||
class ResetStrategy(object):
|
||||
print_once = PrintOnce()
|
||||
|
||||
def __init__(self, port, reset_delay=DEFAULT_RESET_DELAY):
|
||||
self.port = port
|
||||
self.reset_delay = reset_delay
|
||||
|
||||
def __call__():
|
||||
def __call__(self):
|
||||
"""
|
||||
On targets with USB modes, the reset process can cause the port to
|
||||
disconnect / reconnect during reset.
|
||||
This will retry reconnections on ports that
|
||||
drop out during the reset sequence.
|
||||
"""
|
||||
for retry in reversed(range(3)):
|
||||
try:
|
||||
if not self.port.isOpen():
|
||||
self.port.open()
|
||||
self.reset()
|
||||
break
|
||||
except OSError as e:
|
||||
# ENOTTY for TIOCMSET; EINVAL for TIOCMGET
|
||||
if e.errno in [errno.ENOTTY, errno.EINVAL]:
|
||||
self.print_once(
|
||||
"WARNING: Chip was NOT reset. Setting RTS/DTR lines is not "
|
||||
f"supported for port '{self.port.name}'. Set --before and --after "
|
||||
"arguments to 'no_reset' and switch to bootloader manually to "
|
||||
"avoid this warning."
|
||||
)
|
||||
break
|
||||
elif not retry:
|
||||
raise
|
||||
self.port.close()
|
||||
time.sleep(0.5)
|
||||
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
def _setDTR(self, state):
|
||||
@@ -63,7 +94,7 @@ class ClassicReset(ResetStrategy):
|
||||
Classic reset sequence, sets DTR and RTS lines sequentially.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
def reset(self):
|
||||
self._setDTR(False) # IO0=HIGH
|
||||
self._setRTS(True) # EN=LOW, chip in reset
|
||||
time.sleep(0.1)
|
||||
@@ -79,7 +110,7 @@ class UnixTightReset(ResetStrategy):
|
||||
which allows setting DTR and RTS lines at the same time.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
def reset(self):
|
||||
self._setDTRandRTS(False, False)
|
||||
self._setDTRandRTS(True, True)
|
||||
self._setDTRandRTS(False, True) # IO0=HIGH & EN=LOW, chip in reset
|
||||
@@ -96,7 +127,7 @@ class USBJTAGSerialReset(ResetStrategy):
|
||||
is connecting via its USB-JTAG-Serial peripheral.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
def reset(self):
|
||||
self._setRTS(False)
|
||||
self._setDTR(False) # Idle
|
||||
time.sleep(0.1)
|
||||
@@ -117,13 +148,13 @@ class HardReset(ResetStrategy):
|
||||
Can be used to reset out of the bootloader or to restart a running app.
|
||||
"""
|
||||
|
||||
def __init__(self, port, uses_usb_otg=False):
|
||||
def __init__(self, port, uses_usb=False):
|
||||
super().__init__(port)
|
||||
self.uses_usb_otg = uses_usb_otg
|
||||
self.uses_usb = uses_usb
|
||||
|
||||
def __call__(self):
|
||||
def reset(self):
|
||||
self._setRTS(True) # EN->LOW
|
||||
if self.uses_usb_otg:
|
||||
if self.uses_usb:
|
||||
# Give the chip some time to come out of reset,
|
||||
# to be able to handle further DTR/RTS transitions
|
||||
time.sleep(0.2)
|
||||
@@ -162,7 +193,7 @@ class CustomReset(ResetStrategy):
|
||||
"U": "self._setDTRandRTS({})",
|
||||
}
|
||||
|
||||
def __call__(self):
|
||||
def reset(self):
|
||||
exec(self.constructed_strategy)
|
||||
|
||||
def __init__(self, port, seq_str):
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from .esp32 import ESP32ROM
|
||||
from .esp32c2 import ESP32C2ROM
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from .esp32c5 import ESP32C5ROM
|
||||
from .esp32c5beta3 import ESP32C5BETA3ROM
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
from .esp32c61 import ESP32C61ROM
|
||||
from .esp32c6beta import ESP32C6BETAROM
|
||||
from .esp32h2 import ESP32H2ROM
|
||||
from .esp32h2beta1 import ESP32H2BETA1ROM
|
||||
@@ -25,6 +28,9 @@ CHIP_DEFS = {
|
||||
"esp32h2beta2": ESP32H2BETA2ROM,
|
||||
"esp32c2": ESP32C2ROM,
|
||||
"esp32c6": ESP32C6ROM,
|
||||
"esp32c61": ESP32C61ROM,
|
||||
"esp32c5": ESP32C5ROM,
|
||||
"esp32c5beta3": ESP32C5BETA3ROM,
|
||||
"esp32h2": ESP32H2ROM,
|
||||
"esp32p4": ESP32P4ROM,
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
import struct
|
||||
import time
|
||||
from typing import Dict, Optional
|
||||
|
||||
from loader import ESPLoader
|
||||
from util import FatalError, NotSupportedError
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError, NotSupportedError
|
||||
|
||||
|
||||
class ESP32ROM(ESPLoader):
|
||||
@@ -36,6 +37,9 @@ class ESP32ROM(ESPLoader):
|
||||
SPI_MISO_DLEN_OFFS = 0x2C
|
||||
EFUSE_RD_REG_BASE = 0x3FF5A000
|
||||
|
||||
EFUSE_BLK0_RDATA3_REG_OFFS = EFUSE_RD_REG_BASE + 0x00C
|
||||
EFUSE_BLK0_RDATA5_REG_OFFS = EFUSE_RD_REG_BASE + 0x014
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + 0x18
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 7 # EFUSE_RD_DISABLE_DL_ENCRYPT
|
||||
|
||||
@@ -46,6 +50,11 @@ class ESP32ROM(ESPLoader):
|
||||
EFUSE_RD_ABS_DONE_0_MASK = 1 << 4
|
||||
EFUSE_RD_ABS_DONE_1_MASK = 1 << 5
|
||||
|
||||
EFUSE_VDD_SPI_REG = EFUSE_RD_REG_BASE + 0x10
|
||||
VDD_SPI_XPD = 1 << 14 # XPD_SDIO_REG
|
||||
VDD_SPI_TIEH = 1 << 15 # XPD_SDIO_TIEH
|
||||
VDD_SPI_FORCE = 1 << 16 # XPD_SDIO_FORCE
|
||||
|
||||
DR_REG_SYSCON_BASE = 0x3FF66000
|
||||
APB_CTL_DATE_ADDR = DR_REG_SYSCON_BASE + 0x7C
|
||||
APB_CTL_DATE_V = 0x1
|
||||
@@ -61,6 +70,17 @@ class ESP32ROM(ESPLoader):
|
||||
TIMERS_RTC_CALI_VALUE = 0x01FFFFFF
|
||||
TIMERS_RTC_CALI_VALUE_S = 7
|
||||
|
||||
GPIO_STRAP_REG = 0x3FF44038
|
||||
GPIO_STRAP_VDDSPI_MASK = 1 << 5 # GPIO_STRAP_VDDSDIO
|
||||
|
||||
RTC_CNTL_SDIO_CONF_REG = 0x3FF48074
|
||||
RTC_CNTL_XPD_SDIO_REG = 1 << 31
|
||||
RTC_CNTL_DREFH_SDIO_M = 3 << 29
|
||||
RTC_CNTL_DREFM_SDIO_M = 3 << 27
|
||||
RTC_CNTL_DREFL_SDIO_M = 3 << 25
|
||||
RTC_CNTL_SDIO_FORCE = 1 << 22
|
||||
RTC_CNTL_SDIO_PD_EN = 1 << 21
|
||||
|
||||
FLASH_SIZES = {
|
||||
"1MB": 0x00,
|
||||
"2MB": 0x10,
|
||||
@@ -105,6 +125,8 @@ class ESP32ROM(ESPLoader):
|
||||
|
||||
UF2_FAMILY_ID = 0x1C5F21B0
|
||||
|
||||
KEY_PURPOSES: Dict[int, str] = {}
|
||||
|
||||
""" Try to read the BLOCK1 (encryption key) and check if it is valid """
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
@@ -121,7 +143,7 @@ class ESP32ROM(ESPLoader):
|
||||
# When ESP32 has not generated AES/encryption key in BLOCK1,
|
||||
# the contents will be readable and 0.
|
||||
# If the flash encryption is enabled it is expected to have a valid
|
||||
# non-zero key. We break out on first occurance of non-zero value
|
||||
# non-zero key. We break out on first occurrence of non-zero value
|
||||
key_word = [0] * 7
|
||||
for i in range(len(key_word)):
|
||||
key_word[i] = self.read_efuse(14 + i)
|
||||
@@ -205,11 +227,11 @@ class ESP32ROM(ESPLoader):
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
rev3 = major_rev == 3
|
||||
single_core = self.read_efuse(3) & (1 << 0) # CHIP_VER DIS_APP_CPU
|
||||
sc = self.read_efuse(3) & (1 << 0) # single core, CHIP_VER DIS_APP_CPU
|
||||
|
||||
chip_name = {
|
||||
0: "ESP32-S0WDQ6" if single_core else "ESP32-D0WDQ6",
|
||||
1: "ESP32-S0WD" if single_core else "ESP32-D0WD",
|
||||
0: "ESP32-S0WDQ6" if sc else "ESP32-D0WDQ6-V3" if rev3 else "ESP32-D0WDQ6",
|
||||
1: "ESP32-S0WD" if sc else "ESP32-D0WD-V3" if rev3 else "ESP32-D0WD",
|
||||
2: "ESP32-D2WD",
|
||||
4: "ESP32-U4WDH",
|
||||
5: "ESP32-PICO-V3" if rev3 else "ESP32-PICO-D4",
|
||||
@@ -217,10 +239,6 @@ class ESP32ROM(ESPLoader):
|
||||
7: "ESP32-D0WDR2-V3",
|
||||
}.get(pkg_version, "unknown ESP32")
|
||||
|
||||
# ESP32-D0WD-V3, ESP32-D0WDQ6-V3
|
||||
if chip_name.startswith("ESP32-D0WD") and rev3:
|
||||
chip_name += "-V3"
|
||||
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
@@ -269,13 +287,30 @@ class ESP32ROM(ESPLoader):
|
||||
coding_scheme = word6 & 0x3
|
||||
features += [
|
||||
"Coding Scheme %s"
|
||||
% {0: "None", 1: "3/4", 2: "Repeat (UNSUPPORTED)", 3: "Invalid"}[
|
||||
coding_scheme
|
||||
]
|
||||
% {
|
||||
0: "None",
|
||||
1: "3/4",
|
||||
2: "Repeat (UNSUPPORTED)",
|
||||
3: "None (may contain encoding data)",
|
||||
}[coding_scheme]
|
||||
]
|
||||
|
||||
return features
|
||||
|
||||
def get_chip_spi_pads(self):
|
||||
"""Read chip spi pad config
|
||||
return: clk, q, d, hd, cd
|
||||
"""
|
||||
efuse_blk0_rdata5 = self.read_reg(self.EFUSE_BLK0_RDATA5_REG_OFFS)
|
||||
spi_pad_clk = efuse_blk0_rdata5 & 0x1F
|
||||
spi_pad_q = (efuse_blk0_rdata5 >> 5) & 0x1F
|
||||
spi_pad_d = (efuse_blk0_rdata5 >> 10) & 0x1F
|
||||
spi_pad_cs = (efuse_blk0_rdata5 >> 15) & 0x1F
|
||||
|
||||
efuse_blk0_rdata3_reg = self.read_reg(self.EFUSE_BLK0_RDATA3_REG_OFFS)
|
||||
spi_pad_hd = (efuse_blk0_rdata3_reg >> 4) & 0x1F
|
||||
return spi_pad_clk, spi_pad_q, spi_pad_d, spi_pad_hd, spi_pad_cs
|
||||
|
||||
def read_efuse(self, n):
|
||||
"""Read the nth word of the ESP3x EFUSE region."""
|
||||
return self.read_reg(self.EFUSE_RD_REG_BASE + (4 * n))
|
||||
@@ -295,32 +330,63 @@ class ESP32ROM(ESPLoader):
|
||||
def get_erase_size(self, offset, size):
|
||||
return size
|
||||
|
||||
def _get_efuse_flash_voltage(self) -> Optional[str]:
|
||||
efuse = self.read_reg(self.EFUSE_VDD_SPI_REG)
|
||||
# check efuse setting
|
||||
if efuse & (self.VDD_SPI_FORCE | self.VDD_SPI_XPD | self.VDD_SPI_TIEH):
|
||||
return "3.3V"
|
||||
elif efuse & (self.VDD_SPI_FORCE | self.VDD_SPI_XPD):
|
||||
return "1.8V"
|
||||
elif efuse & self.VDD_SPI_FORCE:
|
||||
return "OFF"
|
||||
return None
|
||||
|
||||
def _get_rtc_cntl_flash_voltage(self) -> Optional[str]:
|
||||
reg = self.read_reg(self.RTC_CNTL_SDIO_CONF_REG)
|
||||
# check if override is set in RTC_CNTL_SDIO_CONF_REG
|
||||
if reg & self.RTC_CNTL_SDIO_FORCE:
|
||||
if reg & self.RTC_CNTL_DREFH_SDIO_M:
|
||||
return "1.9V"
|
||||
elif reg & self.RTC_CNTL_XPD_SDIO_REG:
|
||||
return "1.8V"
|
||||
else:
|
||||
return "OFF"
|
||||
return None
|
||||
|
||||
def get_flash_voltage(self):
|
||||
"""Get flash voltage setting and print it to the console."""
|
||||
voltage = self._get_rtc_cntl_flash_voltage()
|
||||
source = "RTC_CNTL"
|
||||
if not voltage:
|
||||
voltage = self._get_efuse_flash_voltage()
|
||||
source = "eFuse"
|
||||
if not voltage:
|
||||
strap_reg = self.read_reg(self.GPIO_STRAP_REG)
|
||||
strap_reg &= self.GPIO_STRAP_VDDSPI_MASK
|
||||
voltage = "1.8V" if strap_reg else "3.3V"
|
||||
source = "a strapping pin"
|
||||
print(f"Flash voltage set by {source} to {voltage}")
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
new_voltage = new_voltage.upper()
|
||||
if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES:
|
||||
raise FatalError(
|
||||
"The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'"
|
||||
f"The only accepted VDDSDIO overrides are {', '.join(self.OVERRIDE_VDDSDIO_CHOICES)}"
|
||||
)
|
||||
RTC_CNTL_SDIO_CONF_REG = 0x3FF48074
|
||||
RTC_CNTL_XPD_SDIO_REG = 1 << 31
|
||||
RTC_CNTL_DREFH_SDIO_M = 3 << 29
|
||||
RTC_CNTL_DREFM_SDIO_M = 3 << 27
|
||||
RTC_CNTL_DREFL_SDIO_M = 3 << 25
|
||||
# RTC_CNTL_SDIO_TIEH = (1 << 23)
|
||||
# not used here, setting TIEH=1 would set 3.3V output,
|
||||
# RTC_CNTL_SDIO_TIEH is not used here, setting TIEH=1 would set 3.3V output,
|
||||
# not safe for esptool.py to do
|
||||
RTC_CNTL_SDIO_FORCE = 1 << 22
|
||||
RTC_CNTL_SDIO_PD_EN = 1 << 21
|
||||
|
||||
reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting
|
||||
reg_val |= RTC_CNTL_SDIO_PD_EN
|
||||
reg_val = self.RTC_CNTL_SDIO_FORCE # override efuse setting
|
||||
reg_val |= self.RTC_CNTL_SDIO_PD_EN
|
||||
if new_voltage != "OFF":
|
||||
reg_val |= RTC_CNTL_XPD_SDIO_REG # enable internal LDO
|
||||
reg_val |= self.RTC_CNTL_XPD_SDIO_REG # enable internal LDO
|
||||
if new_voltage == "1.9V":
|
||||
reg_val |= (
|
||||
RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M
|
||||
self.RTC_CNTL_DREFH_SDIO_M
|
||||
| self.RTC_CNTL_DREFM_SDIO_M
|
||||
| self.RTC_CNTL_DREFL_SDIO_M
|
||||
) # boost voltage
|
||||
self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val)
|
||||
self.write_reg(self.RTC_CNTL_SDIO_CONF_REG, reg_val)
|
||||
print("VDDSDIO regulator set to %s" % new_voltage)
|
||||
|
||||
def read_flash_slow(self, offset, length, progress_fn):
|
||||
@@ -329,11 +395,17 @@ class ESP32ROM(ESPLoader):
|
||||
data = b""
|
||||
while len(data) < length:
|
||||
block_len = min(BLOCK_LEN, length - len(data))
|
||||
r = self.check_command(
|
||||
"read flash block",
|
||||
self.ESP_READ_FLASH_SLOW,
|
||||
struct.pack("<II", offset + len(data), block_len),
|
||||
)
|
||||
try:
|
||||
r = self.check_command(
|
||||
"read flash block",
|
||||
self.ESP_READ_FLASH_SLOW,
|
||||
struct.pack("<II", offset + len(data), block_len),
|
||||
)
|
||||
except FatalError:
|
||||
print(
|
||||
"Hint: Consider specifying flash size using '--flash_size' argument"
|
||||
)
|
||||
raise
|
||||
if len(r) < block_len:
|
||||
raise FatalError(
|
||||
"Expected %d byte block, got %d bytes. Serial errors?"
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
|
||||
import struct
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from loader import ESPLoader
|
||||
from util import FatalError
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError
|
||||
|
||||
|
||||
class ESP32C2ROM(ESP32C3ROM):
|
||||
@@ -20,8 +21,8 @@ class ESP32C2ROM(ESP32C3ROM):
|
||||
DROM_MAP_START = 0x3C000000
|
||||
DROM_MAP_END = 0x3C400000
|
||||
|
||||
# Magic value for ESP32C2 ECO0 and ECO1 respectively
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x6F51306F, 0x7C41A06F]
|
||||
# Magic value for ESP32C2 ECO0 , ECO1 and ECO4 respectively
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x6F51306F, 0x7C41A06F, 0x0C21E06F]
|
||||
|
||||
EFUSE_BASE = 0x60008800
|
||||
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x040
|
||||
@@ -64,6 +65,8 @@ class ESP32C2ROM(ESP32C3ROM):
|
||||
|
||||
UF2_FAMILY_ID = 0x2B88D29C
|
||||
|
||||
KEY_PURPOSES: Dict[int, str] = {}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 1
|
||||
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 22) & 0x07
|
||||
@@ -146,7 +149,7 @@ class ESP32C2ROM(ESP32C3ROM):
|
||||
# When chip has not generated AES/encryption key in BLOCK3,
|
||||
# the contents will be readable and 0.
|
||||
# If the flash encryption is enabled it is expected to have a valid
|
||||
# non-zero key. We break out on first occurance of non-zero value
|
||||
# non-zero key. We break out on first occurrence of non-zero value
|
||||
key_word = [0] * 7 if key_len_256 else [0] * 3
|
||||
for i in range(len(key_word)):
|
||||
key_word[i] = self.read_reg(self.EFUSE_BLOCK_KEY0_REG + i * 4)
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from .esp32 import ESP32ROM
|
||||
from loader import ESPLoader
|
||||
from util import FatalError, NotImplementedInROMError
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32C3ROM(ESP32ROM):
|
||||
@@ -27,6 +28,8 @@ class ESP32C3ROM(ESP32ROM):
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
SPI_ADDR_REG_MSB = False
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0x0
|
||||
|
||||
# Magic values for ESP32-C3 eco 1+2, eco 3, eco 6, and eco 7 respectively
|
||||
@@ -99,6 +102,20 @@ class ESP32C3ROM(ESP32ROM):
|
||||
|
||||
UF2_FAMILY_ID = 0xD42BA06C
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "RESERVED",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07
|
||||
@@ -152,6 +169,9 @@ class ESP32C3ROM(ESP32ROM):
|
||||
# ESP32C3 XTAL is fixed to 40MHz
|
||||
return 40
|
||||
|
||||
def get_flash_voltage(self):
|
||||
pass # not supported on ESP32-C3
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-C3"
|
||||
@@ -176,8 +196,10 @@ class ESP32C3ROM(ESP32ROM):
|
||||
)
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
if key_block < 0 or key_block > self.EFUSE_MAX_KEY:
|
||||
raise FatalError(
|
||||
f"Valid key block numbers must be in range 0-{self.EFUSE_MAX_KEY}"
|
||||
)
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
@@ -191,7 +213,9 @@ class ESP32C3ROM(ESP32ROM):
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see an AES-128 key
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
purposes = [
|
||||
self.get_key_block_purpose(b) for b in range(self.EFUSE_MAX_KEY + 1)
|
||||
]
|
||||
|
||||
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
|
||||
|
||||
|
||||
190
tools/python/esptool/targets/esp32c5.py
Normal file
190
tools/python/esptool/targets/esp32c5.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
from ..loader import ESPLoader
|
||||
from ..reset import HardReset
|
||||
from ..util import FatalError
|
||||
|
||||
|
||||
class ESP32C5ROM(ESP32C6ROM):
|
||||
CHIP_NAME = "ESP32-C5"
|
||||
IMAGE_CHIP_ID = 23
|
||||
|
||||
EFUSE_BASE = 0x600B4800
|
||||
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
|
||||
MAC_EFUSE_REG = EFUSE_BASE + 0x044
|
||||
|
||||
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
|
||||
|
||||
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY0_SHIFT = 24
|
||||
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY1_SHIFT = 28
|
||||
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY2_SHIFT = 0
|
||||
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY3_SHIFT = 4
|
||||
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY4_SHIFT = 8
|
||||
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY5_SHIFT = 12
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
|
||||
|
||||
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
|
||||
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
|
||||
|
||||
IROM_MAP_START = 0x42000000
|
||||
IROM_MAP_END = 0x42800000
|
||||
DROM_MAP_START = 0x42800000
|
||||
DROM_MAP_END = 0x43000000
|
||||
|
||||
PCR_SYSCLK_CONF_REG = 0x60096110
|
||||
PCR_SYSCLK_XTAL_FREQ_V = 0x7F << 24
|
||||
PCR_SYSCLK_XTAL_FREQ_S = 24
|
||||
|
||||
UARTDEV_BUF_NO = 0x4085F51C # Variable in ROM .bss which indicates the port in use
|
||||
|
||||
# Magic value for ESP32C5
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x1101406F]
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"80m": 0xF,
|
||||
"40m": 0x0,
|
||||
"20m": 0x2,
|
||||
}
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x42800000, 0x43000000, "DROM"],
|
||||
[0x40800000, 0x40860000, "DRAM"],
|
||||
[0x40800000, 0x40860000, "BYTE_ACCESSIBLE"],
|
||||
[0x4003A000, 0x40040000, "DROM_MASK"],
|
||||
[0x40000000, 0x4003A000, "IROM_MASK"],
|
||||
[0x42000000, 0x42800000, "IROM"],
|
||||
[0x40800000, 0x40860000, "IRAM"],
|
||||
[0x50000000, 0x50004000, "RTC_IRAM"],
|
||||
[0x50000000, 0x50004000, "RTC_DRAM"],
|
||||
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
|
||||
]
|
||||
|
||||
UF2_FAMILY_ID = 0xF71C0343
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "ECDSA_KEY",
|
||||
2: "XTS_AES_256_KEY_1",
|
||||
3: "XTS_AES_256_KEY_2",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
12: "KM_INIT_KEY",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 26) & 0x07
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F
|
||||
|
||||
def get_major_chip_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 4) & 0x03
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-C5",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-C5")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_crystal_freq(self):
|
||||
# The crystal detection algorithm of ESP32/ESP8266
|
||||
# works for ESP32-C5 as well.
|
||||
return ESPLoader.get_crystal_freq(self)
|
||||
|
||||
def get_crystal_freq_rom_expect(self):
|
||||
return (
|
||||
self.read_reg(self.PCR_SYSCLK_CONF_REG) & self.PCR_SYSCLK_XTAL_FREQ_V
|
||||
) >> self.PCR_SYSCLK_XTAL_FREQ_S
|
||||
|
||||
def hard_reset(self):
|
||||
print("Hard resetting via RTS pin...")
|
||||
HardReset(self._port, self.uses_usb_jtag_serial())()
|
||||
|
||||
def change_baud(self, baud):
|
||||
if not self.IS_STUB:
|
||||
crystal_freq_rom_expect = self.get_crystal_freq_rom_expect()
|
||||
crystal_freq_detect = self.get_crystal_freq()
|
||||
print(
|
||||
f"ROM expects crystal freq: {crystal_freq_rom_expect} MHz, detected {crystal_freq_detect} MHz"
|
||||
)
|
||||
baud_rate = baud
|
||||
# If detect the XTAL is 48MHz, but the ROM code expects it to be 40MHz
|
||||
if crystal_freq_detect == 48 and crystal_freq_rom_expect == 40:
|
||||
baud_rate = baud * 40 // 48
|
||||
# If detect the XTAL is 40MHz, but the ROM code expects it to be 48MHz
|
||||
elif crystal_freq_detect == 40 and crystal_freq_rom_expect == 48:
|
||||
baud_rate = baud * 48 // 40
|
||||
else:
|
||||
ESPLoader.change_baud(self, baud_rate)
|
||||
return
|
||||
|
||||
print(f"Changing baud rate to {baud_rate}")
|
||||
self.command(self.ESP_CHANGE_BAUDRATE, struct.pack("<II", baud_rate, 0))
|
||||
print("Changed.")
|
||||
self._set_port_baudrate(baud)
|
||||
time.sleep(0.05) # get rid of garbage sent during baud rate change
|
||||
self.flush_input()
|
||||
else:
|
||||
ESPLoader.change_baud(self, baud)
|
||||
|
||||
def check_spi_connection(self, spi_connection):
|
||||
if not set(spi_connection).issubset(set(range(0, 29))):
|
||||
raise FatalError("SPI Pin numbers must be in the range 0-28.")
|
||||
if any([v for v in spi_connection if v in [13, 14]]):
|
||||
print(
|
||||
"WARNING: GPIO pins 13 and 14 are used by USB-Serial/JTAG, "
|
||||
"consider using other pins for SPI flash connection."
|
||||
)
|
||||
|
||||
|
||||
class ESP32C5StubLoader(ESP32C5ROM):
|
||||
"""Access class for ESP32C5 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32C5ROM.STUB_CLASS = ESP32C5StubLoader
|
||||
129
tools/python/esptool/targets/esp32c5beta3.py
Normal file
129
tools/python/esptool/targets/esp32c5beta3.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
from ..loader import ESPLoader
|
||||
|
||||
|
||||
class ESP32C5BETA3ROM(ESP32C6ROM):
|
||||
CHIP_NAME = "ESP32-C5(beta3)"
|
||||
IMAGE_CHIP_ID = 17
|
||||
|
||||
IROM_MAP_START = 0x41000000
|
||||
IROM_MAP_END = 0x41800000
|
||||
DROM_MAP_START = 0x41000000
|
||||
DROM_MAP_END = 0x41800000
|
||||
|
||||
# Magic value for ESP32C5(beta3)
|
||||
CHIP_DETECT_MAGIC_VALUE = [0xE10D8082]
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"80m": 0xF,
|
||||
"40m": 0x0,
|
||||
"20m": 0x2,
|
||||
}
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x41800000, 0x42000000, "DROM"],
|
||||
[0x40800000, 0x40880000, "DRAM"],
|
||||
[0x40800000, 0x40880000, "BYTE_ACCESSIBLE"],
|
||||
[0x4004A000, 0x40050000, "DROM_MASK"],
|
||||
[0x40000000, 0x4004A000, "IROM_MASK"],
|
||||
[0x41000000, 0x41800000, "IROM"],
|
||||
[0x40800000, 0x40880000, "IRAM"],
|
||||
[0x50000000, 0x50004000, "RTC_IRAM"],
|
||||
[0x50000000, 0x50004000, "RTC_DRAM"],
|
||||
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
|
||||
]
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "ECDSA_KEY",
|
||||
2: "XTS_AES_256_KEY_1",
|
||||
3: "XTS_AES_256_KEY_2",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
12: "KM_INIT_KEY",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 26) & 0x07
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F
|
||||
|
||||
def get_major_chip_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 4) & 0x03
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-C5 beta3 (QFN40)",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-C5 beta3")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_crystal_freq(self):
|
||||
# The crystal detection algorithm of ESP32/ESP8266
|
||||
# works for ESP32-C5 beta3 as well.
|
||||
return ESPLoader.get_crystal_freq(self)
|
||||
|
||||
def change_baud(self, baud):
|
||||
rom_with_48M_XTAL = not self.IS_STUB and self.get_crystal_freq() == 48
|
||||
if rom_with_48M_XTAL:
|
||||
# The code is copied over from ESPLoader.change_baud().
|
||||
# Probably this is just a temporary solution until the next chip revision.
|
||||
|
||||
# The ROM code thinks it uses a 40 MHz XTAL. Recompute the baud rate
|
||||
# in order to trick the ROM code to set the correct baud rate for
|
||||
# a 48 MHz XTAL.
|
||||
false_rom_baud = baud * 40 // 48
|
||||
|
||||
print(f"Changing baud rate to {baud}")
|
||||
self.command(
|
||||
self.ESP_CHANGE_BAUDRATE, struct.pack("<II", false_rom_baud, 0)
|
||||
)
|
||||
print("Changed.")
|
||||
self._set_port_baudrate(baud)
|
||||
time.sleep(0.05) # get rid of garbage sent during baud rate change
|
||||
self.flush_input()
|
||||
else:
|
||||
ESPLoader.change_baud(self, baud)
|
||||
|
||||
|
||||
class ESP32C5BETA3StubLoader(ESP32C5BETA3ROM):
|
||||
"""Access class for ESP32C5BETA3 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32C5BETA3ROM.STUB_CLASS = ESP32C5BETA3StubLoader
|
||||
@@ -6,7 +6,7 @@
|
||||
import struct
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from util import FatalError, NotImplementedInROMError
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32C6ROM(ESP32C3ROM):
|
||||
@@ -161,8 +161,10 @@ class ESP32C6ROM(ESP32C3ROM):
|
||||
)
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
if key_block < 0 or key_block > self.EFUSE_MAX_KEY:
|
||||
raise FatalError(
|
||||
f"Valid key block numbers must be in range 0-{self.EFUSE_MAX_KEY}"
|
||||
)
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
@@ -176,7 +178,9 @@ class ESP32C6ROM(ESP32C3ROM):
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see an AES-128 key
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
purposes = [
|
||||
self.get_key_block_purpose(b) for b in range(self.EFUSE_MAX_KEY + 1)
|
||||
]
|
||||
|
||||
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
|
||||
|
||||
|
||||
144
tools/python/esptool/targets/esp32c61.py
Normal file
144
tools/python/esptool/targets/esp32c61.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
|
||||
|
||||
class ESP32C61ROM(ESP32C6ROM):
|
||||
CHIP_NAME = "ESP32-C61"
|
||||
IMAGE_CHIP_ID = 20
|
||||
|
||||
# Magic value for ESP32C61
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x33F0206F, 0x2421606F]
|
||||
|
||||
UART_DATE_REG_ADDR = 0x60000000 + 0x7C
|
||||
|
||||
EFUSE_BASE = 0x600B4800
|
||||
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
|
||||
MAC_EFUSE_REG = EFUSE_BASE + 0x044
|
||||
|
||||
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
|
||||
|
||||
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY0_SHIFT = 0
|
||||
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY1_SHIFT = 4
|
||||
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY2_SHIFT = 8
|
||||
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY3_SHIFT = 12
|
||||
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY4_SHIFT = 16
|
||||
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY5_SHIFT = 20
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x030
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 23
|
||||
|
||||
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x034
|
||||
EFUSE_SECURE_BOOT_EN_MASK = 1 << 26
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"80m": 0xF,
|
||||
"40m": 0x0,
|
||||
"20m": 0x2,
|
||||
}
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x41800000, 0x42000000, "DROM"],
|
||||
[0x40800000, 0x40860000, "DRAM"],
|
||||
[0x40800000, 0x40860000, "BYTE_ACCESSIBLE"],
|
||||
[0x4004AC00, 0x40050000, "DROM_MASK"],
|
||||
[0x40000000, 0x4004AC00, "IROM_MASK"],
|
||||
[0x41000000, 0x41800000, "IROM"],
|
||||
[0x40800000, 0x40860000, "IRAM"],
|
||||
[0x50000000, 0x50004000, "RTC_IRAM"],
|
||||
[0x50000000, 0x50004000, "RTC_DRAM"],
|
||||
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
|
||||
]
|
||||
|
||||
UF2_FAMILY_ID = 0x77D850C4
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "ECDSA_KEY",
|
||||
2: "XTS_AES_256_KEY_1",
|
||||
3: "XTS_AES_256_KEY_2",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
12: "KM_INIT_KEY",
|
||||
13: "XTS_AES_256_KEY_1_PSRAM",
|
||||
14: "XTS_AES_256_KEY_2_PSRAM",
|
||||
15: "XTS_AES_128_KEY_PSRAM",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 26) & 0x07
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F
|
||||
|
||||
def get_major_chip_version(self):
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 4) & 0x03
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-C61",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-C61")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
return ["WiFi 6", "BT 5"]
|
||||
|
||||
def read_mac(self, mac_type="BASE_MAC"):
|
||||
"""Read MAC from EFUSE region"""
|
||||
mac0 = self.read_reg(self.MAC_EFUSE_REG)
|
||||
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
|
||||
base_mac = struct.pack(">II", mac1, mac0)[2:]
|
||||
# BASE MAC: 60:55:f9:f7:2c:a2
|
||||
macs = {
|
||||
"BASE_MAC": tuple(base_mac),
|
||||
}
|
||||
return macs.get(mac_type, None)
|
||||
|
||||
|
||||
class ESP32C61StubLoader(ESP32C61ROM):
|
||||
"""Access class for ESP32C61 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32C61ROM.STUB_CLASS = ESP32C61StubLoader
|
||||
@@ -3,8 +3,10 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
from util import FatalError
|
||||
from ..util import FatalError
|
||||
|
||||
|
||||
class ESP32H2ROM(ESP32C6ROM):
|
||||
@@ -32,6 +34,22 @@ class ESP32H2ROM(ESP32C6ROM):
|
||||
|
||||
UF2_FAMILY_ID = 0x332726F6
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "ECDSA_KEY",
|
||||
2: "XTS_AES_256_KEY_1",
|
||||
3: "XTS_AES_256_KEY_2",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 4
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x07
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from util import FatalError, NotImplementedInROMError
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
class ESP32H2BETA1ROM(ESP32C3ROM):
|
||||
@@ -66,7 +69,7 @@ class ESP32H2BETA1ROM(ESP32C3ROM):
|
||||
|
||||
FLASH_ENCRYPTED_WRITE_ALIGN = 16
|
||||
|
||||
MEMORY_MAP = []
|
||||
MEMORY_MAP: List = []
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"48m": 0xF,
|
||||
@@ -75,6 +78,21 @@ class ESP32H2BETA1ROM(ESP32C3ROM):
|
||||
"12m": 0x2,
|
||||
}
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "ECDSA_KEY",
|
||||
2: "RESERVED",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 4
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x07
|
||||
@@ -119,8 +137,10 @@ class ESP32H2BETA1ROM(ESP32C3ROM):
|
||||
return None # doesn't exist on ESP32-H2
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
if key_block < 0 or key_block > self.EFUSE_MAX_KEY:
|
||||
raise FatalError(
|
||||
f"Valid key block numbers must be in range 0-{self.EFUSE_MAX_KEY}"
|
||||
)
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
@@ -134,7 +154,9 @@ class ESP32H2BETA1ROM(ESP32C3ROM):
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see an AES-128 key
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
purposes = [
|
||||
self.get_key_block_purpose(b) for b in range(self.EFUSE_MAX_KEY + 1)
|
||||
]
|
||||
|
||||
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from .esp32 import ESP32ROM
|
||||
from loader import ESPLoader
|
||||
from util import FatalError, NotImplementedInROMError
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32P4ROM(ESP32ROM):
|
||||
@@ -21,7 +22,7 @@ class ESP32P4ROM(ESP32ROM):
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0x2000 # First 2 sectors are reserved for FE purposes
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x0]
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x0, 0x0ADDBAD0]
|
||||
|
||||
UART_DATE_REG_ADDR = 0x500CA000 + 0x8C
|
||||
|
||||
@@ -37,6 +38,8 @@ class ESP32P4ROM(ESP32ROM):
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
SPI_ADDR_REG_MSB = False
|
||||
|
||||
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
|
||||
|
||||
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
|
||||
@@ -85,17 +88,34 @@ class ESP32P4ROM(ESP32ROM):
|
||||
|
||||
UF2_FAMILY_ID = 0x3D308E94
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "ECDSA_KEY",
|
||||
2: "XTS_AES_256_KEY_1",
|
||||
3: "XTS_AES_256_KEY_2",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
12: "KM_INIT_KEY",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
# ESP32P4 TODO
|
||||
return 0
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 20) & 0x07
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
# ESP32P4 TODO
|
||||
return 0
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F
|
||||
|
||||
def get_major_chip_version(self):
|
||||
# ESP32P4 TODO
|
||||
return 0
|
||||
num_word = 2
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 4) & 0x03
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
@@ -112,6 +132,9 @@ class ESP32P4ROM(ESP32ROM):
|
||||
# ESP32P4 XTAL is fixed to 40MHz
|
||||
return 40
|
||||
|
||||
def get_flash_voltage(self):
|
||||
pass # not supported on ESP32-P4
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-P4"
|
||||
@@ -136,8 +159,10 @@ class ESP32P4ROM(ESP32ROM):
|
||||
)
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
if key_block < 0 or key_block > self.EFUSE_MAX_KEY:
|
||||
raise FatalError(
|
||||
f"Valid key block numbers must be in range 0-{self.EFUSE_MAX_KEY}"
|
||||
)
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
@@ -151,7 +176,9 @@ class ESP32P4ROM(ESP32ROM):
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see either an AES-128 key or two AES-256 keys
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
purposes = [
|
||||
self.get_key_block_purpose(b) for b in range(self.EFUSE_MAX_KEY + 1)
|
||||
]
|
||||
|
||||
if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes):
|
||||
return True
|
||||
@@ -170,7 +197,13 @@ class ESP32P4ROM(ESP32ROM):
|
||||
# self.disable_watchdogs()
|
||||
|
||||
def check_spi_connection(self, spi_connection):
|
||||
pass # TODO: Define GPIOs for --spi-connection
|
||||
if not set(spi_connection).issubset(set(range(0, 55))):
|
||||
raise FatalError("SPI Pin numbers must be in the range 0-54.")
|
||||
if any([v for v in spi_connection if v in [24, 25]]):
|
||||
print(
|
||||
"WARNING: GPIO pins 24 and 25 are used by USB-Serial/JTAG, "
|
||||
"consider using other pins for SPI flash connection."
|
||||
)
|
||||
|
||||
|
||||
class ESP32P4StubLoader(ESP32P4ROM):
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import os
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from .esp32 import ESP32ROM
|
||||
from loader import ESPLoader
|
||||
from reset import HardReset
|
||||
from util import FatalError, NotImplementedInROMError
|
||||
from ..loader import ESPLoader
|
||||
from ..reset import HardReset
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32S2ROM(ESP32ROM):
|
||||
@@ -31,6 +32,8 @@ class ESP32S2ROM(ESP32ROM):
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
SPI_ADDR_REG_MSB = False
|
||||
|
||||
MAC_EFUSE_REG = 0x3F41A044 # ESP32-S2 has special block for MAC efuses
|
||||
|
||||
UART_CLKDIV_REG = 0x3F400014
|
||||
@@ -81,6 +84,7 @@ class ESP32S2ROM(ESP32ROM):
|
||||
|
||||
GPIO_STRAP_REG = 0x3F404038
|
||||
GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode
|
||||
GPIO_STRAP_VDDSPI_MASK = 1 << 4
|
||||
RTC_CNTL_OPTION1_REG = 0x3F408128
|
||||
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB?
|
||||
|
||||
@@ -99,8 +103,29 @@ class ESP32S2ROM(ESP32ROM):
|
||||
[0x50000000, 0x50002000, "RTC_DATA"],
|
||||
]
|
||||
|
||||
EFUSE_VDD_SPI_REG = EFUSE_BASE + 0x34
|
||||
VDD_SPI_XPD = 1 << 4
|
||||
VDD_SPI_TIEH = 1 << 5
|
||||
VDD_SPI_FORCE = 1 << 6
|
||||
|
||||
UF2_FAMILY_ID = 0xBFDD4EEE
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "RESERVED",
|
||||
2: "XTS_AES_256_KEY_1",
|
||||
3: "XTS_AES_256_KEY_2",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 4
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F
|
||||
@@ -183,6 +208,9 @@ class ESP32S2ROM(ESP32ROM):
|
||||
# ESP32-S2 XTAL is fixed to 40MHz
|
||||
return 40
|
||||
|
||||
def _get_rtc_cntl_flash_voltage(self):
|
||||
return None # not supported on ESP32-S2
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-S2"
|
||||
@@ -215,8 +243,10 @@ class ESP32S2ROM(ESP32ROM):
|
||||
)
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
if key_block < 0 or key_block > self.EFUSE_MAX_KEY:
|
||||
raise FatalError(
|
||||
f"Valid key block numbers must be in range 0-{self.EFUSE_MAX_KEY}"
|
||||
)
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
@@ -230,7 +260,9 @@ class ESP32S2ROM(ESP32ROM):
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see either an AES-128 key or two AES-256 keys
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
purposes = [
|
||||
self.get_key_block_purpose(b) for b in range(self.EFUSE_MAX_KEY + 1)
|
||||
]
|
||||
|
||||
if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes):
|
||||
return True
|
||||
@@ -266,15 +298,12 @@ class ESP32S2ROM(ESP32ROM):
|
||||
strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0
|
||||
and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0
|
||||
):
|
||||
print(
|
||||
"WARNING: {} chip was placed into download mode using GPIO0.\n"
|
||||
"esptool.py can not exit the download mode over USB. "
|
||||
"To run the app, reset the chip manually.\n"
|
||||
"To suppress this note, set --after option to 'no_reset'.".format(
|
||||
self.get_chip_description()
|
||||
)
|
||||
raise SystemExit(
|
||||
f"Error: {self.get_chip_description()} chip was placed into download "
|
||||
"mode using GPIO0.\nesptool.py can not exit the download mode over "
|
||||
"USB. To run the app, reset the chip manually.\n"
|
||||
"To suppress this note, set --after option to 'no_reset'."
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
def hard_reset(self):
|
||||
uses_usb_otg = self.uses_usb_otg()
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import os
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from .esp32 import ESP32ROM
|
||||
from loader import ESPLoader
|
||||
from reset import HardReset
|
||||
from util import FatalError, NotImplementedInROMError
|
||||
from ..loader import ESPLoader
|
||||
from ..reset import HardReset
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32S3ROM(ESP32ROM):
|
||||
@@ -34,6 +35,8 @@ class ESP32S3ROM(ESP32ROM):
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
SPI_ADDR_REG_MSB = False
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0x0
|
||||
|
||||
SUPPORTS_ENCRYPTED_FLASH = True
|
||||
@@ -95,6 +98,7 @@ class ESP32S3ROM(ESP32ROM):
|
||||
|
||||
GPIO_STRAP_REG = 0x60004038
|
||||
GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode
|
||||
GPIO_STRAP_VDDSPI_MASK = 1 << 4
|
||||
RTC_CNTL_OPTION1_REG = 0x6000812C
|
||||
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB?
|
||||
|
||||
@@ -115,8 +119,29 @@ class ESP32S3ROM(ESP32ROM):
|
||||
[0x50000000, 0x50002000, "RTC_DATA"],
|
||||
]
|
||||
|
||||
EFUSE_VDD_SPI_REG = EFUSE_BASE + 0x34
|
||||
VDD_SPI_XPD = 1 << 4
|
||||
VDD_SPI_TIEH = 1 << 5
|
||||
VDD_SPI_FORCE = 1 << 6
|
||||
|
||||
UF2_FAMILY_ID = 0xC47E5767
|
||||
|
||||
EFUSE_MAX_KEY = 5
|
||||
KEY_PURPOSES: Dict[int, str] = {
|
||||
0: "USER/EMPTY",
|
||||
1: "RESERVED",
|
||||
2: "XTS_AES_256_KEY_1",
|
||||
3: "XTS_AES_256_KEY_2",
|
||||
4: "XTS_AES_128_KEY",
|
||||
5: "HMAC_DOWN_ALL",
|
||||
6: "HMAC_DOWN_JTAG",
|
||||
7: "HMAC_DOWN_DIGITAL_SIGNATURE",
|
||||
8: "HMAC_UP",
|
||||
9: "SECURE_BOOT_DIGEST0",
|
||||
10: "SECURE_BOOT_DIGEST1",
|
||||
11: "SECURE_BOOT_DIGEST2",
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07
|
||||
@@ -221,8 +246,10 @@ class ESP32S3ROM(ESP32ROM):
|
||||
return None # doesn't exist on ESP32-S3
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
if key_block < 0 or key_block > self.EFUSE_MAX_KEY:
|
||||
raise FatalError(
|
||||
f"Valid key block numbers must be in range 0-{self.EFUSE_MAX_KEY}"
|
||||
)
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
@@ -236,7 +263,9 @@ class ESP32S3ROM(ESP32ROM):
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see either an AES-128 key or two AES-256 keys
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
purposes = [
|
||||
self.get_key_block_purpose(b) for b in range(self.EFUSE_MAX_KEY + 1)
|
||||
]
|
||||
|
||||
if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes):
|
||||
return True
|
||||
@@ -251,6 +280,9 @@ class ESP32S3ROM(ESP32ROM):
|
||||
& self.EFUSE_SECURE_BOOT_EN_MASK
|
||||
)
|
||||
|
||||
def _get_rtc_cntl_flash_voltage(self):
|
||||
return None # not supported on ESP32-S3
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-S3"
|
||||
@@ -328,21 +360,28 @@ class ESP32S3ROM(ESP32ROM):
|
||||
strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0
|
||||
and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0
|
||||
):
|
||||
print(
|
||||
"WARNING: {} chip was placed into download mode using GPIO0.\n"
|
||||
"esptool.py can not exit the download mode over USB. "
|
||||
"To run the app, reset the chip manually.\n"
|
||||
"To suppress this note, set --after option to 'no_reset'.".format(
|
||||
self.get_chip_description()
|
||||
)
|
||||
raise SystemExit(
|
||||
f"Error: {self.get_chip_description()} chip was placed into download "
|
||||
"mode using GPIO0.\nesptool.py can not exit the download mode over "
|
||||
"USB. To run the app, reset the chip manually.\n"
|
||||
"To suppress this note, set --after option to 'no_reset'."
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
def hard_reset(self):
|
||||
uses_usb_otg = self.uses_usb_otg()
|
||||
if uses_usb_otg:
|
||||
self._check_if_can_reset()
|
||||
|
||||
try:
|
||||
# Clear force download boot mode to avoid the chip being stuck in download mode after reset
|
||||
# workaround for issue: https://github.com/espressif/arduino-esp32/issues/6762
|
||||
self.write_reg(
|
||||
self.RTC_CNTL_OPTION1_REG, 0, self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
||||
)
|
||||
except Exception:
|
||||
# Skip if response was not valid and proceed to reset; e.g. when monitoring while resetting
|
||||
pass
|
||||
|
||||
print("Hard resetting via RTS pin...")
|
||||
HardReset(self._port, uses_usb_otg)()
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from loader import ESPLoader
|
||||
from util import FatalError, NotSupportedError
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError, NotSupportedError
|
||||
|
||||
|
||||
class ESP8266ROM(ESPLoader):
|
||||
@@ -169,12 +169,18 @@ class ESP8266ROM(ESPLoader):
|
||||
else:
|
||||
return (num_sectors - head_sectors) * sector_size
|
||||
|
||||
def get_flash_voltage(self):
|
||||
pass # not supported on ESP8266
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotSupportedError(self, "Overriding VDDSDIO")
|
||||
|
||||
def check_spi_connection(self, spi_connection):
|
||||
raise NotSupportedError(self, "Setting --spi-connection")
|
||||
|
||||
def get_secure_boot_enabled(self):
|
||||
return False # ESP8266 doesn't have security features
|
||||
|
||||
|
||||
class ESP8266StubLoader(ESP8266ROM):
|
||||
"""Access class for ESP8266 stub loader, runs on top of ROM."""
|
||||
|
||||
3
tools/python/esptool/targets/stub_flasher/1/README.md
Normal file
3
tools/python/esptool/targets/stub_flasher/1/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Licensing
|
||||
|
||||
The binaries in JSON format distributed in this directory are released as Free Software under GNU General Public License Version 2 or later. They were released at https://github.com/espressif/esptool-legacy-flasher-stub/releases/tag/v1.3.0 from where the sources can be obtained.
|
||||
8
tools/python/esptool/targets/stub_flasher/1/esp32c3.json
Normal file
8
tools/python/esptool/targets/stub_flasher/1/esp32c3.json
Normal file
File diff suppressed because one or more lines are too long
8
tools/python/esptool/targets/stub_flasher/1/esp32c5.json
Normal file
8
tools/python/esptool/targets/stub_flasher/1/esp32c5.json
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"entry": 1082131910,
|
||||
"text": "ARG3BwBgTsaDqYcASsg3CYRAJspSxAbOIsy3BABgfVoTCQkAwEwTdPQ/DeDyQGJEI6g0AUJJ0kSySSJKBWGCgIhAgycJABN19Q+Cl30U4xlE/8m/EwcADJRBqodjGOUAhUeFxiOgBQB5VYKABUdjh+YACUZjjcYAfVWCgEIFEwewDUGFY5XnAolHnMH1t5MGwA1jFtUAmMETBQAMgoCTBtANfVVjldcAmMETBbANgoC3NYVAQRGThQW6BsZhP2NFBQa3N4VAk4eHsQOnBwgD1kcIE3X1D5MGFgDCBsGCI5LXCDKXIwCnAAPXRwiRZ5OHBwRjHvcCN7eEQBMHh7GhZ7qXA6YHCLf2hEC3N4VAk4eHsZOGhrVjH+YAI6bHCCOg1wgjkgcIIaD5V+MG9fyyQEEBgoAjptcII6DnCN23NzcAYHxLnYv1/zcnAGB8S52L9f+CgEERBsbdN7c3AGAjpgcCNwcACJjDmEN9/8hXskATRfX/BYlBAYKAQREGxtk/fd03BwBAtzcAYJjDNzcAYBxD/f+yQEEBgoBBESLEN4SEQJMHxABKwAOpBwEGxibCYwoJBEU3OcW9RxMExACBRGPWJwEERL2Ik7QUAH03hT8cRDcGgAATl8cAmeA3BgABt/b/AHWPtzYAYNjCkMKYQn3/QUeR4AVHMwnpQLqXIygkARzEskAiRJJEAklBAYKAQREGxhMHAAxjEOUCEwWwDZcAgP/ngIDjEwXADbJAQQEXA4D/ZwCD4hMHsA3jGOX+lwCA/+eAgOETBdANxbdBESLEJsIGxiqEswS1AGMXlACyQCJEkkRBAYKAA0UEAAUERTfttxMFAAwXA4D/ZwAD3jVxJstOx/1yhWn9dCLNSslSxVbDBs+ThIT6FpGThwkHppcYCLOE5wAqiSaFLoSXAID/54DgSJOHCQcYCAVqupezikdBMeQFZ311kwWF+pMHBwcTBYX5FAiqlzOF1wCTBwcHrpezhdcAKsaXAID/54CgRTJFwUWhPwFFhWIWkfpAakTaREpJukkqSppKDWGCgKKJY3OKAIVpTobWhUqFlwCA/+eA4OITdfUPAe1OhtaFJoWXAID/54DgQE6ZMwQ0QVG3EwUwBlW/MXH9ck7XUtVW017PBt8i3SbbStla0WLNZstqyW7HqokWkRMFAAIuirKKtosCypcAgP/ngKA7hWdj4FcThWR9dBMEhPqThwQHopcYCDOE5wAihZcAgP/ngCA6fXsTDDv5kwyL+ROHBAeThwQHFAhil+aXAUkzDNcAs4zXAFJNY3xNCWNxqQNBqFU1poUIAaU9cT0mhgwBIoWXAID/54AANqaZJpljdUkDswepQWPxdwOzBCpBY/OaANaEJoYMAU6FlwCA/+eAQNQTdfUPVd0CzIFEeV2NTaMJAQBihZcAgP/ngMDDffkDRTEB5oUFMWNPBQDj4p3+hWeThwcHppcYCLqX2pcjiqf4hQTxt+MVpf2RR+OF9PYFZ311kwcHB5MFhfoTBYX5FAiqlzOF1wCTBwcHrpezhdcAKsaXAID/54AgLO0zMkXBRX07zTMTBQAClwCA/+eAwCmFYhaR+lBqVNpUSlm6WSpamloKW/pLakzaTEpNuk0pYYKAAREGziLMnTk3BM4/bAATBUT/lwCA/+eAwMqqhwVFleeyR5P3ByA+xkE5NzcAYBxHtwZAABMFRP/VjxzHskWXAID/54BAyDM1oADyQGJEBWGCgEERt4eEQAbGk4fHAAVHI4DnABPXxQCYxwVnfRfMw8jH+Y06laqVsYGMyyOqBwBBNxnBEwVQDLJAQQGCgAERIsw3hIRAkwfEACbKxEdOxgbOSsiqiRMExABj85UAroSpwAMpRAAmmRNZyQAcSGNV8AAcRGNe+QLpNn3dSEAmhs6FlwCA/+eAQLsTdfUPAcWTB0AMXMhcQKaXXMBcRIWPXMTyQGJE0kRCSbJJBWGCgOE+bb+3V0FJGXGTh/eEAUU+zobeotym2srYztbS1NbS2tDezuLM5srqyO7GlwCA/+eAoK23B4RANzeFQJOHBwATB4e6Y+DnFK0xkUVoCD05jTG3t4RAk4eHsSFnPpcjIPcItwWAQLcHgEABRpOHBwuThQUANwmEQBVFIyD5AJcAgP/ngMAONwcAYFxHEwUAAreEhECT5xcQXMeXAID/54CADbcXCWCIX4FFtzmFQHGJYRUTNRUAlwCA/+eAQLbBZ/0XEwcAEIVmQWa3BQABAUWThMQAtwqEQA1qlwCA/+eAAKyTiYmxEwkJABOLygAmmoOnyQj134OryQiFRyOmCQgjAvECg8cbAAlHIxPhAqMC8QIC1E1HY4vnBlFHY4nnBilHY5/nAIPHOwADxysAogfZjxFHY5bnAIOniwCcQz7UjT6hRUgQmTaDxzsAA8crAKIH2Y8RZ0EHY373AhMFsA2XAID/54BgkxMFwA2XAID/54CgkhMF4A6XAID/54DgkQ0+vbcjoAcAkQdtvclHIxPxAn23A8cbANFGY+fmAoVGY+bmAAFMEwTwD52oeRcTd/cPyUbj6Ob+tzaFQAoHk4bGujaXGEMCh5MGBwOT9vYPEUbjadb8Ewf3AhN39w+NRmPu5gi3NoVACgeThoa/NpcYQwKHEwdAAmOa5xAC1B1EAUWXAID/54BAiQFFiTRVNE00oUVIEH0UlTx98AFMAUQTdfQPLTQTdfwPFTRZNOMRBOyDxxsASUdjZfcwCUfjeffq9ReT9/cPPUfjY/fqNzeFQIoHEweHwLqXnEOChwVEnetwEIFFAUWXAID/54BgiR3h0UVoEBk8AUQxqAVEge+XAID/54DgjTM0oAApoCFHY4XnAAVEAUxhtwOsiwADpMsAs2eMANIH9feZOWX1wWwinP0cfX0zBYxAXdyzd5UBlePBbDMFjEBj5owC/XwzBYxAXdAxgZcAgP/ngICKXflmlPW3MYGXAID/54CAiV3xapTRt0GBlwCA/+eAwIhZ+TMElEHBtyFH44rn8AFMEwQADDm3QUfNv0FHBUTjnef2g6XLAAOliwBZOrm/QUcFROOT5/YDpwsBkWdj6Oceg6VLAQOliwAxMYG3QUcFROOU5/SDpwsBEWdjafccA6fLAIOlSwEDpYsAM4TnAt02I6wEACMkirAJvwPHBABjAwcUA6eLAMEXEwQADGMT9wDASAFHkwbwDmNG9wKDx1sAA8dLAAFMogfZjwPHawBCB12Pg8d7AOIH2Y/jhPbmEwQQDIW1M4brAANGhgEFB7GO4beDxwQA/cfcRGOdBxTASCOABABVvWFHY5bnAoOnywEDp4sBg6ZLAQOmCwGDpcsAA6WLAJfwf//ngIB5KowzNKAAAb0BTAVEKbURRwVE453n5reXAGC0X2V3fRcFZvmO0Y4DpYsAtN+0V4FF+Y7RjrTX9F/5jtGO9N/0U3WPUY/405fwf//ngKB8BbUT9/cA4xcH6pPcRwAThIsAAUx9XeN3nNtIRJfwf//ngGBgGERUQBBA+Y5jB6cBHEITR/f/fY/ZjhTCBQxBBNm/EUe1tUFHBUTjmufeg6eLAAOnSwEjJPkAIyLpAMmzgyVJAMEXkeWJzwFMEwRgDKG7AyeJAGNm9wYT9zcA4xsH4gMoiQABRgFHMwXoQLOG5QBjafcA4wcG0iMkqQAjItkADbMzhusAEE4RB5DCBUbpvyFHBUTjlOfYAySJABnAEwSADCMkCQAjIgkAMzSAAL2zAUwTBCAMxbkBTBMEgAzlsQFMEwSQDMWxEwcgDWOD5wwTB0AN45HnugPEOwCDxysAIgRdjJfwf//ngIBfA6zEAEEUY3OEASKM4w8MtsBAYpQxgJxIY1XwAJxEY1r0Cu/wr+B13chAYoaThYsBl/B//+eAgFsBxZMHQAzcyNxA4pfcwNxEs4eHQdzEl/B//+eAYFoVvgllEwUFcQOsywADpIsAl/B//+eA4Eq3BwBg2Eu3BgABwRaTV0cBEgd1j72L2Y+zh4cDAUWz1YcCl/B//+eAQEwTBYA+l/B//+eAgEfdtIOmSwEDpgsBg6XLAAOliwDv8K/2wbyDxTsAg8crABOFiwGiBd2NwRWpOm287/AP2oG3A8Q7AIPHKwATjIsBIgRdjNxEQRTF45FHhUtj/ocIkweQDNzIebQDpw0AItAFSLOH7EA+1oMnirBjc/QADUhCxjrE7/CP1SJHMkg3hYRA4oV8EJOGygAQEBMFRQKX8H//54AASje3hECTCMcAglcDp4iwg6UNAB2MHY8+nLJXI6TosKqLvpUjoL0Ak4fKAJ2NAcWhZ2OW9QBahV04I6BtAQnE3ESZw+NAcPlj3wsAkwdwDIW/hUu3PYVAt4yEQJONjbqTjMwA6b/jlQue3ETjggeekweADLG3g6eLAOObB5wBRZfwf//ngCA5CWUTBQVxl/B//+eAwDSX8H//54DAOU26A6TLAOMGBJoBRZfwf//ngIA2EwWAPpfwf//ngEAyApRBuvZQZlTWVEZZtlkmWpZaBlv2S2ZM1kxGTbZNCWGCgAAA",
|
||||
"text_start": 1082130432,
|
||||
"data": "DACEQO4IgEA6CYBAkgmAQGAKgEDMCoBAegqAQLYHgEAcCoBAXAqAQKYJgEBmB4BA2gmAQGYHgEDICIBADAmAQDoJgECSCYBA2giAQCAIgEBQCIBA1giAQCQNgEA6CYBA5AuAQNgMgECyBoBAAg2AQLIGgECyBoBAsgaAQLIGgECyBoBAsgaAQLIGgECyBoBAgAuAQLIGgEAADIBA2AyAQA==",
|
||||
"data_start": 1082469288,
|
||||
"bss_start": 1082392576
|
||||
}
|
||||
8
tools/python/esptool/targets/stub_flasher/1/esp32c6.json
Normal file
8
tools/python/esptool/targets/stub_flasher/1/esp32c6.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
tools/python/esptool/targets/stub_flasher/1/esp32h2.json
Normal file
8
tools/python/esptool/targets/stub_flasher/1/esp32h2.json
Normal file
File diff suppressed because one or more lines are too long
8
tools/python/esptool/targets/stub_flasher/1/esp32p4.json
Normal file
8
tools/python/esptool/targets/stub_flasher/1/esp32p4.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"entry": 1341195918,
|
||||
"text": "QREixCbCBsa3Jw1QEUc3BPVP2Mu3JA1QEwQEANxAkYuR57JAIkSSREEBgoCIQBxAE3X1D4KX3bcBEbenDFBOxoOphwBKyDcJ9U8mylLEBs4izLekDFB9WhMJCQDATBN09D8N4PJAYkQjqDQBQknSRLJJIkoFYYKAiECDJwkAE3X1D4KXfRTjGUT/yb8TBwAMlEGqh2MY5QCFR4XGI6AFAHlVgoAFR2OH5gAJRmONxgB9VYKAQgUTB7ANQYVjlecCiUecwfW3kwbADWMW1QCYwRMFAAyCgJMG0A19VWOV1wCYwRMFsA2CgLc19k9BEZOFRboGxmE/Y0UFBrc39k+Th8exA6cHCAPWRwgTdfUPkwYWAMIGwYIjktcIMpcjAKcAA9dHCJFnk4cHBGMe9wI3t/VPEwfHsaFnupcDpgcIt/b1T7c39k+Th8exk4bGtWMf5gAjpscII6DXCCOSBwghoPlX4wb1/LJAQQGCgCOm1wgjoOcI3bc31whQfEudi/X/N8cIUHxLnYv1/4KAQREGxt03t9cIUCOmBwI3BwAImMOYQ33/yFeyQBNF9f8FiUEBgoBBEQbG2T993TcHAEC31whQmMM31whQHEP9/7JAQQGCgEERIsQ3hPVPkwcEAUrAA6kHAQbGJsJjCgkERTc5xb1HEwQEAYFEY9YnAQREvYiTtBQAfTeFPxxENwaAABOXxwCZ4DcGAAG39v8AdY+31ghQ2MKQwphCff9BR5HgBUczCelAupcjKCQBHMSyQCJEkkQCSUEBgoABEQbOIswlNzcE9E9sABMFxP6XAM//54Ag86qHBUWV57JHk/cHID7GiTc31whQHEe3BkAAEwXE/tWPHMeyRZcAz//ngKDwMzWgAPJAYkQFYYKAQRG3h/VPBsaThwcBBUcjgOcAE9fFAJjHBWd9F8zDyMf5jTqVqpWxgYzLI6oHAEE3GcETBVAMskBBAYKAAREizDeE9U+TBwQBJsrER07GBs5KyKqJEwQEAWPzlQCuhKnAAylEACaZE1nJABxIY1XwABxEY175ArU9fd1IQCaGzoWXAM//54Cg4xN19Q8BxZMHQAxcyFxAppdcwFxEhY9cxPJAYkTSREJJskkFYYKAaTVtv0ERBsaXAM//54BA1gNFhQGyQGkVEzUVAEEBgoBBEQbGxTcRwRlFskBBARcDz/9nAOPPQREGxibCIsSqhJcAz//ngADNdT8NyTcH9U+TBgcAg9dGABMEBwCFB8IHwYMjkvYAkwYADGOG1AATB+ADY3X3AG03IxIEALJAIkSSREEBgoBBEQbGEwcADGMa5QATBbANRTcTBcANskBBAVm/EwewDeMb5f5xNxMF0A31t0ERIsQmwgbGKoSzBLUAYxeUALJAIkSSREEBgoADRQQABQRNP+23NXEmy07H/XKFaf10Is1KyVLFVsMGz5OEhPoWkZOHCQemlxgIs4TnACqJJoUuhJcAz//ngOAZk4cJBxgIBWq6l7OKR0Ex5AVnfXWTBYX6kwcHBxMFhfkUCKqXM4XXAJMHBweul7OF1wAqxpcAz//ngKAWMkXBRZU3AUWFYhaR+kBqRNpESkm6SSpKmkoNYYKAooljc4oAhWlOhtaFSoWXAM//54CgyRN19Q8B7U6G1oUmhZcAz//ngOARTpkzBDRBUbcTBTAGVb8TBQAMSb0xcf1yBWdO11LVVtNezwbfIt0m20rZWtFizWbLaslux/13FpETBwcHPpccCLqXPsYjqgf4qokuirKKtosNNZMHAAIZwbcHAgA+hZcAz//ngIAKhWdj5VcTBWR9eRMJifqTBwQHypcYCDOJ5wBKhZcAz//ngAAJfXsTDDv5kwyL+RMHBAeTBwQHFAhil+aXgUQzDNcAs4zXAFJNY3xNCWPxpANBqJk/ooUIAY01uTcihgwBSoWXAM//54DgBKKZopRj9UQDs4ekQWPxdwMzBJpAY/OKAFaEIoYMAU6FlwDP/+eA4LgTdfUPVd0CzAFEeV2NTaMJAQBihZcAz//ngKCnffkDRTEB5oVZPGNPBQDj4o3+hWeThwcHopcYCLqX2pcjiqf4BQTxt+MVpf2RR+MF9PYFZ311kwcHB5MFhfoTBYX5FAiqlzOF1wCTBwcHrpezhdcAKsaXAM//54AA+3E9MkXBRWUzUT3dObcHAgAZ4ZMHAAI+hZcAz//ngAD4hWIWkfpQalTaVEpZulkqWppaClv6S2pM2kxKTbpNKWGCgLdXQUkZcZOH94QBRYbeotym2srYztbS1NbS2tDezuLM5srqyO7GPs6XAM//54DgoHkxBcU3R9hQt2cRUBMHF6qYzyOgBwAjrAcAmNPYT7cGBABVj9jPI6AHArcH9U83N/ZPk4cHABMHx7ohoCOgBwCRB+Pt5/7VM5FFaAjFOfE7t7f1T5OHx7EhZz6XIyD3CLcH8U83CfVPk4eHDiMg+QC3OfZPKTmTicmxEwkJAGMFBRC3Zw1QEwcQArjPhUVFRZcAz//ngKDmtwXxTwFGk4UFAEVFlwDP/+eAoOe3Jw1QEUeYyzcFAgCXAM//54Dg5rcHDlCIX4FFt4T1T3GJYRUTNRUAlwDP/+eAYKXBZ/0XEwcAEIVmQWa3BQABAUWThAQBtwr1Tw1qlwDP/+eAIJsTiwoBJpqDp8kI9d+Dq8kIhUcjpgkIIwLxAoPHGwAJRyMT4QKjAvECAtRNR2OB5whRR2OP5wYpR2Of5wCDxzsAA8crAKIH2Y8RR2OW5wCDp4sAnEM+1NE5oUVIEMU2g8c7AAPHKwCiB9mPEWdBB2N09wQTBbANqTYTBcANkTYTBeAOPT5dMUG3twXxTwFGk4WFAxVFlwDP/+eAoNg3pwxQXEcTBQACk+cXEFzHMbfJRyMT8QJNtwPHGwDRRmPn5gKFRmPm5gABTBME8A+FqHkXE3f3D8lG4+jm/rc29k8KB5OGBrs2lxhDAoeTBgcDk/b2DxFG42nW/BMH9wITd/cPjUZj6+YItzb2TwoHk4bGvzaXGEMChxMHQAJjl+cQAtQdRAFFcTwBReU0ATH9PqFFSBB9FCE2dfQBTAFEE3X0D8E8E3X8D+k0zTbjHgTqg8cbAElHY2v3MAlH43b36vUXk/f3Dz1H42D36jc39k+KBxMHx8C6l5xDgocFRJ3rcBCBRQFFl/DO/+eAoHcd4dFFaBBtNAFEMagFRIHvl/DO/+eAIH0zNKAAKaAhR2OF5wAFRAFMYbcDrIsAA6TLALNnjADSB/X30TBl9cFsIpz9HH19MwWMQF3cs3eVAZXjwWwzBYxAY+aMAv18MwWMQF3QMYGX8M7/54DAeV35ZpT1tzGBl/DO/+eAwHhd8WqU0bdBgZfwzv/ngAB4WfkzBJRBwbchR+OK5/ABTBMEAAw5t0FHzb9BRwVE453n9oOlywADpYsAOTy5v0FHBUTjk+f2A6cLAZFnY+7nHoOlSwEDpYsA7/C/hz2/QUcFROOT5/SDpwsBEWdjbvccA6fLAIOlSwEDpYsAM4TnAu/wP4UjrAQAIySKsDm3A8cEAGMHBxQDp4sAwRcTBAAMYxP3AMBIAUeTBvAOY0b3AoPHWwADx0sAAUyiB9mPA8drAEIHXY+Dx3sA4gfZj+OC9uYTBBAMsb0zhusAA0aGAQUHsY7ht4PHBAD9y9xEY5EHFsBII4AEAEW9YUdjlucCg6fLAQOniwGDpksBA6YLAYOlywADpYsAl/DO/+eAgGgqjDM0oAAxtQFMBUQZtRFHBUTjm+fmtxcOUPRfZXd9FwVm+Y7RjgOliwCThQcI9N+UQfmO0Y6UwZOFRwiUQfmO0Y6UwbRfgUV1j1GPuN+X8M7/54AgaxG9E/f3AOMRB+qT3EcAE4SLAAFMfV3jcZzbSESX8M7/54AgThhEVEAQQPmOYwenARxCE0f3/32P2Y4UwgUMQQTZvxFHhbVBRwVE45Tn3oOniwADp0sBIyb5ACMk6QBdu4MliQDBF5Hlic8BTBMEYAyxswMnyQBjZvcGE/c3AOMVB+IDKMkAAUYBRzMF6ECzhuUAY2n3AOMBBtIjJqkAIyTZABm7M4brABBOEQeQwgVG6b8hRwVE457n1gMkyQAZwBMEgAwjJgkAIyQJADM0gACNswFMEwQgDNWxAUwTBIAM8bkBTBMEkAzRuRMHIA1jg+cMEwdADeOY57gDxDsAg8crACIEXYyX8M7/54AATgOsxABBFGNzhAEijOMGDLbAQGKUMYCcSGNV8ACcRGNb9Arv8O/Rdd3IQGKGk4WLAZfwzv/ngABKAcWTB0AM3MjcQOKX3MDcRLOHh0HcxJfwzv/ngOBIDbYJZRMFBXEDrMsAA6SLAJfwzv/ngKA4t6cMUNhLtwYAAcEWk1dHARIHdY+9i9mPs4eHAwFFs9WHApfwzv/ngAA6EwWAPpfwzv/ngEA10byDpksBA6YLAYOlywADpYsA7/DP/n28g8U7AIPHKwAThYsBogXdjcEV7/DP21207/Avyz2/A8Q7AIPHKwATjIsBIgRdjNxEQRTN45FHhUtj/4cIkweQDNzIrbwDpw0AItAFSLOH7EA+1oMnirBjc/QADUhCxjrE7/CvxiJHMkg3hfVP4oV8EJOGCgEQEBMFhQKX8M7/54BgNze39U+TCAcBglcDp4iwg6UNAB2MHY8+nLJXI6TosKqLvpUjoL0Ak4cKAZ2NAcWhZ2OX9QBahe/wb9EjoG0BCcTcRJnD409w92PfCwCTB3AMvbeFS7c99k+3jPVPk43NupOMDAHpv+OaC5zcROOHB5yTB4AMqbeDp4sA45AHnO/wD9YJZRMFBXGX8M7/54CgIpfwzv/ngKAnTbIDpMsA4w4EmO/wz9MTBYA+l/DO/+eAgCAClFmy9lBmVNZURlm2WSZalloGW/ZLZkzWTEZNtk0JYYKAAAA=",
|
||||
"text_start": 1341194240,
|
||||
"data": "EAD1TwYK8U9WCvFPrgrxT4QL8U/wC/FPngvxT9QI8U9AC/FPgAvxT8IK8U+ECPFP9grxT4QI8U/gCfFPJgrxT1YK8U+uCvFP8gnxTzgJ8U9oCfFP7gnxT0AO8U9WCvFPCA3xTwAO8U/EB/FPJA7xT8QH8U/EB/FPxAfxT8QH8U/EB/FPxAfxT8QH8U/EB/FPpAzxT8QH8U8mDfFPAA7xTw==",
|
||||
"data_start": 1341533100,
|
||||
"bss_start": 1341456384
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
201
tools/python/esptool/targets/stub_flasher/2/LICENSE-APACHE
Normal file
201
tools/python/esptool/targets/stub_flasher/2/LICENSE-APACHE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
25
tools/python/esptool/targets/stub_flasher/2/LICENSE-MIT
Normal file
25
tools/python/esptool/targets/stub_flasher/2/LICENSE-MIT
Normal file
@@ -0,0 +1,25 @@
|
||||
Copyright 2022 esp-rs
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
3
tools/python/esptool/targets/stub_flasher/2/README.md
Normal file
3
tools/python/esptool/targets/stub_flasher/2/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Licensing
|
||||
|
||||
The binaries in JSON format distributed in this directory are dual licensed under the Apache License Version 2.0 or the MIT license. They were released at https://github.com/esp-rs/esp-flasher-stub/releases/tag/v0.3.0 from where the sources can be obtained.
|
||||
1
tools/python/esptool/targets/stub_flasher/2/esp32.json
Normal file
1
tools/python/esptool/targets/stub_flasher/2/esp32.json
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user