Skip to main content

pytactl

Python implementation of Test Automation Controller (TAC/Alpaca) for controlling Qualcomm debug boards. It uses config files and PSOC firmware from the original TAC (Alpaca) system.

Installation

Install the pytactl command on your system from PyPI with pipx:

pipx install pytactl

To install from a checkout of this repository instead:

pipx install .

This puts a single pytactl entry point on your PATH. Note that the hid dependency needs libhidapi package to be installed (required for Bughopper V2 boards).

Alternatively, for development in a virtualenv:

virtualenv -p python3 venv
. ./venv/bin/activate
pip install -e .

Run pytactl -h to see all available options.

System dependencies

pytactl uses hidapi for HID-based debug boards, including Bughopper V2. Install the native HIDAPI library before installing or running pytactl.

Ubuntu / Debian

sudo apt update
sudo apt install -y libhidapi-hidraw0 libhidapi-libusb0

Fedora

sudo dnf install -y hidapi

USB permissions

By default, USB devices are not accessible without root. Install the udev rules file to have access to the supported boards as an user:

sudo cp 60-pytactl.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger

Then make sure your user is in the plugdev group (log out and back in after):

sudo usermod -aG plugdev $USER

Configuration

Bughopper boards (V1 and V2) work out of the box. They are self-describing and need no config files.

All other debug boards (FTDI, PSOC, PIC32CX) are driven from a configuration file, and the configs ship with pytactl. They live in pytactl/tac_configs/, vendored from qcom-test-automation-controller and converted to the pinout format pytactl loads (see Config file format below). Nothing needs fetching before a board can be driven:

pytactl shell --serial <serial>

pytactl/tac_configs/README.md records the upstream commit the set was taken from.

Listing the configs pytactl finds

listconfigs shows which config directory pytactl uses (the installed set, the bundled one, or the one given with --tac-config-path), each debug board devicelist.json lists with its config file, platform id and USB descriptor, and any config file no board entry refers to:

pytactl listconfigs --log-level INFO

