From 659b4608894fa771f43edd74c1daaa07e60dd19d Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 14 May 2020 21:43:48 +0100 Subject: [PATCH 01/32] stash poc --- lib/python/qmk/cli/__init__.py | 1 + lib/python/qmk/cli/console.py | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 lib/python/qmk/cli/console.py diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index f7df90811984..cfb6e6ea59f6 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -12,6 +12,7 @@ from . import clean from . import compile from . import config +from . import console from . import docs from . import doctor from . import fileformat diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py new file mode 100644 index 000000000000..26c7a6fca891 --- /dev/null +++ b/lib/python/qmk/cli/console.py @@ -0,0 +1,56 @@ +"""hid_listen +""" +import hid +import platform +from pathlib import Path +from time import sleep + +from milc import cli + +def patch_linux(dev): + platform_id = platform.platform().lower() + if 'linux' in platform_id: + hidraw = Path(dev['path'].decode('UTF-8')).name + descriptor_path = Path('/sys/class/hidraw/') / hidraw / 'device/report_descriptor' + + report = descriptor_path.read_bytes() + + dev['usage_page'] = (report[2] << 8) + report[1]; + dev['usage'] = report[4]; + return dev + +def is_console_hid(x): + return x['usage_page'] == 0xFF31 and x['usage'] == 0x0074 + +def search(): + return list(filter(is_console_hid, map(patch_linux, hid.enumerate()))) + +@cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') +@cli.argument('-i', '--index', default=0, type=int, help='Device index.') +@cli.subcommand('kinda hid_listen ish.') +def console(cli): + """TODO. + """ + + if cli.args.list: + cli.log.info('Available devices:') + devices = search() + for dev in devices: + cli.log.info("%02x:%02x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) + return + + print('Waiting for device:') + + selected = None + while selected is None: + found = search() + selected = found[cli.args.index] if found[cli.args.index:] else None + + print('.', end = '', flush=True) + sleep(1) + + print() + print('Listening to %s:' % selected['path'].decode()) + device = hid.Device(path=selected['path']) + while True: + print(device.read(32).decode('ascii'), end = '') From 172978b335084f25579097436389fc28360ec10c Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 15 Feb 2021 20:21:24 +0000 Subject: [PATCH 02/32] stash --- lib/python/qmk/cli/console.py | 48 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 26c7a6fca891..cfd322e871f8 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -8,15 +8,15 @@ from milc import cli def patch_linux(dev): - platform_id = platform.platform().lower() - if 'linux' in platform_id: - hidraw = Path(dev['path'].decode('UTF-8')).name - descriptor_path = Path('/sys/class/hidraw/') / hidraw / 'device/report_descriptor' + # platform_id = platform.platform().lower() + # if 'linux' in platform_id: + # hidraw = Path(dev['path'].decode('UTF-8')).name + # descriptor_path = Path('/sys/class/hidraw/') / hidraw / 'device/report_descriptor' - report = descriptor_path.read_bytes() + # report = descriptor_path.read_bytes() - dev['usage_page'] = (report[2] << 8) + report[1]; - dev['usage'] = report[4]; + # dev['usage_page'] = (report[2] << 8) + report[1]; + # dev['usage'] = report[4]; return dev def is_console_hid(x): @@ -29,7 +29,7 @@ def search(): @cli.argument('-i', '--index', default=0, type=int, help='Device index.') @cli.subcommand('kinda hid_listen ish.') def console(cli): - """TODO. + """TODO: """ if cli.args.list: @@ -39,18 +39,26 @@ def console(cli): cli.log.info("%02x:%02x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) return - print('Waiting for device:') + try: + print('Waiting for device:') - selected = None - while selected is None: - found = search() - selected = found[cli.args.index] if found[cli.args.index:] else None + while True: + selected = None + while selected is None: + found = search() + selected = found[cli.args.index] if found[cli.args.index:] else None + print('.', end = '', flush=True) + sleep(1) - print('.', end = '', flush=True) - sleep(1) + print() + print('Listening to %s:' % selected['path'].decode()) + device = hid.Device(path=selected['path']) + try: + while True: + print(device.read(32).decode('ascii'), end = '') - print() - print('Listening to %s:' % selected['path'].decode()) - device = hid.Device(path=selected['path']) - while True: - print(device.read(32).decode('ascii'), end = '') + except hid.HIDException: + print('Device disconnected.') + print('Waiting for new device:') + except KeyboardInterrupt: + pass From 3c0850724920b3bc2293979e30fd7889810ec6d4 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 15 Feb 2021 21:32:12 +0000 Subject: [PATCH 03/32] tidy up implementation --- lib/python/qmk/cli/console.py | 99 ++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index cfd322e871f8..856de7d44368 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -1,64 +1,77 @@ """hid_listen """ import hid -import platform +import asyncio +import signal from pathlib import Path -from time import sleep from milc import cli -def patch_linux(dev): - # platform_id = platform.platform().lower() - # if 'linux' in platform_id: - # hidraw = Path(dev['path'].decode('UTF-8')).name - # descriptor_path = Path('/sys/class/hidraw/') / hidraw / 'device/report_descriptor' - # report = descriptor_path.read_bytes() +def _is_console_hid(x): + return x['usage_page'] == 0xFF31 and x['usage'] == 0x0074 - # dev['usage_page'] = (report[2] << 8) + report[1]; - # dev['usage'] = report[4]; - return dev -def is_console_hid(x): - return x['usage_page'] == 0xFF31 and x['usage'] == 0x0074 +def _search(): + return list(filter(_is_console_hid, hid.enumerate())) + + +def list_devices(): + cli.log.info('Available devices:') + devices = _search() + for dev in devices: + cli.log.info(" %02x:%02x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) + + +def state_search(loop): + print('.', end='', flush=True) + found = _search() + selected = found[0] if found[0:] else None + + if selected: + loop.call_later(1, state_connect, loop, selected) + else: + loop.call_later(1, state_search, loop) + + +def state_connect(loop, selected): + print() + print('Listening to %s:' % selected['path'].decode()) + device = hid.Device(path=selected['path']) + loop.call_soon(state_read, loop, device) + + +def state_read(loop, device): + print(device.read(32).decode('ascii'), end='') + loop.call_later(0.1, state_read, loop, device) + + +def state_exception(loop, context): + # print('Exception handler called') + # print(context) + print('Device disconnected.') + print('Waiting for new device:') + loop.call_soon(state_search, loop) -def search(): - return list(filter(is_console_hid, map(patch_linux, hid.enumerate()))) @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') -@cli.argument('-i', '--index', default=0, type=int, help='Device index.') @cli.subcommand('kinda hid_listen ish.') def console(cli): """TODO: """ if cli.args.list: - cli.log.info('Available devices:') - devices = search() - for dev in devices: - cli.log.info("%02x:%02x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) - return + return list_devices() + + print('Waiting for device:') + + loop = asyncio.get_event_loop() + + loop.add_signal_handler(signal.SIGINT, loop.stop) # Handle ctrl+c + loop.set_exception_handler(state_exception) # Handle disconnect/connect errors + loop.call_soon(state_search, loop) try: - print('Waiting for device:') - - while True: - selected = None - while selected is None: - found = search() - selected = found[cli.args.index] if found[cli.args.index:] else None - print('.', end = '', flush=True) - sleep(1) - - print() - print('Listening to %s:' % selected['path'].decode()) - device = hid.Device(path=selected['path']) - try: - while True: - print(device.read(32).decode('ascii'), end = '') - - except hid.HIDException: - print('Device disconnected.') - print('Waiting for new device:') - except KeyboardInterrupt: - pass + loop.run_forever() + finally: + loop.close() From 80df63802a85d66486fbec4dc2bd29fcdb1d3400 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 15 Feb 2021 23:06:26 +0000 Subject: [PATCH 04/32] Tidy up slightly for review --- lib/python/qmk/cli/console.py | 53 ++++++++++++++++++++++------------- requirements-dev.txt | 1 + 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 856de7d44368..d53281aebb09 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -1,9 +1,24 @@ -"""hid_listen +"""Acquire debugging information from usb hid devices + +cli implementation of https://www.pjrc.com/teensy/hid_listen.html + +State machine is implemented as follows: + +-+ + +-+ More Data? + | +------------+ + | | | ++-----+-------+ +-----------------+ +-----+------+ | +| | | | | | | +| Search +----->+ Connect +------>+ Read +<----+ +| | | | | | ++-----^-------+ +-----------------+ +------+-----+ + | | + +-----------------------------------------------+ + Disconnect/Error """ import hid import asyncio import signal -from pathlib import Path from milc import cli @@ -23,41 +38,41 @@ def list_devices(): cli.log.info(" %02x:%02x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) -def state_search(loop): +def state_search(state_machine): print('.', end='', flush=True) found = _search() selected = found[0] if found[0:] else None if selected: - loop.call_later(1, state_connect, loop, selected) + state_machine.call_later(1, state_connect, state_machine, selected) else: - loop.call_later(1, state_search, loop) + state_machine.call_later(1, state_search, state_machine) -def state_connect(loop, selected): +def state_connect(state_machine, selected): print() print('Listening to %s:' % selected['path'].decode()) device = hid.Device(path=selected['path']) - loop.call_soon(state_read, loop, device) + state_machine.call_soon(state_read, state_machine, device) -def state_read(loop, device): +def state_read(state_machine, device): print(device.read(32).decode('ascii'), end='') - loop.call_later(0.1, state_read, loop, device) + state_machine.call_later(0.1, state_read, state_machine, device) -def state_exception(loop, context): +def state_exception(state_machine, context): # print('Exception handler called') # print(context) print('Device disconnected.') print('Waiting for new device:') - loop.call_soon(state_search, loop) + state_machine.call_soon(state_search, state_machine) @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') -@cli.subcommand('kinda hid_listen ish.') +@cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True) def console(cli): - """TODO: + """Acquire debugging information from usb hid devices """ if cli.args.list: @@ -65,13 +80,13 @@ def console(cli): print('Waiting for device:') - loop = asyncio.get_event_loop() + state_machine = asyncio.get_event_loop() - loop.add_signal_handler(signal.SIGINT, loop.stop) # Handle ctrl+c - loop.set_exception_handler(state_exception) # Handle disconnect/connect errors - loop.call_soon(state_search, loop) + state_machine.add_signal_handler(signal.SIGINT, state_machine.stop) # Handle ctrl+c + state_machine.set_exception_handler(state_exception) # Handle disconnect/connect errors + state_machine.call_soon(state_search, state_machine) try: - loop.run_forever() + state_machine.run_forever() finally: - loop.close() + state_machine.close() diff --git a/requirements-dev.txt b/requirements-dev.txt index 1db3b6d73315..dd522d7641e9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,7 @@ -r requirements.txt # Python development requirements +hid nose2 flake8 pep8-naming From 77f131406aee51457c397a5408e945ec7416fc23 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 15 Feb 2021 23:15:45 +0000 Subject: [PATCH 05/32] Tidy up slightly for review --- lib/python/qmk/cli/console.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index d53281aebb09..4ac5ae31da88 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -3,6 +3,7 @@ cli implementation of https://www.pjrc.com/teensy/hid_listen.html State machine is implemented as follows: + +-+ +-+ More Data? | +------------+ @@ -11,10 +12,10 @@ | | | | | | | | Search +----->+ Connect +------>+ Read +<----+ | | | | | | -+-----^-------+ +-----------------+ +------+-----+ - | | ++-----+-------+ +-----------------+ +------+-----+ + ^ | + | Disconnect/Error? | +-----------------------------------------------+ - Disconnect/Error """ import hid import asyncio From 808804313cc256b30a95651b621a38659251b290 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 15 Feb 2021 23:52:50 +0000 Subject: [PATCH 06/32] Bodge environment to make tests pass --- .github/workflows/cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 28c6bb367985..df727518e577 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -23,6 +23,6 @@ jobs: with: submodules: recursive - name: Install dependencies - run: pip3 install -r requirements.txt + run: pip3 install -r requirements-dev.txt - name: Run tests run: bin/qmk pytest From 4534b5d1d8f4f48e38e6faff91f7e77673b2e568 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 16 Feb 2021 23:37:15 +0000 Subject: [PATCH 07/32] Refactor away from asyncio due to windows issues --- lib/python/qmk/cli/console.py | 62 ++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 4ac5ae31da88..dac8094bce91 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -18,12 +18,35 @@ +-----------------------------------------------+ """ import hid -import asyncio -import signal +import queue +import time from milc import cli +class StateMachine(queue.Queue): + def transition(self, func, *args): + self.put(lambda: func(self, *args)) + + def transition_later(self, delay, func, *args): + time.sleep(delay) + self.put(lambda: func(self, *args)) + + def on_exception(self, func): + self._except_func = func + + def run_forever(self): + while True: + try: + f = self.get() + f() + except KeyboardInterrupt: + break + except BaseException as e: + if self._except_func: + self._except_func(e) + + def _is_console_hid(x): return x['usage_page'] == 0xFF31 and x['usage'] == 0x0074 @@ -39,35 +62,35 @@ def list_devices(): cli.log.info(" %02x:%02x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) -def state_search(state_machine): +def state_search(sm): print('.', end='', flush=True) + found = _search() selected = found[0] if found[0:] else None if selected: - state_machine.call_later(1, state_connect, state_machine, selected) + sm.transition_later(0.5, state_connect, selected) else: - state_machine.call_later(1, state_search, state_machine) + sm.transition_later(1, state_search) -def state_connect(state_machine, selected): +def state_connect(sm, selected): print() print('Listening to %s:' % selected['path'].decode()) device = hid.Device(path=selected['path']) - state_machine.call_soon(state_read, state_machine, device) + sm.transition_later(1, state_read, device) -def state_read(state_machine, device): +def state_read(sm, device): print(device.read(32).decode('ascii'), end='') - state_machine.call_later(0.1, state_read, state_machine, device) + sm.transition(state_read, device) -def state_exception(state_machine, context): - # print('Exception handler called') - # print(context) +def state_exception(sm, context): + # print(str(e)) print('Device disconnected.') print('Waiting for new device:') - state_machine.call_soon(state_search, state_machine) + sm.transition(state_search) @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') @@ -81,13 +104,8 @@ def console(cli): print('Waiting for device:') - state_machine = asyncio.get_event_loop() - - state_machine.add_signal_handler(signal.SIGINT, state_machine.stop) # Handle ctrl+c - state_machine.set_exception_handler(state_exception) # Handle disconnect/connect errors - state_machine.call_soon(state_search, state_machine) + sm = StateMachine() + sm.transition(state_search) + sm.on_exception(lambda e: sm.transition(state_exception, e)) - try: - state_machine.run_forever() - finally: - state_machine.close() + sm.run_forever() From 933e2201d6e14c7d73fee98041dfdf3173a4c67c Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 19 Feb 2021 21:35:45 +0000 Subject: [PATCH 08/32] Filter devices --- lib/python/qmk/cli/console.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index dac8094bce91..bb2d23f59e28 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -51,8 +51,17 @@ def _is_console_hid(x): return x['usage_page'] == 0xFF31 and x['usage'] == 0x0074 +def _is_filtered_device(x): + name = "%04x:%04x" % (x['vendor_id'], x['product_id']) + return name.lower().startswith(cli.args.device.lower()) + + def _search(): - return list(filter(_is_console_hid, hid.enumerate())) + devices = filter(_is_console_hid, hid.enumerate()) + if cli.args.device: + devices = filter(_is_filtered_device, devices) + + return list(devices) def list_devices(): @@ -66,7 +75,7 @@ def state_search(sm): print('.', end='', flush=True) found = _search() - selected = found[0] if found[0:] else None + selected = found[cli.args.index] if found[cli.args.index:] else None if selected: sm.transition_later(0.5, state_connect, selected) @@ -82,7 +91,7 @@ def state_connect(sm, selected): def state_read(sm, device): - print(device.read(32).decode('ascii'), end='') + print(device.read(32, 1).decode('ascii'), end='') sm.transition(state_read, device) @@ -93,6 +102,8 @@ def state_exception(sm, context): sm.transition(state_search) +@cli.argument('-d', '--device', help='device to select - uses format :.') +@cli.argument('-i', '--index', default=0, help='device index to select.') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') @cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True) def console(cli): From ba4e8254d8cbcdb2256b7451ba1e4cc4de4bac39 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 19 Feb 2021 21:41:11 +0000 Subject: [PATCH 09/32] align vid/pid printing --- lib/python/qmk/cli/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index bb2d23f59e28..8b73965f772d 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -68,7 +68,7 @@ def list_devices(): cli.log.info('Available devices:') devices = _search() for dev in devices: - cli.log.info(" %02x:%02x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) + cli.log.info(" %04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) def state_search(sm): From 81fdad398d4a98fd51bd30f977068f8899e0996c Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 1 May 2021 07:58:02 -0700 Subject: [PATCH 10/32] Add hidapi to the installers --- requirements-dev.txt | 1 - requirements.txt | 1 + util/install/arch.sh | 10 +++++----- util/install/debian.sh | 7 +++---- util/install/fedora.sh | 9 ++++----- util/install/gentoo.sh | 7 ++++--- util/install/msys2.sh | 9 ++++----- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index dd522d7641e9..1db3b6d73315 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,6 @@ -r requirements.txt # Python development requirements -hid nose2 flake8 pep8-naming diff --git a/requirements.txt b/requirements.txt index 8553e2c3f02d..a5b5800613a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ appdirs argcomplete colorama dotty-dict +hid hjson jsonschema>=3 milc>=1.3.0 diff --git a/util/install/arch.sh b/util/install/arch.sh index 7442e2f136fb..9e16074a6ad2 100755 --- a/util/install/arch.sh +++ b/util/install/arch.sh @@ -4,13 +4,13 @@ _qmk_install() { echo "Installing dependencies" sudo pacman --needed --noconfirm -S \ - base-devel clang diffutils gcc git unzip wget zip \ - python-pip \ - avr-binutils \ - arm-none-eabi-binutils arm-none-eabi-gcc arm-none-eabi-newlib \ - avrdude dfu-programmer dfu-util + base-devel clang diffutils gcc git unzip wget zip python-pip \ + avr-binutils arm-none-eabi-binutils arm-none-eabi-gcc \ + arm-none-eabi-newlib avrdude dfu-programmer dfu-util sudo pacman --needed --noconfirm -U https://archive.archlinux.org/packages/a/avr-gcc/avr-gcc-8.3.0-1-x86_64.pkg.tar.xz sudo pacman --needed --noconfirm -S avr-libc # Must be installed after the above, or it will bring in the latest avr-gcc instead + sudo pacman --needed --noconfirm -S hidapi # This will fail if the community repo isn't enabled + python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt } diff --git a/util/install/debian.sh b/util/install/debian.sh index 0ae9764a33c1..d9e1b6c784d2 100755 --- a/util/install/debian.sh +++ b/util/install/debian.sh @@ -13,10 +13,9 @@ _qmk_install() { sudo apt-get -yq install \ build-essential clang-format diffutils gcc git unzip wget zip \ - python3-pip \ - binutils-avr gcc-avr avr-libc \ - binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi \ - avrdude dfu-programmer dfu-util teensy-loader-cli libusb-dev + python3-pip binutils-avr gcc-avr avr-libc binutils-arm-none-eabi \ + gcc-arm-none-eabi libnewlib-arm-none-eabi avrdude dfu-programmer \ + dfu-util teensy-loader-cli libhidapi-hidraw0 python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt } diff --git a/util/install/fedora.sh b/util/install/fedora.sh index 44b71b98bff4..f598270cacd3 100755 --- a/util/install/fedora.sh +++ b/util/install/fedora.sh @@ -5,11 +5,10 @@ _qmk_install() { # TODO: Check whether devel/headers packages are really needed sudo dnf -y install \ - clang diffutils git gcc glibc-headers kernel-devel kernel-headers make unzip wget zip \ - python3 \ - avr-binutils avr-gcc avr-libc \ - arm-none-eabi-binutils-cs arm-none-eabi-gcc-cs arm-none-eabi-newlib \ - avrdude dfu-programmer dfu-util libusb-devel + clang diffutils git gcc glibc-headers kernel-devel kernel-headers \ + make unzip wget zip python3 avr-binutils avr-gcc avr-libc \ + arm-none-eabi-binutils-cs arm-none-eabi-gcc-cs arm-none-eabi-newlib \ + avrdude dfu-programmer dfu-util hidapi python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt } diff --git a/util/install/gentoo.sh b/util/install/gentoo.sh index 97eb5df07f63..604d07bf84fd 100755 --- a/util/install/gentoo.sh +++ b/util/install/gentoo.sh @@ -22,9 +22,10 @@ _qmk_install() { echo "sys-devel/gcc multilib" | sudo tee --append /etc/portage/package.use/qmkfirmware >/dev/null sudo emerge -auN sys-devel/gcc sudo emerge -au --noreplace \ - app-arch/unzip app-arch/zip net-misc/wget sys-devel/clang sys-devel/crossdev \ - \>=dev-lang/python-3.7 \ - dev-embedded/avrdude dev-embedded/dfu-programmer app-mobilephone/dfu-util + app-arch/unzip app-arch/zip net-misc/wget sys-devel/clang \ + sys-devel/crossdev \>=dev-lang/python-3.7 dev-embedded/avrdude \ + dev-embedded/dfu-programmer app-mobilephone/dfu-util sys-apps/hwloc \ + dev-libs/hidapi sudo crossdev -s4 --stable --g \<9 --portage --verbose --target avr sudo crossdev -s4 --stable --g \<9 --portage --verbose --target arm-none-eabi diff --git a/util/install/msys2.sh b/util/install/msys2.sh index c8598a60fa50..2fd5ba4277b5 100755 --- a/util/install/msys2.sh +++ b/util/install/msys2.sh @@ -9,11 +9,10 @@ _qmk_install() { pacman --needed --noconfirm --disable-download-timeout -S pactoys-git pacboy sync --needed --noconfirm --disable-download-timeout \ - base-devel: toolchain:x clang:x git: unzip: \ - python3-pip:x \ - avr-binutils:x avr-gcc:x avr-libc:x \ - arm-none-eabi-binutils:x arm-none-eabi-gcc:x arm-none-eabi-newlib:x \ - avrdude:x bootloadhid:x dfu-programmer:x dfu-util:x teensy-loader-cli:x + base-devel: toolchain:x clang:x git: unzip: python3-pip:x \ + avr-binutils:x avr-gcc:x avr-libc:x arm-none-eabi-binutils:x \ + arm-none-eabi-gcc:x arm-none-eabi-newlib:x avrdude:x bootloadhid:x \ + dfu-programmer:x dfu-util:x teensy-loader-cli:x hidapi:x _qmk_install_drivers From 22b6e1c25be9170da76e277c665f887254ed9f0c Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 1 May 2021 12:05:32 -0700 Subject: [PATCH 11/32] start preparing for multiple hid_listeners --- lib/python/qmk/cli/console.py | 137 +++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 53 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 8b73965f772d..d22f500a3559 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -20,20 +20,51 @@ import hid import queue import time +from platform import platform from milc import cli +IS_LINUX = 'linux' in platform().lower() + + +class ConsoleMessages(queue.Queue): + """Process and print console messages from devices. + """ + def print_message(self, message): + """Nicely format and print a message. + """ + cli.echo('{fg_blue}%(vid)s:%(pid)s:%(index)s{fg_reset}: %(text)s' % message) + + def run_forever(self): + while True: + try: + self.print_message(self.get()) + + except KeyboardInterrupt: + break + + except Exception as e: + cli.log.error("Uncaught exception: %s: %s", e.__class__.__name__, e) + cli.log.exception(e) + class StateMachine(queue.Queue): + def __init__(self, console): + self.console = console + + super().__init__() + def transition(self, func, *args): - self.put(lambda: func(self, *args)) + self.put(lambda: func(*args)) def transition_later(self, delay, func, *args): time.sleep(delay) - self.put(lambda: func(self, *args)) + self.put(lambda: func(*args)) - def on_exception(self, func): - self._except_func = func + def on_exception(self, e): + cli.log.error('Exception: %s: %s', e.__class__.__name__, e) + cli.log.exception(e) + self.transition(self.on_exception, e) def run_forever(self): while True: @@ -43,80 +74,80 @@ def run_forever(self): except KeyboardInterrupt: break except BaseException as e: - if self._except_func: - self._except_func(e) - + self.on_exception(e) -def _is_console_hid(x): - return x['usage_page'] == 0xFF31 and x['usage'] == 0x0074 + def is_console_hid(self, hid_device): + return hid_device['usage_page'] == 0xFF31 and hid_device['usage'] == 0x0074 + def has_3rd_interface(self, hid_device): + return hid_device['interface_number'] == 2 -def _is_filtered_device(x): - name = "%04x:%04x" % (x['vendor_id'], x['product_id']) - return name.lower().startswith(cli.args.device.lower()) + def is_filtered_device(self, hid_device): + name = "%04x:%04x" % (hid_device['vendor_id'], hid_device['product_id']) + return name.lower().startswith(cli.args.device.lower()) + def find_devices(self): + hid_devices = hid.enumerate() + devices = list(filter(self.is_console_hid, hid_devices)) -def _search(): - devices = filter(_is_console_hid, hid.enumerate()) - if cli.args.device: - devices = filter(_is_filtered_device, devices) + if not devices: + # Some versions of linux don't report usage and usage_page. In that + # case we fallback to devices that have a 3rd interface + devices = filter(self.has_3rd_interface, hid_devices) - return list(devices) + if cli.args.device: + devices = filter(self.is_filtered_device, devices) + return devices -def list_devices(): - cli.log.info('Available devices:') - devices = _search() - for dev in devices: - cli.log.info(" %04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) - + def search(self, *rest): + print('.', end='', flush=True) -def state_search(sm): - print('.', end='', flush=True) + found = list(self.find_devices()) + selected = found[cli.args.index] if found[cli.args.index:] else None - found = _search() - selected = found[cli.args.index] if found[cli.args.index:] else None + if selected: + self.transition_later(0.5, self.on_connect, selected) + else: + self.transition_later(1, self.search) - if selected: - sm.transition_later(0.5, state_connect, selected) - else: - sm.transition_later(1, state_search) + def on_connect(self, selected, *rest): + print() + print('Listening to %s:' % selected['path'].decode()) + device = hid.Device(path=selected['path']) + self.transition_later(1, self.read, device) + def read(self, device, *rest): + print(device.read(32, 1).decode('ascii'), end='') + self.transition(self.read, device) -def state_connect(sm, selected): - print() - print('Listening to %s:' % selected['path'].decode()) - device = hid.Device(path=selected['path']) - sm.transition_later(1, state_read, device) + def on_exception(self, context, *rest): + print(str(context)) + print('Device disconnected.') + print('Waiting for new device..', end="") + self.transition(self.search) -def state_read(sm, device): - print(device.read(32, 1).decode('ascii'), end='') - sm.transition(state_read, device) - +def list_devices(sm): + cli.log.info('Available devices:') -def state_exception(sm, context): - # print(str(e)) - print('Device disconnected.') - print('Waiting for new device:') - sm.transition(state_search) + for dev in sm.find_devices(): + cli.log.info(" %04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) -@cli.argument('-d', '--device', help='device to select - uses format :.') +@cli.argument('-d', '--device', help='device to select - uses format :[:].') @cli.argument('-i', '--index', default=0, help='device index to select.') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') @cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True) def console(cli): """Acquire debugging information from usb hid devices """ + console = ConsoleMessages() + sm = StateMachine(console) if cli.args.list: - return list_devices() - - print('Waiting for device:') - - sm = StateMachine() - sm.transition(state_search) - sm.on_exception(lambda e: sm.transition(state_exception, e)) + return list_devices(sm) + print('Waiting for device..', end="") + sm.transition(sm.search) sm.run_forever() From 0e47e49a47fd61932dfadda31c7fbdc1f8b06ada Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 1 May 2021 12:07:05 -0700 Subject: [PATCH 12/32] udev rules for hid_listen --- util/udev/50-qmk.rules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/udev/50-qmk.rules b/util/udev/50-qmk.rules index acaa7dcc587c..679fe4ced38e 100644 --- a/util/udev/50-qmk.rules +++ b/util/udev/50-qmk.rules @@ -60,3 +60,6 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000e", TAG+="uacc SUBSYSTEMS=="usb", ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="0036", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" ### Micro SUBSYSTEMS=="usb", ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="0037", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" + +# hid_listen +KERNEL=="hidraw*", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl" From e360b685861a68addf1448492d71736d2a4ea839 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 1 May 2021 20:43:52 -0700 Subject: [PATCH 13/32] refactor to move closer to end state --- lib/python/qmk/cli/console.py | 79 +++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index d22f500a3559..daf87510ca2d 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -33,7 +33,7 @@ class ConsoleMessages(queue.Queue): def print_message(self, message): """Nicely format and print a message. """ - cli.echo('{fg_blue}%(vid)s:%(pid)s:%(index)s{fg_reset}: %(text)s' % message) + cli.echo('{fg_blue}%(vid)s:%(pid)s{fg_reset}: %(text)s' % message) def run_forever(self): while True: @@ -48,18 +48,56 @@ def run_forever(self): cli.log.exception(e) +class MonitorDevice(object): + def __init__(self, console, hid_device): + self.console = console + self.device = hid.Device(path=hid_device['path']) + self.vid = hid_device['vendor_id'] + self.pid = hid_device['product_id'] + self.current_line = '' + + print() + print('Listening to %s %s (%04x:%04x) on %s:' % (hid_device['manufacturer_string'], hid_device['product_string'], hid_device['vendor_id'], hid_device['product_id'], hid_device['path'].decode())) + + def read(self, size, encoding='ascii', timeout=1): + """Read size bytes from the device. + """ + return self.device.read(size, timeout).decode(encoding) + + def read_line(self): + """Read from the device's console until we get a \n. + """ + while '\n' not in self.current_line: + self.current_line += self.read(32) + + lines = self.current_line.split('\n', 1) + self.current_line = lines[1] + + return lines[0] + + def on_exception(self, e): + cli.log.error('Exception: %s: %s: %s', self.__class__.__name__, e.__class__.__name__, e) + cli.log.exception(e) + + def run_forever(self): + while True: + self.console.put({ + 'vid': self.vid, + 'pid': self.pid, + 'text': self.read_line() + }) + + class StateMachine(queue.Queue): def __init__(self, console): self.console = console super().__init__() - def transition(self, func, *args): - self.put(lambda: func(*args)) - - def transition_later(self, delay, func, *args): - time.sleep(delay) - self.put(lambda: func(*args)) + def transition(self, func, *args, delay=None, **kwargs): + if delay: + time.sleep(delay) + self.put((func, args, kwargs)) def on_exception(self, e): cli.log.error('Exception: %s: %s', e.__class__.__name__, e) @@ -69,8 +107,8 @@ def on_exception(self, e): def run_forever(self): while True: try: - f = self.get() - f() + f, args, kwargs = self.get() + f(*args, **kwargs) except KeyboardInterrupt: break except BaseException as e: @@ -100,29 +138,24 @@ def find_devices(self): return devices - def search(self, *rest): + def search(self): print('.', end='', flush=True) found = list(self.find_devices()) selected = found[cli.args.index] if found[cli.args.index:] else None if selected: - self.transition_later(0.5, self.on_connect, selected) + self.transition(self.on_connect, selected, delay=0.5) else: - self.transition_later(1, self.search) - - def on_connect(self, selected, *rest): - print() - print('Listening to %s:' % selected['path'].decode()) - device = hid.Device(path=selected['path']) - self.transition_later(1, self.read, device) + self.transition(self.search, delay=1) - def read(self, device, *rest): - print(device.read(32, 1).decode('ascii'), end='') - self.transition(self.read, device) + def on_connect(self, hid_device): + monitor = MonitorDevice(self.console, hid_device) + self.transition(monitor.run_forever, delay=1) - def on_exception(self, context, *rest): - print(str(context)) + def on_exception(self, e): + cli.log.error('Exception: %s: %s: %s', self.__class__.__name__, e.__class__.__name__, e) + cli.log.exception(e) print('Device disconnected.') print('Waiting for new device..', end="") self.transition(self.search) From 60b097ca187e09cb6fab16e55669d082c7b6a329 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 1 May 2021 21:26:06 -0700 Subject: [PATCH 14/32] very basic implementation of the threaded model --- lib/python/qmk/cli/console.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index daf87510ca2d..d504dd2f5f92 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -21,6 +21,7 @@ import queue import time from platform import platform +from threading import Thread from milc import cli @@ -33,7 +34,7 @@ class ConsoleMessages(queue.Queue): def print_message(self, message): """Nicely format and print a message. """ - cli.echo('{fg_blue}%(vid)s:%(pid)s{fg_reset}: %(text)s' % message) + cli.echo('{fg_blue}%(vendor_id)04x:%(product_id)04x:%(pathstr)s{fg_reset}: %(text)s' % message) def run_forever(self): while True: @@ -51,13 +52,13 @@ def run_forever(self): class MonitorDevice(object): def __init__(self, console, hid_device): self.console = console + hid_device['pathstr'] = hid_device['path'].decode('utf-8') + self.hid_device = hid_device self.device = hid.Device(path=hid_device['path']) - self.vid = hid_device['vendor_id'] - self.pid = hid_device['product_id'] self.current_line = '' print() - print('Listening to %s %s (%04x:%04x) on %s:' % (hid_device['manufacturer_string'], hid_device['product_string'], hid_device['vendor_id'], hid_device['product_id'], hid_device['path'].decode())) + cli.log.info('Listening to {fg_cyan}%s %s{fg_reset} ({fg_blue}%04x:%04x{fg_reset}) on {fg_blue}%s{fg_reset}:', hid_device['manufacturer_string'], hid_device['product_string'], hid_device['vendor_id'], hid_device['product_id'], hid_device['path'].decode()) def read(self, size, encoding='ascii', timeout=1): """Read size bytes from the device. @@ -82,8 +83,7 @@ def on_exception(self, e): def run_forever(self): while True: self.console.put({ - 'vid': self.vid, - 'pid': self.pid, + **self.hid_device, 'text': self.read_line() }) @@ -105,6 +105,8 @@ def on_exception(self, e): self.transition(self.on_exception, e) def run_forever(self): + self.transition(self.search) + while True: try: f, args, kwargs = self.get() @@ -156,8 +158,8 @@ def on_connect(self, hid_device): def on_exception(self, e): cli.log.error('Exception: %s: %s: %s', self.__class__.__name__, e.__class__.__name__, e) cli.log.exception(e) - print('Device disconnected.') - print('Waiting for new device..', end="") + cli.log.info('Device disconnected.') + print('Waiting for new device..', end="", flush=True) self.transition(self.search) @@ -181,6 +183,7 @@ def console(cli): if cli.args.list: return list_devices(sm) - print('Waiting for device..', end="") - sm.transition(sm.search) - sm.run_forever() + print('Waiting for device..', end="", flush=True) + sm_t = Thread(target=sm.run_forever, daemon=True) + sm_t.start() + console.run_forever() From 399c8a87b2f491d7a47e2e20b6bf49a9e41e5510 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 2 May 2021 12:46:14 -0700 Subject: [PATCH 15/32] refactor how vid/pid/index are supplied and parsed --- lib/python/qmk/cli/console.py | 118 ++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 26 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index d504dd2f5f92..c46571a70b26 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -34,7 +34,7 @@ class ConsoleMessages(queue.Queue): def print_message(self, message): """Nicely format and print a message. """ - cli.echo('{fg_blue}%(vendor_id)04x:%(product_id)04x:%(pathstr)s{fg_reset}: %(text)s' % message) + cli.echo('{fg_blue}%(vendor_id)04X:%(product_id)04X:%(index)d{fg_reset}: %(text)s' % message) def run_forever(self): while True: @@ -58,7 +58,7 @@ def __init__(self, console, hid_device): self.current_line = '' print() - cli.log.info('Listening to {fg_cyan}%s %s{fg_reset} ({fg_blue}%04x:%04x{fg_reset}) on {fg_blue}%s{fg_reset}:', hid_device['manufacturer_string'], hid_device['product_string'], hid_device['vendor_id'], hid_device['product_id'], hid_device['path'].decode()) + cli.log.info('Listening to {fg_cyan}%s %s{fg_reset} ({fg_blue}%04X:%04X{fg_reset}) on {fg_blue}%s{fg_reset}:', hid_device['manufacturer_string'], hid_device['product_string'], hid_device['vendor_id'], hid_device['product_id'], hid_device['path'].decode()) def read(self, size, encoding='ascii', timeout=1): """Read size bytes from the device. @@ -88,9 +88,12 @@ def run_forever(self): }) -class StateMachine(queue.Queue): - def __init__(self, console): +class FindDevices(queue.Queue): + def __init__(self, console, vid, pid, index): self.console = console + self.vid = vid + self.pid = pid + self.index = index super().__init__() @@ -100,11 +103,15 @@ def transition(self, func, *args, delay=None, **kwargs): self.put((func, args, kwargs)) def on_exception(self, e): + """Called when an exception occurs in `run_forever`. + """ cli.log.error('Exception: %s: %s', e.__class__.__name__, e) cli.log.exception(e) self.transition(self.on_exception, e) def run_forever(self): + """Process messages from our queue in a loop. + """ self.transition(self.search) while True: @@ -117,43 +124,65 @@ def run_forever(self): self.on_exception(e) def is_console_hid(self, hid_device): + """Returns true when the usage page indicates it's a teensy-style console. + """ return hid_device['usage_page'] == 0xFF31 and hid_device['usage'] == 0x0074 def has_3rd_interface(self, hid_device): + """Returns true when this is the third interface supplied by a usb device. + """ return hid_device['interface_number'] == 2 def is_filtered_device(self, hid_device): - name = "%04x:%04x" % (hid_device['vendor_id'], hid_device['product_id']) - return name.lower().startswith(cli.args.device.lower()) + """Returns True if the device should be included in the list of available consoles. + """ + return int2hex(hid_device['vendor_id']) == self.vid and int2hex(hid_device['product_id']) == self.pid def find_devices(self): + """Returns a list of available teensy-style consoles. + """ + devices = [] + hid_devices = hid.enumerate() devices = list(filter(self.is_console_hid, hid_devices)) if not devices: # Some versions of linux don't report usage and usage_page. In that # case we fallback to devices that have a 3rd interface - devices = filter(self.has_3rd_interface, hid_devices) + devices = list(filter(self.has_3rd_interface, hid_devices)) + + if self.vid and self.pid: + devices = list(filter(self.is_filtered_device, devices)) + + # Add index numbers + device_index = {} + for device in devices: + id = ':'.join((int2hex(device['vendor_id']), int2hex(device['product_id']))) - if cli.args.device: - devices = filter(self.is_filtered_device, devices) + if id not in device_index: + device_index[id] = 0 + + device_index[id] += 1 + device['index'] = device_index[id] return devices def search(self): - print('.', end='', flush=True) + """Look for devices with teensy-style consoles and, if available, connect to it. + """ + found = self.find_devices() - found = list(self.find_devices()) - selected = found[cli.args.index] if found[cli.args.index:] else None + if found: + if self.index <= len(found): + monitor = MonitorDevice(self.console, found[self.index-1]) + return self.transition(monitor.run_forever, delay=1) - if selected: - self.transition(self.on_connect, selected, delay=0.5) + print() + cli.log.warning("Only %d devices found, requested index is %d. Trying again in %d seconds...", len(found), self.index, cli.config.console.wait) else: - self.transition(self.search, delay=1) + print('.', end='', flush=True) - def on_connect(self, hid_device): - monitor = MonitorDevice(self.console, hid_device) - self.transition(monitor.run_forever, delay=1) + self.transition(self.search, delay=cli.config.console.wait) def on_exception(self, e): cli.log.error('Exception: %s: %s: %s', self.__class__.__name__, e.__class__.__name__, e) @@ -163,27 +192,64 @@ def on_exception(self, e): self.transition(self.search) -def list_devices(sm): +def int2hex(number): + """Returns a string representation of the number as hex. + """ + return "%04X" % number + + +def list_devices(device_finder): cli.log.info('Available devices:') - for dev in sm.find_devices(): - cli.log.info(" %04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) + for dev in device_finder.find_devices(): + cli.log.info("\t%s:%s:%d\t%s\t%s %s", int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['path'].decode('utf-8'), dev['manufacturer_string'], dev['product_string']) @cli.argument('-d', '--device', help='device to select - uses format :[:].') -@cli.argument('-i', '--index', default=0, help='device index to select.') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') +@cli.argument('-w', '--wait', type=int, default=1, help="How many seconds to wait between checks (Default: 1)") @cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True) def console(cli): """Acquire debugging information from usb hid devices """ + vid = None + pid = None + index = 1 + + if cli.config.console.device: + device = cli.config.console.device.split(':') + + if len(device) == 2: + vid, pid = device + + elif len(device) == 3: + vid, pid, index = device + + if not index.isdigit(): + cli.log.error('Device index must be a number! Got "%s" instead.', index) + exit(1) + + index = int(index) + + if index < 1: + cli.log.error('Device index must be greater than 0! Got %s', index) + exit(1) + + else: + cli.log.error('Invalid format for device, expected ":[:]" but got "%s".', cli.config.console.device) + cli.print_help() + exit(1) + + vid = vid.upper() + pid = pid.upper() + console = ConsoleMessages() - sm = StateMachine(console) + device_finder = FindDevices(console, vid, pid, index) if cli.args.list: - return list_devices(sm) + return list_devices(device_finder) print('Waiting for device..', end="", flush=True) - sm_t = Thread(target=sm.run_forever, daemon=True) - sm_t.start() + device_finder_t = Thread(target=device_finder.run_forever, daemon=True) + device_finder_t.start() console.run_forever() From 850d129a2a7c3cbd9796c5b628aacf8de0df83ef Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 2 May 2021 14:54:52 -0700 Subject: [PATCH 16/32] windows improvements --- lib/python/qmk/cli/console.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index c46571a70b26..30fc453001fa 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -52,13 +52,12 @@ def run_forever(self): class MonitorDevice(object): def __init__(self, console, hid_device): self.console = console - hid_device['pathstr'] = hid_device['path'].decode('utf-8') self.hid_device = hid_device self.device = hid.Device(path=hid_device['path']) self.current_line = '' print() - cli.log.info('Listening to {fg_cyan}%s %s{fg_reset} ({fg_blue}%04X:%04X{fg_reset}) on {fg_blue}%s{fg_reset}:', hid_device['manufacturer_string'], hid_device['product_string'], hid_device['vendor_id'], hid_device['product_id'], hid_device['path'].decode()) + cli.log.info('Listening to {fg_cyan}%s %s{fg_reset} ({fg_blue}%04X:%04X{fg_reset}):', hid_device['manufacturer_string'], hid_device['product_string'], hid_device['vendor_id'], hid_device['product_id']) def read(self, size, encoding='ascii', timeout=1): """Read size bytes from the device. @@ -69,7 +68,7 @@ def read_line(self): """Read from the device's console until we get a \n. """ while '\n' not in self.current_line: - self.current_line += self.read(32) + self.current_line += self.read(32).replace('\x00', '') lines = self.current_line.split('\n', 1) self.current_line = lines[1] @@ -202,7 +201,7 @@ def list_devices(device_finder): cli.log.info('Available devices:') for dev in device_finder.find_devices(): - cli.log.info("\t%s:%s:%d\t%s\t%s %s", int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['path'].decode('utf-8'), dev['manufacturer_string'], dev['product_string']) + cli.log.info("\t%s:%s:%d\t%s %s", int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) @cli.argument('-d', '--device', help='device to select - uses format :[:].') From 5535d6ae3ae8519e47ed3fbb1ca5ed886fc10ef8 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 2 May 2021 16:59:16 -0700 Subject: [PATCH 17/32] read the report directly when usage page isn't available --- lib/python/qmk/cli/console.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 30fc453001fa..3d1052d27726 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -20,6 +20,7 @@ import hid import queue import time +from pathlib import Path from platform import platform from threading import Thread @@ -127,28 +128,41 @@ def is_console_hid(self, hid_device): """ return hid_device['usage_page'] == 0xFF31 and hid_device['usage'] == 0x0074 - def has_3rd_interface(self, hid_device): - """Returns true when this is the third interface supplied by a usb device. - """ - return hid_device['interface_number'] == 2 - def is_filtered_device(self, hid_device): """Returns True if the device should be included in the list of available consoles. """ return int2hex(hid_device['vendor_id']) == self.vid and int2hex(hid_device['product_id']) == self.pid - def find_devices(self): - """Returns a list of available teensy-style consoles. + def find_devices_by_report(self, hid_devices): + """Returns a list of available teensy-style consoles by doing a brute-force search. + + Some versions of linux don't report usage and usage_page. In that case we fallback to reading the report (possibly inaccurately) ourselves. """ devices = [] + for device in hid_devices: + path = device['path'].decode('utf-8') + + if path.startswith('/dev/hidraw'): + number = path[11:] + report = Path(f'/sys/class/hidraw/hidraw{number}/device/report_descriptor') + + if report.exists(): + rp = report.read_bytes() + + if rp[1] == 0x31 and rp[3] == 0x09: + devices.append(device) + + return devices + + def find_devices(self): + """Returns a list of available teensy-style consoles. + """ hid_devices = hid.enumerate() devices = list(filter(self.is_console_hid, hid_devices)) if not devices: - # Some versions of linux don't report usage and usage_page. In that - # case we fallback to devices that have a 3rd interface - devices = list(filter(self.has_3rd_interface, hid_devices)) + devices = self.find_devices_by_report(hid_devices) if self.vid and self.pid: devices = list(filter(self.is_filtered_device, devices)) From 7e9c268f7789ee08baa448c69d5746f42546d10d Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 2 May 2021 20:28:30 -0700 Subject: [PATCH 18/32] add per-device colors, the choice to show names or numbers, and refactor --- lib/python/qmk/cli/console.py | 158 ++++++++++++---------------------- 1 file changed, 53 insertions(+), 105 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 3d1052d27726..a3ad296534ba 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -1,64 +1,35 @@ """Acquire debugging information from usb hid devices cli implementation of https://www.pjrc.com/teensy/hid_listen.html - -State machine is implemented as follows: - - +-+ - +-+ More Data? - | +------------+ - | | | -+-----+-------+ +-----------------+ +-----+------+ | -| | | | | | | -| Search +----->+ Connect +------>+ Read +<----+ -| | | | | | -+-----+-------+ +-----------------+ +------+-----+ - ^ | - | Disconnect/Error? | - +-----------------------------------------------+ """ import hid -import queue -import time from pathlib import Path -from platform import platform from threading import Thread +from time import sleep from milc import cli -IS_LINUX = 'linux' in platform().lower() - - -class ConsoleMessages(queue.Queue): - """Process and print console messages from devices. - """ - def print_message(self, message): - """Nicely format and print a message. - """ - cli.echo('{fg_blue}%(vendor_id)04X:%(product_id)04X:%(index)d{fg_reset}: %(text)s' % message) - - def run_forever(self): - while True: - try: - self.print_message(self.get()) - - except KeyboardInterrupt: - break - - except Exception as e: - cli.log.error("Uncaught exception: %s: %s", e.__class__.__name__, e) - cli.log.exception(e) +LOG_COLOR = { + 'next': 0, + 'colors': [ + '{fg_blue}', + '{fg_cyan}', + '{fg_green}', + '{fg_magenta}', + '{fg_red}', + '{fg_yellow}' + ] +} class MonitorDevice(object): - def __init__(self, console, hid_device): - self.console = console + def __init__(self, hid_device, numeric): self.hid_device = hid_device + self.numeric = numeric self.device = hid.Device(path=hid_device['path']) self.current_line = '' - print() - cli.log.info('Listening to {fg_cyan}%s %s{fg_reset} ({fg_blue}%04X:%04X{fg_reset}):', hid_device['manufacturer_string'], hid_device['product_string'], hid_device['vendor_id'], hid_device['product_id']) + cli.log.info('Listening to %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', hid_device) def read(self, size, encoding='ascii', timeout=1): """Read size bytes from the device. @@ -76,52 +47,53 @@ def read_line(self): return lines[0] - def on_exception(self, e): - cli.log.error('Exception: %s: %s: %s', self.__class__.__name__, e.__class__.__name__, e) - cli.log.exception(e) - def run_forever(self): while True: - self.console.put({ - **self.hid_device, - 'text': self.read_line() - }) + try: + message = {**self.hid_device, 'text': self.read_line()} + if self.numeric: + cli.echo('%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all}: %(text)s' % message) + else: + cli.echo('%(color)s%(manufacturer_string)s:%(product_string)s:%(index)d{style_reset_all}: %(text)s' % message) + + except hid.HIDException: + break -class FindDevices(queue.Queue): - def __init__(self, console, vid, pid, index): - self.console = console + +class FindDevices(object): + def __init__(self, vid, pid, index, numeric): self.vid = vid self.pid = pid self.index = index - - super().__init__() - - def transition(self, func, *args, delay=None, **kwargs): - if delay: - time.sleep(delay) - self.put((func, args, kwargs)) - - def on_exception(self, e): - """Called when an exception occurs in `run_forever`. - """ - cli.log.error('Exception: %s: %s', e.__class__.__name__, e) - cli.log.exception(e) - self.transition(self.on_exception, e) + self.numeric = numeric def run_forever(self): """Process messages from our queue in a loop. """ - self.transition(self.search) + live_devices = {} while True: try: - f, args, kwargs = self.get() - f(*args, **kwargs) + for device in list(live_devices): + if not live_devices[device]['thread'].is_alive(): + cli.log.info('Disconnected from %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', live_devices[device]) + del live_devices[device] + + for device in self.find_devices(): + if device['path'] not in live_devices: + device['color'] = LOG_COLOR['colors'][LOG_COLOR['next']] + LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) + live_devices[device['path']] = device + monitor = MonitorDevice(device, self.numeric) + device['thread'] = Thread(target=monitor.run_forever, daemon=True) + + device['thread'].start() + + sleep(1) + except KeyboardInterrupt: break - except BaseException as e: - self.on_exception(e) def is_console_hid(self, hid_device): """Returns true when the usage page indicates it's a teensy-style console. @@ -180,30 +152,6 @@ def find_devices(self): return devices - def search(self): - """Look for devices with teensy-style consoles and, if available, connect to it. - """ - found = self.find_devices() - - if found: - if self.index <= len(found): - monitor = MonitorDevice(self.console, found[self.index-1]) - return self.transition(monitor.run_forever, delay=1) - - print() - cli.log.warning("Only %d devices found, requested index is %d. Trying again in %d seconds...", len(found), self.index, cli.config.console.wait) - else: - print('.', end='', flush=True) - - self.transition(self.search, delay=cli.config.console.wait) - - def on_exception(self, e): - cli.log.error('Exception: %s: %s: %s', self.__class__.__name__, e.__class__.__name__, e) - cli.log.exception(e) - cli.log.info('Device disconnected.') - print('Waiting for new device..', end="", flush=True) - self.transition(self.search) - def int2hex(number): """Returns a string representation of the number as hex. @@ -215,11 +163,14 @@ def list_devices(device_finder): cli.log.info('Available devices:') for dev in device_finder.find_devices(): - cli.log.info("\t%s:%s:%d\t%s %s", int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) + color = LOG_COLOR['colors'][LOG_COLOR['next']] + LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) + cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) @cli.argument('-d', '--device', help='device to select - uses format :[:].') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') +@cli.argument('-n', '--numeric', arg_only=True, action='store_true', help='Show VID/PID instead of names.') @cli.argument('-w', '--wait', type=int, default=1, help="How many seconds to wait between checks (Default: 1)") @cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True) def console(cli): @@ -256,13 +207,10 @@ def console(cli): vid = vid.upper() pid = pid.upper() - console = ConsoleMessages() - device_finder = FindDevices(console, vid, pid, index) + device_finder = FindDevices(vid, pid, index, cli.args.numeric) if cli.args.list: return list_devices(device_finder) - print('Waiting for device..', end="", flush=True) - device_finder_t = Thread(target=device_finder.run_forever, daemon=True) - device_finder_t.start() - console.run_forever() + print('Looking for devices...', flush=True) + device_finder.run_forever() From f481237b83bcb20e52113629cd92bdedf167a562 Mon Sep 17 00:00:00 2001 From: Zach White Date: Mon, 3 May 2021 08:14:03 -0700 Subject: [PATCH 19/32] add timestamps --- lib/python/qmk/cli/console.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index a3ad296534ba..eec3b432ab30 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -5,7 +5,7 @@ import hid from pathlib import Path from threading import Thread -from time import sleep +from time import sleep, strftime from milc import cli @@ -51,11 +51,11 @@ def run_forever(self): while True: try: message = {**self.hid_device, 'text': self.read_line()} + identifier = (int2hex(message['vendor_id']), int2hex(message['product_id'])) if self.numeric else (message['manufacturer_string'], message['product_string']) + message['identifier'] = ':'.join(identifier) + message['ts'] = '{fg_green}%s{fg_reset} ' % (strftime(cli.config.general.datetime_fmt),) if cli.args.timestamp else '' - if self.numeric: - cli.echo('%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all}: %(text)s' % message) - else: - cli.echo('%(color)s%(manufacturer_string)s:%(product_string)s:%(index)d{style_reset_all}: %(text)s' % message) + cli.echo('%(ts)s%(color)s%(identifier)s:%(index)d{style_reset_all}: %(text)s' % message) except hid.HIDException: break @@ -171,6 +171,7 @@ def list_devices(device_finder): @cli.argument('-d', '--device', help='device to select - uses format :[:].') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') @cli.argument('-n', '--numeric', arg_only=True, action='store_true', help='Show VID/PID instead of names.') +@cli.argument('-t', '--timestamp', arg_only=True, action='store_true', help='Print the timestamp for received messages as well.') @cli.argument('-w', '--wait', type=int, default=1, help="How many seconds to wait between checks (Default: 1)") @cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True) def console(cli): From bf9ad310041b6e2d892368dae20ee9d3f3f0a997 Mon Sep 17 00:00:00 2001 From: Zach White Date: Mon, 3 May 2021 11:29:17 -0700 Subject: [PATCH 20/32] Add support for showing bootloaders --- lib/python/qmk/cli/console.py | 76 ++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index eec3b432ab30..725404a2d805 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -21,6 +21,22 @@ ] } +KNOWN_BOOTLOADERS = { + # VID , PID + ('16C0', '0478'): 'Teensy Halfkay Bootloader', + ('0483', 'DF11'): 'STM32 Bootloader', + ('314B', '0106'): 'APM32 Bootloader', + ('1EAF', '0003'): 'STM32duino Bootloader', + ('16C0', '05DC'): 'USBaspLoader', + ('1C11', 'B007'): 'Kiibohd DFU Bootloader', + ('03EB', '2FEF'): 'ATmega16U2 Bootloader', + ('03EB', '2FF0'): 'ATmega32U2 Bootloader', + ('03EB', '2FF3'): 'ATmega16U4 Bootloader', + ('03EB', '2FF4'): 'ATmega32U4 Bootloader', + ('03EB', '2FF9'): 'AT90USB64 Bootloader', + ('03EB', '2FFA'): 'AT90USB162 Bootloader', + ('03EB', '2FFB'): 'AT90USB128 Bootloader' +} class MonitorDevice(object): def __init__(self, hid_device, numeric): @@ -29,7 +45,7 @@ def __init__(self, hid_device, numeric): self.device = hid.Device(path=hid_device['path']) self.current_line = '' - cli.log.info('Listening to %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', hid_device) + cli.log.info('Console Connected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', hid_device) def read(self, size, encoding='ascii', timeout=1): """Read size bytes from the device. @@ -53,7 +69,7 @@ def run_forever(self): message = {**self.hid_device, 'text': self.read_line()} identifier = (int2hex(message['vendor_id']), int2hex(message['product_id'])) if self.numeric else (message['manufacturer_string'], message['product_string']) message['identifier'] = ':'.join(identifier) - message['ts'] = '{fg_green}%s{fg_reset} ' % (strftime(cli.config.general.datetime_fmt),) if cli.args.timestamp else '' + message['ts'] = '{style_dim}{fg_green}%s{style_reset_all} ' % (strftime(cli.config.general.datetime_fmt),) if cli.args.timestamp else '' cli.echo('%(ts)s%(color)s%(identifier)s:%(index)d{style_reset_all}: %(text)s' % message) @@ -72,15 +88,16 @@ def run_forever(self): """Process messages from our queue in a loop. """ live_devices = {} + live_bootloaders = {} while True: try: for device in list(live_devices): if not live_devices[device]['thread'].is_alive(): - cli.log.info('Disconnected from %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', live_devices[device]) + cli.log.info('Console Disconnected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', live_devices[device]) del live_devices[device] - for device in self.find_devices(): + for device in self.find_devices(hid.enumerate()): if device['path'] not in live_devices: device['color'] = LOG_COLOR['colors'][LOG_COLOR['next']] LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) @@ -90,11 +107,33 @@ def run_forever(self): device['thread'].start() + for device in self.find_bootloaders(hid.enumerate()): + if device['path'] in live_bootloaders: + live_bootloaders[device['path']]['found'] = True + else: + name = KNOWN_BOOTLOADERS[(int2hex(device['vendor_id']), int2hex(device['product_id']))] + cli.log.info('Bootloader Connected: {fg_yellow}%s', name) + device['found'] = True + live_bootloaders[device['path']] = device + + for device in list(live_bootloaders): + if live_bootloaders[device]['found']: + live_bootloaders[device]['found'] = False + else: + name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device]['vendor_id']), int2hex(live_bootloaders[device]['product_id']))] + cli.log.info('Bootloader Disconnected: %s', name) + del live_bootloaders[device] + sleep(1) except KeyboardInterrupt: break + def is_bootloader(self, hid_device): + """Returns true if the device in question matches a known bootloader vid/pid. + """ + return (int2hex(hid_device['vendor_id']), int2hex(hid_device['product_id'])) in KNOWN_BOOTLOADERS + def is_console_hid(self, hid_device): """Returns true when the usage page indicates it's a teensy-style console. """ @@ -127,10 +166,14 @@ def find_devices_by_report(self, hid_devices): return devices - def find_devices(self): + def find_bootloaders(self, hid_devices): + """Returns a list of available bootloader devices. + """ + return list(filter(self.is_bootloader, hid_devices)) + + def find_devices(self, hid_devices): """Returns a list of available teensy-style consoles. """ - hid_devices = hid.enumerate() devices = list(filter(self.is_console_hid, hid_devices)) if not devices: @@ -160,12 +203,23 @@ def int2hex(number): def list_devices(device_finder): - cli.log.info('Available devices:') + hid_devices = hid.enumerate() + devices = device_finder.find_devices(hid_devices) + + if devices: + cli.log.info('Available devices:') + for dev in devices: + color = LOG_COLOR['colors'][LOG_COLOR['next']] + LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) + cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) + + bootloaders = device_finder.find_bootloaders(hid_devices) + + if bootloaders: + cli.log.info('Available Bootloaders:') - for dev in device_finder.find_devices(): - color = LOG_COLOR['colors'][LOG_COLOR['next']] - LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) - cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) + for dev in bootloaders: + cli.log.info("\t%s:%s\t%s", int2hex(dev['vendor_id']), int2hex(dev['product_id']), KNOWN_BOOTLOADERS[(int2hex(dev['vendor_id']), int2hex(dev['product_id']))]) @cli.argument('-d', '--device', help='device to select - uses format :[:].') From 3387ae6bbb68dd323fd5575f6786a59a82f135c3 Mon Sep 17 00:00:00 2001 From: Zach White Date: Mon, 3 May 2021 16:38:25 -0700 Subject: [PATCH 21/32] tweak the color for bootloaders --- lib/python/qmk/cli/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 725404a2d805..47425d314d12 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -112,7 +112,7 @@ def run_forever(self): live_bootloaders[device['path']]['found'] = True else: name = KNOWN_BOOTLOADERS[(int2hex(device['vendor_id']), int2hex(device['product_id']))] - cli.log.info('Bootloader Connected: {fg_yellow}%s', name) + cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name) device['found'] = True live_bootloaders[device['path']] = device From dfeb0f4133107b72ad4cd6b54c34186960c071b9 Mon Sep 17 00:00:00 2001 From: Zach White Date: Tue, 4 May 2021 11:04:30 -0700 Subject: [PATCH 22/32] Align bootloader disconnect with connect color --- lib/python/qmk/cli/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 47425d314d12..5d5672ec7ecc 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -121,7 +121,7 @@ def run_forever(self): live_bootloaders[device]['found'] = False else: name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device]['vendor_id']), int2hex(live_bootloaders[device]['product_id']))] - cli.log.info('Bootloader Disconnected: %s', name) + cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name) del live_bootloaders[device] sleep(1) From 56eafebbf5e9cc9a995d019d96fe1591da4c4397 Mon Sep 17 00:00:00 2001 From: Zach White Date: Fri, 7 May 2021 09:58:38 -0700 Subject: [PATCH 23/32] add support for showing all bootloaders --- lib/python/qmk/cli/console.py | 56 +++++++++++++++++++++-------------- requirements-dev.txt | 2 ++ requirements.txt | 1 - 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 5d5672ec7ecc..642a8a4c9e3a 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -2,11 +2,13 @@ cli implementation of https://www.pjrc.com/teensy/hid_listen.html """ -import hid from pathlib import Path from threading import Thread from time import sleep, strftime +import hid +import usb.core + from milc import cli LOG_COLOR = { @@ -97,34 +99,40 @@ def run_forever(self): cli.log.info('Console Disconnected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', live_devices[device]) del live_devices[device] - for device in self.find_devices(hid.enumerate()): + for device in self.find_devices(): if device['path'] not in live_devices: device['color'] = LOG_COLOR['colors'][LOG_COLOR['next']] LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) live_devices[device['path']] = device - monitor = MonitorDevice(device, self.numeric) - device['thread'] = Thread(target=monitor.run_forever, daemon=True) - device['thread'].start() + try: + monitor = MonitorDevice(device, self.numeric) + device['thread'] = Thread(target=monitor.run_forever, daemon=True) - for device in self.find_bootloaders(hid.enumerate()): - if device['path'] in live_bootloaders: - live_bootloaders[device['path']]['found'] = True + device['thread'].start() + except Exception as e: + cli.log.error("Could not connect to %s%s %s{style_reset_all} (%s:%04X:%04X:%d): %s: %s", device['color'], device['manufacturer_string'], device['product_string'], device['color'], device['vendor_id'], device['product_id'], device['index'], e.__class__.__name__, e) + cli.log.exception() + del live_devices[device['path']] + + for device in self.find_bootloaders(): + if device.address in live_bootloaders: + live_bootloaders[device.address].found = True else: - name = KNOWN_BOOTLOADERS[(int2hex(device['vendor_id']), int2hex(device['product_id']))] + name = KNOWN_BOOTLOADERS[(int2hex(device.idVendor), int2hex(device.idProduct))] cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name) - device['found'] = True - live_bootloaders[device['path']] = device + device.found = True + live_bootloaders[device.address] = device for device in list(live_bootloaders): - if live_bootloaders[device]['found']: - live_bootloaders[device]['found'] = False + if live_bootloaders[device].found: + live_bootloaders[device].found = False else: - name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device]['vendor_id']), int2hex(live_bootloaders[device]['product_id']))] + name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device].idVendor), int2hex(live_bootloaders[device].idProduct))] cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name) del live_bootloaders[device] - sleep(1) + sleep(.1) except KeyboardInterrupt: break @@ -132,7 +140,7 @@ def run_forever(self): def is_bootloader(self, hid_device): """Returns true if the device in question matches a known bootloader vid/pid. """ - return (int2hex(hid_device['vendor_id']), int2hex(hid_device['product_id'])) in KNOWN_BOOTLOADERS + return (int2hex(hid_device.idVendor), int2hex(hid_device.idProduct)) in KNOWN_BOOTLOADERS def is_console_hid(self, hid_device): """Returns true when the usage page indicates it's a teensy-style console. @@ -166,14 +174,15 @@ def find_devices_by_report(self, hid_devices): return devices - def find_bootloaders(self, hid_devices): + def find_bootloaders(self): """Returns a list of available bootloader devices. """ - return list(filter(self.is_bootloader, hid_devices)) + return list(filter(self.is_bootloader, usb.core.find(find_all=True))) - def find_devices(self, hid_devices): + def find_devices(self): """Returns a list of available teensy-style consoles. """ + hid_devices = hid.enumerate() devices = list(filter(self.is_console_hid, hid_devices)) if not devices: @@ -203,8 +212,9 @@ def int2hex(number): def list_devices(device_finder): - hid_devices = hid.enumerate() - devices = device_finder.find_devices(hid_devices) + """Show the user a nicely formatted list of devices. + """ + devices = device_finder.find_devices() if devices: cli.log.info('Available devices:') @@ -213,13 +223,13 @@ def list_devices(device_finder): LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) - bootloaders = device_finder.find_bootloaders(hid_devices) + bootloaders = device_finder.find_bootloaders() if bootloaders: cli.log.info('Available Bootloaders:') for dev in bootloaders: - cli.log.info("\t%s:%s\t%s", int2hex(dev['vendor_id']), int2hex(dev['product_id']), KNOWN_BOOTLOADERS[(int2hex(dev['vendor_id']), int2hex(dev['product_id']))]) + cli.log.info("\t%s:%s\t%s", int2hex(dev.idVendor), int2hex(dev.idProduct), KNOWN_BOOTLOADERS[(int2hex(dev.idVendor), int2hex(dev.idProduct))]) @cli.argument('-d', '--device', help='device to select - uses format :[:].') diff --git a/requirements-dev.txt b/requirements-dev.txt index 1db3b6d73315..12d570e70c6b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,5 +4,7 @@ # Python development requirements nose2 flake8 +hid pep8-naming +pyusb yapf diff --git a/requirements.txt b/requirements.txt index a5b5800613a3..8553e2c3f02d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ appdirs argcomplete colorama dotty-dict -hid hjson jsonschema>=3 milc>=1.3.0 From 4890690eefcd6bfa087fe90e10cfa662c2aa0f27 Mon Sep 17 00:00:00 2001 From: Zach White Date: Fri, 7 May 2021 10:02:07 -0700 Subject: [PATCH 24/32] fix the pyusb check --- bin/qmk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/qmk b/bin/qmk index a2af2951c9a1..4b5fd5bbce8b 100755 --- a/bin/qmk +++ b/bin/qmk @@ -33,6 +33,8 @@ def _check_modules(requirements): # Not every module is importable by its own name. if module['name'] == "pep8-naming": module['import'] = "pep8ext_naming" + elif module['name'] == 'pyusb': + module['import'] = 'usb.core' if not find_spec(module['import']): print('Could not find module %s!' % module['name']) From a4df6831904ec83e95780b7bb2f29682f6973d13 Mon Sep 17 00:00:00 2001 From: Zach White Date: Fri, 7 May 2021 10:38:55 -0700 Subject: [PATCH 25/32] tweaks --- lib/python/qmk/cli/console.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 642a8a4c9e3a..67e4ae499d0d 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -26,18 +26,18 @@ KNOWN_BOOTLOADERS = { # VID , PID ('16C0', '0478'): 'Teensy Halfkay Bootloader', - ('0483', 'DF11'): 'STM32 Bootloader', - ('314B', '0106'): 'APM32 Bootloader', + ('0483', 'DF11'): 'STM32 DFU', + ('314B', '0106'): 'APM32 DFU', ('1EAF', '0003'): 'STM32duino Bootloader', ('16C0', '05DC'): 'USBaspLoader', - ('1C11', 'B007'): 'Kiibohd DFU Bootloader', - ('03EB', '2FEF'): 'ATmega16U2 Bootloader', - ('03EB', '2FF0'): 'ATmega32U2 Bootloader', - ('03EB', '2FF3'): 'ATmega16U4 Bootloader', - ('03EB', '2FF4'): 'ATmega32U4 Bootloader', - ('03EB', '2FF9'): 'AT90USB64 Bootloader', - ('03EB', '2FFA'): 'AT90USB162 Bootloader', - ('03EB', '2FFB'): 'AT90USB128 Bootloader' + ('1C11', 'B007'): 'Kiibohd DFU', + ('03EB', '2FEF'): 'ATmega16U2 DFU', + ('03EB', '2FF0'): 'ATmega32U2 DFU', + ('03EB', '2FF3'): 'ATmega16U4 DFU', + ('03EB', '2FF4'): 'ATmega32U4 DFU', + ('03EB', '2FF9'): 'AT90USB64 DFU', + ('03EB', '2FFA'): 'AT90USB162 DFU', + ('03EB', '2FFB'): 'AT90USB128 DFU' } class MonitorDevice(object): @@ -117,16 +117,16 @@ def run_forever(self): for device in self.find_bootloaders(): if device.address in live_bootloaders: - live_bootloaders[device.address].found = True + live_bootloaders[device.address]._qmk_found = True else: name = KNOWN_BOOTLOADERS[(int2hex(device.idVendor), int2hex(device.idProduct))] cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name) - device.found = True + device._qmk_found = True live_bootloaders[device.address] = device for device in list(live_bootloaders): - if live_bootloaders[device].found: - live_bootloaders[device].found = False + if live_bootloaders[device]._qmk_found: + live_bootloaders[device]._qmk_found = False else: name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device].idVendor), int2hex(live_bootloaders[device].idProduct))] cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name) From 73fa98826c7a1c1ef9a5d8c9a8f920d38800ce00 Mon Sep 17 00:00:00 2001 From: Zach White Date: Fri, 7 May 2021 10:41:18 -0700 Subject: [PATCH 26/32] fix exception --- lib/python/qmk/cli/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 67e4ae499d0d..bb53a31eeecf 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -112,7 +112,7 @@ def run_forever(self): device['thread'].start() except Exception as e: cli.log.error("Could not connect to %s%s %s{style_reset_all} (%s:%04X:%04X:%d): %s: %s", device['color'], device['manufacturer_string'], device['product_string'], device['color'], device['vendor_id'], device['product_id'], device['index'], e.__class__.__name__, e) - cli.log.exception() + cli.log.exception(e) del live_devices[device['path']] for device in self.find_bootloaders(): From da40146cd153dbc2e127dfe9e4d7bf1e15e20aa5 Mon Sep 17 00:00:00 2001 From: Zach White Date: Fri, 7 May 2021 10:42:54 -0700 Subject: [PATCH 27/32] hide a stack trace behind -v --- lib/python/qmk/cli/console.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index bb53a31eeecf..f19d02c7ee60 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -112,7 +112,8 @@ def run_forever(self): device['thread'].start() except Exception as e: cli.log.error("Could not connect to %s%s %s{style_reset_all} (%s:%04X:%04X:%d): %s: %s", device['color'], device['manufacturer_string'], device['product_string'], device['color'], device['vendor_id'], device['product_id'], device['index'], e.__class__.__name__, e) - cli.log.exception(e) + if cli.config.general.verbose: + cli.log.exception(e) del live_devices[device['path']] for device in self.find_bootloaders(): From c42e01041da2665d2b175d4add34139b372fafd1 Mon Sep 17 00:00:00 2001 From: Zach White Date: Fri, 7 May 2021 10:49:28 -0700 Subject: [PATCH 28/32] add --no-bootloaders option --- lib/python/qmk/cli/console.py | 47 +++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index f19d02c7ee60..8d21140d79c9 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -116,22 +116,23 @@ def run_forever(self): cli.log.exception(e) del live_devices[device['path']] - for device in self.find_bootloaders(): - if device.address in live_bootloaders: - live_bootloaders[device.address]._qmk_found = True - else: - name = KNOWN_BOOTLOADERS[(int2hex(device.idVendor), int2hex(device.idProduct))] - cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name) - device._qmk_found = True - live_bootloaders[device.address] = device - - for device in list(live_bootloaders): - if live_bootloaders[device]._qmk_found: - live_bootloaders[device]._qmk_found = False - else: - name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device].idVendor), int2hex(live_bootloaders[device].idProduct))] - cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name) - del live_bootloaders[device] + if cli.args.bootloaders: + for device in self.find_bootloaders(): + if device.address in live_bootloaders: + live_bootloaders[device.address]._qmk_found = True + else: + name = KNOWN_BOOTLOADERS[(int2hex(device.idVendor), int2hex(device.idProduct))] + cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name) + device._qmk_found = True + live_bootloaders[device.address] = device + + for device in list(live_bootloaders): + if live_bootloaders[device]._qmk_found: + live_bootloaders[device]._qmk_found = False + else: + name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device].idVendor), int2hex(live_bootloaders[device].idProduct))] + cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name) + del live_bootloaders[device] sleep(.1) @@ -224,16 +225,18 @@ def list_devices(device_finder): LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) - bootloaders = device_finder.find_bootloaders() + if cli.args.bootloaders: + bootloaders = device_finder.find_bootloaders() - if bootloaders: - cli.log.info('Available Bootloaders:') + if bootloaders: + cli.log.info('Available Bootloaders:') - for dev in bootloaders: - cli.log.info("\t%s:%s\t%s", int2hex(dev.idVendor), int2hex(dev.idProduct), KNOWN_BOOTLOADERS[(int2hex(dev.idVendor), int2hex(dev.idProduct))]) + for dev in bootloaders: + cli.log.info("\t%s:%s\t%s", int2hex(dev.idVendor), int2hex(dev.idProduct), KNOWN_BOOTLOADERS[(int2hex(dev.idVendor), int2hex(dev.idProduct))]) -@cli.argument('-d', '--device', help='device to select - uses format :[:].') +@cli.argument('--bootloaders', arg_only=True, default=True, action='store_boolean', help='displaying bootloaders.') +@cli.argument('-d', '--device', help='Device to select - uses format :[:].') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') @cli.argument('-n', '--numeric', arg_only=True, action='store_true', help='Show VID/PID instead of names.') @cli.argument('-t', '--timestamp', arg_only=True, action='store_true', help='Print the timestamp for received messages as well.') From d30fc7cae21d98b1374ce0dd029ba930bba68e0b Mon Sep 17 00:00:00 2001 From: Zach White Date: Fri, 7 May 2021 10:59:07 -0700 Subject: [PATCH 29/32] add documentation for qmk console --- docs/cli_commands.md | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 6498b28b883a..e484152a1f74 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -107,6 +107,54 @@ This command lets you configure the behavior of QMK. For the full `qmk config` d qmk config [-ro] [config_token1] [config_token2] [...] [config_tokenN] ``` +## `qmk console` + +This command lets you connect to keyboard consoles to get debugging messages. It only works if your keyboard firmware has been compiled with `CONSOLE_ENABLED=yes`. + +**Usage**: + +``` +qmk console [-d :[:]] [-l] [-n] [-t] [-w ] +``` + +**Examples**: + +Connect to all available keyboards and show their console messages: + +``` +qmk console +``` + +List all devices: + +``` +qmk console -l +``` + +Show only messages from clueboard/66/rev3 keyboards: + +``` +qmk console -d C1ED:2370 +``` + +Show only messages from the second clueboard/66/rev3: + +``` +qmk console -d C1ED:2370:2 +``` + +Show timestamps and VID:PID instead of names: + +``` +qmk console -n -t +``` + +Disable bootloader messages: + +``` +qmk console --no-bootloaders +``` + ## `qmk doctor` This command examines your environment and alerts you to potential build or flash problems. It can fix many of them if you want it to. From 273f7ff5785e6f658489508960e5f3e12ff323ca Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 8 May 2021 08:42:16 -0700 Subject: [PATCH 30/32] Apply suggestions from code review Co-authored-by: Ryan --- util/install/arch.sh | 4 ++-- util/install/debian.sh | 6 +++--- util/install/fedora.sh | 6 +++--- util/install/msys2.sh | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/util/install/arch.sh b/util/install/arch.sh index 9e16074a6ad2..eac4ad64eff0 100755 --- a/util/install/arch.sh +++ b/util/install/arch.sh @@ -5,8 +5,8 @@ _qmk_install() { sudo pacman --needed --noconfirm -S \ base-devel clang diffutils gcc git unzip wget zip python-pip \ - avr-binutils arm-none-eabi-binutils arm-none-eabi-gcc \ - arm-none-eabi-newlib avrdude dfu-programmer dfu-util + avr-binutils arm-none-eabi-binutils arm-none-eabi-gcc \ + arm-none-eabi-newlib avrdude dfu-programmer dfu-util sudo pacman --needed --noconfirm -U https://archive.archlinux.org/packages/a/avr-gcc/avr-gcc-8.3.0-1-x86_64.pkg.tar.xz sudo pacman --needed --noconfirm -S avr-libc # Must be installed after the above, or it will bring in the latest avr-gcc instead diff --git a/util/install/debian.sh b/util/install/debian.sh index d9e1b6c784d2..ef87c41b517d 100755 --- a/util/install/debian.sh +++ b/util/install/debian.sh @@ -13,9 +13,9 @@ _qmk_install() { sudo apt-get -yq install \ build-essential clang-format diffutils gcc git unzip wget zip \ - python3-pip binutils-avr gcc-avr avr-libc binutils-arm-none-eabi \ - gcc-arm-none-eabi libnewlib-arm-none-eabi avrdude dfu-programmer \ - dfu-util teensy-loader-cli libhidapi-hidraw0 + python3-pip binutils-avr gcc-avr avr-libc binutils-arm-none-eabi \ + gcc-arm-none-eabi libnewlib-arm-none-eabi avrdude dfu-programmer \ + dfu-util teensy-loader-cli libhidapi-hidraw0 python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt } diff --git a/util/install/fedora.sh b/util/install/fedora.sh index f598270cacd3..10fc7c8ad826 100755 --- a/util/install/fedora.sh +++ b/util/install/fedora.sh @@ -6,9 +6,9 @@ _qmk_install() { # TODO: Check whether devel/headers packages are really needed sudo dnf -y install \ clang diffutils git gcc glibc-headers kernel-devel kernel-headers \ - make unzip wget zip python3 avr-binutils avr-gcc avr-libc \ - arm-none-eabi-binutils-cs arm-none-eabi-gcc-cs arm-none-eabi-newlib \ - avrdude dfu-programmer dfu-util hidapi + make unzip wget zip python3 avr-binutils avr-gcc avr-libc \ + arm-none-eabi-binutils-cs arm-none-eabi-gcc-cs arm-none-eabi-newlib \ + avrdude dfu-programmer dfu-util hidapi python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt } diff --git a/util/install/msys2.sh b/util/install/msys2.sh index 2fd5ba4277b5..9b8343aed02c 100755 --- a/util/install/msys2.sh +++ b/util/install/msys2.sh @@ -10,9 +10,9 @@ _qmk_install() { pacman --needed --noconfirm --disable-download-timeout -S pactoys-git pacboy sync --needed --noconfirm --disable-download-timeout \ base-devel: toolchain:x clang:x git: unzip: python3-pip:x \ - avr-binutils:x avr-gcc:x avr-libc:x arm-none-eabi-binutils:x \ - arm-none-eabi-gcc:x arm-none-eabi-newlib:x avrdude:x bootloadhid:x \ - dfu-programmer:x dfu-util:x teensy-loader-cli:x hidapi:x + avr-binutils:x avr-gcc:x avr-libc:x arm-none-eabi-binutils:x \ + arm-none-eabi-gcc:x arm-none-eabi-newlib:x avrdude:x bootloadhid:x \ + dfu-programmer:x dfu-util:x teensy-loader-cli:x hidapi:x _qmk_install_drivers From 3143d1363129e8139178720d5d282709f7f4d600 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 8 May 2021 09:00:03 -0700 Subject: [PATCH 31/32] pyformat --- lib/python/qmk/cli/console.py | 37 +++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 8d21140d79c9..877af93d47f5 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -19,27 +19,28 @@ '{fg_green}', '{fg_magenta}', '{fg_red}', - '{fg_yellow}' - ] + '{fg_yellow}', + ], } KNOWN_BOOTLOADERS = { - # VID , PID - ('16C0', '0478'): 'Teensy Halfkay Bootloader', - ('0483', 'DF11'): 'STM32 DFU', - ('314B', '0106'): 'APM32 DFU', - ('1EAF', '0003'): 'STM32duino Bootloader', - ('16C0', '05DC'): 'USBaspLoader', - ('1C11', 'B007'): 'Kiibohd DFU', - ('03EB', '2FEF'): 'ATmega16U2 DFU', - ('03EB', '2FF0'): 'ATmega32U2 DFU', - ('03EB', '2FF3'): 'ATmega16U4 DFU', - ('03EB', '2FF4'): 'ATmega32U4 DFU', - ('03EB', '2FF9'): 'AT90USB64 DFU', - ('03EB', '2FFA'): 'AT90USB162 DFU', - ('03EB', '2FFB'): 'AT90USB128 DFU' + # VID , PID + ('16C0', '0478'): 'Teensy Halfkay Bootloader', + ('0483', 'DF11'): 'STM32 DFU', + ('314B', '0106'): 'APM32 DFU', + ('1EAF', '0003'): 'STM32duino Bootloader', + ('16C0', '05DC'): 'USBaspLoader', + ('1C11', 'B007'): 'Kiibohd DFU', + ('03EB', '2FEF'): 'ATmega16U2 DFU', + ('03EB', '2FF0'): 'ATmega32U2 DFU', + ('03EB', '2FF3'): 'ATmega16U4 DFU', + ('03EB', '2FF4'): 'ATmega32U4 DFU', + ('03EB', '2FF9'): 'AT90USB64 DFU', + ('03EB', '2FFA'): 'AT90USB162 DFU', + ('03EB', '2FFB'): 'AT90USB128 DFU' } + class MonitorDevice(object): def __init__(self, hid_device, numeric): self.hid_device = hid_device @@ -111,7 +112,9 @@ def run_forever(self): device['thread'].start() except Exception as e: - cli.log.error("Could not connect to %s%s %s{style_reset_all} (%s:%04X:%04X:%d): %s: %s", device['color'], device['manufacturer_string'], device['product_string'], device['color'], device['vendor_id'], device['product_id'], device['index'], e.__class__.__name__, e) + device['e'] = e + device['e_name'] = e.__class__.__name__ + cli.log.error("Could not connect to %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s:%(vendor_id)04X:%(product_id)04X:%(index)d): %(e_name)s: %(e)s", device) if cli.config.general.verbose: cli.log.exception(e) del live_devices[device['path']] From ab0d7a37841651069fc0e6cddffbad920d567cd0 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 8 May 2021 12:41:10 -0700 Subject: [PATCH 32/32] clean up and flesh out KNOWN_BOOTLOADERS --- lib/python/qmk/cli/console.py | 40 +++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py index 877af93d47f5..45ff0c8bee11 100644 --- a/lib/python/qmk/cli/console.py +++ b/lib/python/qmk/cli/console.py @@ -25,19 +25,33 @@ KNOWN_BOOTLOADERS = { # VID , PID - ('16C0', '0478'): 'Teensy Halfkay Bootloader', - ('0483', 'DF11'): 'STM32 DFU', - ('314B', '0106'): 'APM32 DFU', - ('1EAF', '0003'): 'STM32duino Bootloader', - ('16C0', '05DC'): 'USBaspLoader', - ('1C11', 'B007'): 'Kiibohd DFU', - ('03EB', '2FEF'): 'ATmega16U2 DFU', - ('03EB', '2FF0'): 'ATmega32U2 DFU', - ('03EB', '2FF3'): 'ATmega16U4 DFU', - ('03EB', '2FF4'): 'ATmega32U4 DFU', - ('03EB', '2FF9'): 'AT90USB64 DFU', - ('03EB', '2FFA'): 'AT90USB162 DFU', - ('03EB', '2FFB'): 'AT90USB128 DFU' + ('03EB', '2FEF'): 'atmel-dfu: ATmega16U2', + ('03EB', '2FF0'): 'atmel-dfu: ATmega32U2', + ('03EB', '2FF3'): 'atmel-dfu: ATmega16U4', + ('03EB', '2FF4'): 'atmel-dfu: ATmega32U4', + ('03EB', '2FF9'): 'atmel-dfu: AT90USB64', + ('03EB', '2FFA'): 'atmel-dfu: AT90USB162', + ('03EB', '2FFB'): 'atmel-dfu: AT90USB128', + ('03EB', '6124'): 'Microchip SAM-BA', + ('0483', 'DF11'): 'stm32-dfu: STM32 BOOTLOADER', + ('16C0', '05DC'): 'USBasp: USBaspLoader', + ('16C0', '05DF'): 'bootloadHID: HIDBoot', + ('16C0', '0478'): 'halfkay: Teensy Halfkay', + ('1B4F', '9203'): 'caterina: Pro Micro 3.3V', + ('1B4F', '9205'): 'caterina: Pro Micro 5V', + ('1B4F', '9207'): 'caterina: LilyPadUSB', + ('1C11', 'B007'): 'kiibohd: Kiibohd DFU Bootloader', + ('1EAF', '0003'): 'stm32duino: Maple 003', + ('1FFB', '0101'): 'caterina: Polou A-Star 32U4 Bootloader', + ('2341', '0036'): 'caterina: Arduino Leonardo', + ('2341', '0037'): 'caterina: Arduino Micro', + ('239A', '000C'): 'caterina: Adafruit Feather 32U4', + ('239A', '000D'): 'caterina: Adafruit ItsyBitsy 32U4 3v', + ('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v', + ('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v', + ('2A03', '0036'): 'caterina: Arduino Leonardo', + ('2A03', '0037'): 'caterina: Arduino Micro', + ('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode' }