mirror of
https://github.com/wagiminator/ATtiny814-USB-PD-Adapter.git
synced 2025-08-09 12:59:09 +03:00
Initial commit
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
"""Base class for all HID transport mechanisms."""
|
||||
|
||||
from logging import getLogger
|
||||
from . import toolinfo
|
||||
|
||||
|
||||
class HidTool(object):
|
||||
"""
|
||||
Holds transport and DAP properties of a CMSIS-DAP debugger.
|
||||
|
||||
Used to select the debugger to use if multiple debuggers are connected.
|
||||
"""
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, too-many-arguments
|
||||
# These are primary keys used to identify the debugger.
|
||||
|
||||
def __init__(self, vendor_id, product_id, serial_number, product_string="", manufacturer_string=""):
|
||||
self.logger = getLogger(__name__)
|
||||
self.interface_number = -1
|
||||
self.vendor_id = vendor_id
|
||||
self.product_id = product_id
|
||||
self.serial_number = serial_number
|
||||
self.product_string = product_string
|
||||
self.manufacturer_string = manufacturer_string
|
||||
self.firmware_version = ""
|
||||
self.device_vendor_id = ""
|
||||
self.device_name = ""
|
||||
self.packet_size = 64
|
||||
|
||||
def set_packet_size(self, packet_size):
|
||||
"""
|
||||
Sets the packet size
|
||||
|
||||
:param packet_size: bytes per packet
|
||||
"""
|
||||
self.packet_size = packet_size
|
||||
|
||||
def set_product_string(self, product_string):
|
||||
"""
|
||||
Sets the product string
|
||||
|
||||
:param product_string: product name string
|
||||
"""
|
||||
self.product_string = product_string
|
||||
|
||||
|
||||
class HidTransportBase(object):
|
||||
"""Base class for HID transports"""
|
||||
|
||||
def __init__(self):
|
||||
self.logger = getLogger(__name__)
|
||||
self.devices = []
|
||||
self.device = None
|
||||
self.detect_devices()
|
||||
self.connected = False
|
||||
|
||||
def __del__(self):
|
||||
# Make sure we always disconnect the HID connection
|
||||
self.disconnect()
|
||||
|
||||
def detect_devices(self):
|
||||
"""Raise error as this method needs to be overridden."""
|
||||
raise NotImplementedError("method needs to be defined by sub-class")
|
||||
|
||||
def get_matching_tools(self, serial_number_substring='', product=None):
|
||||
"""
|
||||
Returns a list of tools matching the given serial_number_substring and product.
|
||||
|
||||
:param serial_number_substring: can be an empty string or a subset of a serial number. Not case sensitive
|
||||
This function will do matching of the last part of the devices serial numbers to
|
||||
the serial_number_substring. Examples:
|
||||
'123' will match "MCHP3252000000043123" but not "MCP32520001230000000"
|
||||
'' will match any serial number
|
||||
:param product: product type to connect to. If None any tool matching the serial_number_substring
|
||||
will be returned
|
||||
:return: List of matching tools
|
||||
"""
|
||||
# Support systems which use None as the standard for a unspecified USB serial
|
||||
if serial_number_substring is None:
|
||||
serial_number_substring = ""
|
||||
|
||||
# Making serial_number_substring case insensitive
|
||||
serial_number_substring = serial_number_substring.lower()
|
||||
|
||||
# Support tool shortnames
|
||||
toolname_in_product_string = toolinfo.tool_shortname_to_product_string_name(product)
|
||||
if toolname_in_product_string is not None:
|
||||
# Making product name case insensitive
|
||||
toolname_in_product_string = toolname_in_product_string.lower()
|
||||
|
||||
matching_devices = []
|
||||
for device in self.devices:
|
||||
if toolname_in_product_string is None or device.product_string.lower().startswith(
|
||||
toolname_in_product_string):
|
||||
if device.serial_number.lower().endswith(serial_number_substring):
|
||||
matching_devices.append(device)
|
||||
|
||||
return matching_devices
|
||||
|
||||
def connect(self, serial_number=None, product=None):
|
||||
"""
|
||||
Makes a HID connection to a debugger
|
||||
|
||||
:param serial_number: instance serial number to connect to
|
||||
:param product: product type to connect to
|
||||
:return: True if successfully connected to a tool, False if not
|
||||
"""
|
||||
if self.connected:
|
||||
return True
|
||||
|
||||
device_count = len(self.devices)
|
||||
self.logger.debug("{:d} devices available".format(device_count))
|
||||
if device_count == 0:
|
||||
self.logger.error("No CMSIS-DAP devices found.")
|
||||
return False
|
||||
|
||||
matching_devices = self.get_matching_tools(serial_number_substring=serial_number, product=product)
|
||||
number_of_matching_devices = len(matching_devices)
|
||||
|
||||
# Did we find exactly 1 tool?
|
||||
if number_of_matching_devices != 1:
|
||||
log_str = "Found {:d} daps matching the filter serial = \"{}\" and product = \"{}\""
|
||||
self.logger.debug(log_str.format(number_of_matching_devices, serial_number, product))
|
||||
if number_of_matching_devices > 1:
|
||||
self.logger.error("Too many products found. Please specify one of:")
|
||||
for device in self.devices:
|
||||
self.logger.error(" > {:s} {:s}".format(device.product_string,
|
||||
device.serial_number))
|
||||
return False
|
||||
|
||||
# Everything is peachy, connect to the tool
|
||||
self.device = matching_devices[0]
|
||||
self.hid_connect(self.device)
|
||||
self.logger.debug("Connected OK")
|
||||
self.connected = True
|
||||
packet_size = toolinfo.get_default_report_size(self.device.product_id)
|
||||
self.device.set_packet_size(packet_size)
|
||||
self.hid_info()
|
||||
return True
|
||||
|
||||
def disconnect(self):
|
||||
"""Release the HID connection"""
|
||||
if self.connected:
|
||||
self.hid_disconnect()
|
||||
self.connected = False
|
||||
|
||||
def hid_connect(self, device):
|
||||
"""Raise error as this method needs to be overridden."""
|
||||
raise NotImplementedError("method needs to be defined by sub-class")
|
||||
|
||||
def hid_info(self):
|
||||
"""Raise error as this method needs to be overridden."""
|
||||
raise NotImplementedError("method needs to be defined by sub-class")
|
||||
|
||||
def hid_disconnect(self):
|
||||
"""Raise error as this method needs to be overridden."""
|
||||
raise NotImplementedError("method needs to be defined by sub-class")
|
||||
|
||||
def get_report_size(self):
|
||||
"""
|
||||
Get the packet size in bytes
|
||||
|
||||
:return: bytes per packet/report
|
||||
"""
|
||||
return self.device.packet_size
|
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Factory for HID transport connections.
|
||||
|
||||
Currently supports only Cython/HIDAPI
|
||||
"""
|
||||
|
||||
import platform
|
||||
from logging import getLogger
|
||||
from ..pyedbglib_errors import PyedbglibNotSupportedError
|
||||
|
||||
|
||||
def hid_transport(library="hidapi"):
|
||||
"""
|
||||
Dispatch a transport layer for the OS in question
|
||||
|
||||
The transport layer is typically used to connect to a tool and then it is passed in as a parameter when creating
|
||||
protocol objects. An example where the transport layer is used to create an instance of the housekeepingprotocol
|
||||
for communication with the nEDBG debugger::
|
||||
|
||||
from pyedbglib.hidtransport.hidtransportfactory import hid_transport
|
||||
transport = hid_transport()
|
||||
connect_status = False
|
||||
try:
|
||||
connect_status = transport.connect(serial_number='', product='nedbg')
|
||||
except IOError as error:
|
||||
print("Unable to connect to USB device ({})".format(error))
|
||||
|
||||
if not connect_status:
|
||||
print("Unable to connect to USB device")
|
||||
|
||||
housekeeper = housekeepingprotocol.Jtagice3HousekeepingProtocol(transport)
|
||||
|
||||
:param library: Transport library to use, currently only 'hidapi' is supported which will use the libusb hidapi
|
||||
:type library: string
|
||||
:returns: Instance of transport layer object
|
||||
:rtype: class:cyhidapi:CyHidApiTransport
|
||||
"""
|
||||
logger = getLogger(__name__)
|
||||
operating_system = platform.system().lower()
|
||||
logger.debug("HID transport using library '{:s}' on OS '{:s}'".format(library, operating_system))
|
||||
|
||||
# HID API is the primary transport
|
||||
if library == 'hidapi':
|
||||
hid_api_supported_os = ['windows', 'darwin', 'linux', 'linux2']
|
||||
if operating_system in hid_api_supported_os:
|
||||
from .cyhidapi import CyHidApiTransport
|
||||
return CyHidApiTransport()
|
||||
|
||||
msg = "System '{0:s}' not implemented for library '{1:s}'".format(operating_system, library)
|
||||
logger.error(msg)
|
||||
raise PyedbglibNotSupportedError(msg)
|
||||
|
||||
# Other transports may include cmsis-dap DLL, atusbhid (dll or so) etc
|
||||
msg = "Transport library '{0}' not implemented.".format(library)
|
||||
logger.error(msg)
|
||||
raise PyedbglibNotSupportedError(msg)
|
@@ -0,0 +1,94 @@
|
||||
"""Gathering of all known Microchip CMSIS-DAP debuggers and default EP sizes"""
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
# List of known useful HID/CMSIS-DAP tools
|
||||
# 3G tools:
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_JTAGICE3 = 0x2140
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_ATMELICE = 0x2141
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_POWERDEBUGGER = 0x2144
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_EDBG_A = 0x2111
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_ZERO = 0x2157
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_MASS_STORAGE = 0x2169
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_PUBLIC_EDBG_C = 0x216A
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_KRAKEN = 0x2170
|
||||
|
||||
# 4G tools:
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_MEDBG = 0x2145
|
||||
|
||||
# 5G tools:
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_NEDBG_HID_MSD_DGI_CDC = 0x2175
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_PICKIT4_HID_CDC = 0x2177
|
||||
USB_TOOL_DEVICE_PRODUCT_ID_SNAP_HID_CDC = 0x2180
|
||||
|
||||
# The Product String Names are used to identify the tool based on the USB
|
||||
# device product strings (i.e. these names are usually just a subset of the
|
||||
# actual product strings)
|
||||
TOOL_SHORTNAME_TO_USB_PRODUCT_STRING = {
|
||||
'atmelice': "Atmel-ICE",
|
||||
'powerdebugger': "Power Debugger",
|
||||
'pickit4': "MPLAB PICkit 4",
|
||||
'snap': "MPLAB Snap",
|
||||
'nedbg': "nEDBG",
|
||||
'jtagice3': "JTAGICE3",
|
||||
'medbg': "mEDBG",
|
||||
'edbg': "EDBG",
|
||||
}
|
||||
|
||||
def get_default_report_size(pid):
|
||||
"""
|
||||
Retrieve default EP report size based on known PIDs
|
||||
|
||||
:param pid: product ID
|
||||
:return: packet size
|
||||
"""
|
||||
logger = getLogger(__name__)
|
||||
hid_tools = [
|
||||
# 3G
|
||||
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_JTAGICE3, 'default_report_size': 512},
|
||||
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_ATMELICE, 'default_report_size': 512},
|
||||
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_POWERDEBUGGER, 'default_report_size': 512},
|
||||
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_EDBG_A, 'default_report_size': 512},
|
||||
# 4G
|
||||
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_MEDBG, 'default_report_size': 64},
|
||||
# 5G
|
||||
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_NEDBG_HID_MSD_DGI_CDC, 'default_report_size': 64},
|
||||
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_PICKIT4_HID_CDC, 'default_report_size': 64},
|
||||
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_SNAP_HID_CDC, 'default_report_size': 64}]
|
||||
|
||||
logger.debug("Looking up report size for pid 0x{:04X}".format(pid))
|
||||
for tool in hid_tools:
|
||||
if tool['pid'] == pid:
|
||||
logger.debug("Default report size is {:d}".format(tool['default_report_size']))
|
||||
return tool['default_report_size']
|
||||
logger.debug("PID not found! Reverting to 64b.")
|
||||
return 64
|
||||
|
||||
def tool_shortname_to_product_string_name(shortname):
|
||||
"""
|
||||
Mapping for common short names of tools to product string name
|
||||
|
||||
The intention is that this function is always run on the tool name and that the conversion
|
||||
only happens if the name is a known shortname. If the shortname is not known of if the name
|
||||
provided is already a valid Product string name then the provided shortname parameter will
|
||||
just be returned unchanged. So if the name already is a correct Product string name it is
|
||||
still safe to run this conversion funtion on it.
|
||||
|
||||
:param shortname: shortname typically used by atbackend (powerdebugger, atmelice etc.)
|
||||
:return: String to look for in USB product strings to identify the tool
|
||||
"""
|
||||
logger = getLogger(__name__)
|
||||
|
||||
if shortname is None:
|
||||
logger.debug("Tool shortname is None")
|
||||
# This is also valid as the user might have provided no tool name, but the conversion function
|
||||
# should still be valid
|
||||
return shortname
|
||||
|
||||
shortname_lower = shortname.lower()
|
||||
if shortname_lower not in TOOL_SHORTNAME_TO_USB_PRODUCT_STRING:
|
||||
logger.debug("%s is not a known tool shortname", shortname)
|
||||
# ...but it could be a valid Product string name already so no reason to report an error
|
||||
return shortname
|
||||
|
||||
return TOOL_SHORTNAME_TO_USB_PRODUCT_STRING[shortname_lower]
|
Reference in New Issue
Block a user