Every config a board entry refers to is loaded, and a missing or unreadable one is flagged. The command exits with status 1 when devicelist.json cannot be read, lists no boards, or refers to a missing or invalid config, so it can serve as a check that the configs are installed (for example in a distribution's package tests). Config scripts are not parsed; a script error is reported when a board is driven with that config.

Using a different config set

--tac-config-path <dir> points pytactl at another directory of configs; without it, the bundled set is used.

To use configs that are not in the bundled set — a newer checkout of qcom-test-automation-controller, configs for boards of your own, or a config edited in QTAC — convert their configurations/ directory and point pytactl at the result:

pytactl convertconfigs /path/to/qcom-test-automation-controller/configurations \
  --output /path/to/install
pytactl shell --tac-config-path /path/to/install --serial <serial>

Without --output the configs are converted in place, beside the originals. --dry-run reports what would be written without writing it, and --write-overlay also writes the UI half of each config (see below), producing the complete two-file layout rather than only the half pytactl uses.

Note: some configs in qcom-test-automation-controller currently have syntax issues, and the bundled set carries fixes upstream does not have (see scripts/known-config-differences.json); pick the ones that match your board.

installconfigs (deprecated)

pytactl installconfigs downloads every config file and devicelist.json from the config repository, converts them, and installs the result into a per-user data directory (resolved with platformdirs, e.g. ~/.local/share/pytactl on Linux). It is deprecated and will be removed in a future release:

  • the installed configs are raw upstream, without the fixes the bundled set carries;
  • once that directory is populated, every command not given --tac-config-path uses it instead of the bundled set, and keeps doing so after pytactl is upgraded.

pytactl logs a warning whenever it falls back to the installed set. Delete the directory to go back to the bundled configs, and use convertconfigs with --tac-config-path for a config set of your own. pytactl listconfigs shows which directory is in use.

Config file format

Upstream stores one config file per debug board. Historically that was a single combined .tcnf file holding the hardware pinout, the automation script and the Qt UI layout (tabs, buttons, labels, tooltips, grid cells). PR #54 splits that into two sibling files, so the hardware description can be reused outside the QTAC application:

File Contents
TAC_<CHIP>_<ID>.pinout.json Pins, bus map, script variables and the Alpaca script. Self-describing: "format": "tac-pinout".
TAC_<CHIP>_<ID>.tcnf UI layout only, linked to its pinout file through pinout_ref.

pytactl drives hardware and has no UI, so it loads the .pinout.json half and ignores the overlay. pytactl.tacconfig implements the same split (it reproduces upstream's own output for every config in that PR), which is what installconfigs and convertconfigs apply, so pytactl does not have to wait for the split to land upstream and will consume upstream's .pinout.json files unchanged once it does.

All three shapes are accepted wherever a single config file is given with --config-file-path: a .pinout.json, a split .tcnf (its pinout_ref sibling is loaded from the same directory), or a legacy combined .tcnf, which is converted in memory.

One thing does not survive the split unaided: enabled is a UI field, but several configs wire the same command name to two physical pins and disable one of them. The conversion therefore drops a disabled pin when an enabled pin claims the same command, so the command keeps driving the line it drove before.

Script indentation

A config's script is written in the "Alpaca" automation language: a def at column 0 and its body indented with a single tab, with no nested blocks.

def powerOn()
→   battery 1
→   delay 800
→   pkey 0

pytactl requires that tab. A statement indented with spaces — or with more than one tab — is rejected at load time with a ConfigScriptError naming the offending lines, rather than being quietly accepted: indentation is part of the config file format, and a config that does not follow it is a config to fix, not to work around.

installconfigs and convertconfigs re-indent scripts with tabs as they import them and log each file they touch, so configs that come through either command satisfy the rule and the set bundled with pytactl already does. Three upstream configs (TAC_FTDI_51, TAC_FTDI_52, TAC_FTDI_77) indent two lines each with spaces and are corrected on the way in.

This means a raw upstream .tcnf handed straight to --config-file-path may be rejected where the converted config loads. That is deliberate — convert the directory first:

pytactl convertconfigs /path/to/qcom-test-automation-controller/configurations \
  --output /path/to/install

If you are upgrading and a board that used to work now reports a ConfigScriptError, your installed config directory predates this rule: delete it to use the bundled configs, or run pytactl convertconfigs <dir> to normalise it in place.

Provenance annotation

installconfigs and convertconfigs stamp each config they write with where it came from, in a source object below the format envelope — the repository, the commit (a ref like main moves, so the SHA it resolved to at import time is what gets recorded), the directory within the repository, and the upstream file name:

{
  "$schema": "https://qualcomm.github.io/tac/schemas/pinout-1.0.json",
  "format": "tac-pinout",
  "schema_version": "1.0",
  "source": {
    "repository": "https://github.com/qualcomm/qcom-test-automation-controller.git",
    "commit": "757cc972c88e4a1098881b2bc73f3d53eac286be",
    "path": "configurations",
    "file": "TAC_FTDI_15.tcnf"
  },
  ...
}

convertconfigs reads this from the git checkout it is pointed at, and leaves an already annotated config alone, so re-converting an imported set in place does not restamp it with the wrong repository.

This is pytactl's one addition to the format: upstream's schemas/pinout-1.0.json sets "additionalProperties": false, so an annotated config does not validate against it unchanged. Pass --no-annotate to either subcommand for configs that must.

devicelist.json maps board hardware IDs to their config files, and must be present in the --tac-config-path directory for FTDI/PSOC boards. installconfigs and convertconfigs rewrite its configPath entries to the converted file names. Example entry for a PSOC board:

{
  "catalog": [
    {
      "platform_id": 17,
      "configPath": "TAC_PSOC_17.pinout.json"
    }
  ]
}

Finding your board's serial number

The --serial argument takes the USB serial number, not a device path. The easiest way to discover connected boards and their serial numbers is the list subcommand:

pytactl list

It prints every recognised debug board with its type, USB vendor/product ID, and serial number (read from udev, the same ID_SERIAL_SHORT value you pass to --serial):

Connected debug boards:
  Bughopper V1   vid:pid=0403:6015  serial=DP05DIAN
  PSOC           vid:pid=05c6:9302  serial=0123456789

Alternatively, find it manually with udevadm:

udevadm info /dev/ttyACM0 | grep ID_SERIAL_SHORT

Or using lsusb (replace VID:PID with 0403:6011 for FTDI or 05c6:9302 for PSOC):

lsusb -v -d VID:PID | grep iSerial

Using as a shell

Start the interactive shell with the shell subcommand:

pytactl shell --serial <ID_SERIAL_SHORT>

Optional arguments:

--tac-config-path <dir>   # use a config directory other than the bundled one (see Configuration)
--log-level DEBUG         # log verbosity (default: DEBUG)

Once started, the shell prompt accepts commands generated from your board's config script. The available commands depend on the config — not all boards define every command (e.g. newer configs may omit powerOn/powerOff). Typical commands:

Power control:

powerOn
powerOff
devicePowerOn
devicePowerOff
usbDevicePowerOn
usbDevicePowerOff

Boot modes:

bootToEDL
bootToFastboot
bootToUEFI
reset

GPIO pins (use with 1 to assert, 0 to deassert):

pkey 1      # press power key
pkey 0      # release power key
volup 1
voldn 1

Type help in the shell to list all commands available for your specific board.

Running a single command

Use the oneshot subcommand to run one command and exit, without entering the interactive shell. This is handy for scripting:

pytactl oneshot bootToEDL --serial <ID_SERIAL_SHORT>
pytactl oneshot reset --serial <ID_SERIAL_SHORT>

GPIO pin commands take an integer value (1 to assert, 0 to deassert):

pytactl oneshot pkey 1 --serial <ID_SERIAL_SHORT>
pytactl oneshot pkey 0 --serial <ID_SERIAL_SHORT>

The same commands available in the shell can be used here. An unknown command exits with an error listing the commands supported by your board.

Using as a service

pytactl service --serial <ID_SERIAL_SHORT_1> [<ID_SERIAL_SHORT_2> ...]

The REST API runs on http://localhost:5000. Example usage with curl:

# List connected boards
curl http://localhost:5000/

# List available quick methods (bootToEDL, powerOn, etc.)
curl http://localhost:5000/<boardid>/quick

# Power on/off
curl -X PUT http://localhost:5000/<boardid>/quick/powerOn
curl -X PUT http://localhost:5000/<boardid>/quick/powerOff

# Boot to EDL
curl -X PUT http://localhost:5000/<boardid>/quick/bootToEDL

# Boot to fastboot
curl -X PUT http://localhost:5000/<boardid>/quick/bootToFastboot

# Set a named pin
curl -X PUT "http://localhost:5000/<boardid>/command/reset?value=1"

# Set a raw pin (e.g., bus A, pin 0)
curl -X PUT "http://localhost:5000/<boardid>/pin/A0?value=1"

Note: REST API server runs in debug mode. Running with multiple concurrent threads may lead to unexpected behaviour.

License

pytactl is licensed under the BSD-3-clause License. See LICENSE for the full license text.

Metadata

Release files for pytactl 3.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytactl 3.2.0
File Size Uploaded Provenance
pytactl-3.2.0.tar.gz 78.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pytactl 3.2.0
File Interpreter ABI Platform
pytactl-3.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 194.2 kB

Release files / pytactl-3.2.0.tar.gz

Download URL pytactl-3.2.0.tar.gz
Size 78.0 kB
Tags Source
SHA-256 checksum
How to use checksums
cf54f516c7fbac986df545a9f549d1bafbceefd5161b1107439513812ab141bd
BLAKE2b-256 checksum
How to use checksums
930f37f4f6bbbdc71e69119a147ed622b65b82ddbd919db707b36b3d163e1382
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / pytactl-3.2.0-py3-none-any.whl

Download URL pytactl-3.2.0-py3-none-any.whl
Size 116.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
254bdf0684cdf752ac9bae66149847db62044e1fde9b2cbfd01ed28ef5473923
BLAKE2b-256 checksum
How to use checksums
0400ebd9094f22575c1bfa01585b4c6e0ee28dc33be0d6638a96fb32b0ab0717
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Security for pytactl 3.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pytactl-3.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page