Overview
This page covers how to use elcap in automated manufacturing and production-line environments. The focus is on scripting elcap reliably without any user interaction — no prompts, no update dialogs, and machine-readable output that your scripts can parse.
Key points for scripting:
- Always pass --json to suppress interactive prompts and receive structured output
- elcap never checks for updates or displays update prompts when --json is used
- All output goes to stdout; errors are reported in the JSON envelope rather than thrown to stderr
Using the –json Flag
The --json flag is the recommended way to integrate elcap into automated scripts and factory tooling.
When --json is passed:
- No update prompts — elcap skips its self-update check entirely
- No interactive prompts — all commands complete without waiting for user input (see command-specific notes below)
- Structured output — every command prints a single JSON object to stdout that your script can parse
Response Envelope
Every --json response uses a consistent envelope:
Success:
{
"error": null,
"result": { }
}
Error:
{
"error": {
"msg": " ",
"exit_code": 1,
"type": " ",
"remediations": [ ],
"log_path": " "
}
}
Check error first. If it is null, the command succeeded and the payload is in result. If error is non-null, msg contains the human-readable description, type identifies the error category, remediations lists suggested fixes, and log_path points to the full log file for that invocation. A non-zero process exit code also indicates failure.
Commands That Support –json
The following commands support the --json flag. Call --help without the --json flag for usage details and options for each command.
| Command | Description |
| elcap --json flash | Flash a hex file to a connected device |
| elcap --json tokens list | List available tokens for a chip target |
| elcap --json tokens write | Write one or more tokens to a connected device |
| elcap --json tokens read | Read one or more tokens from a connected device |
| elcap --json tokens erase | Erase one or more tokens on a connected device |
| elcap --json tokens patch | Patch token data into a hex file (no device required) |
| elcap --json device discover | List all connected programmers and devices |
| elcap --json device info | Read device information (e.g. EUI64) |
| elcap --json device erase | Erase device flash |
| elcap --json device update-firmware | Update the programmer's firmware |
| elcap --json device rename | Assign an alias to a connected device |
| elcap --json device lock | Enable flash readout protection on the target MCU |
| elcap --json device unlock | Temporarily disable flash readout protection on the target MCU |
Device Selection
All hardware commands (flash, tokens write, tokens read, tokens erase, device info, etc.) accept the following mutually exclusive options for identifying which device to target:
| Option | Description |
| --usb <serial> | Target a specific device by its USB serial number |
| --ip <address> | Target a network-connected device by IP address |
| --alias <name> | Target a device by a previously assigned alias |
If only one device is connected, these options may be omitted and elcap will select it automatically. When multiple devices are connected and no selector is given with json commands requiring a device, elcap will error — so always specify a device selector when scripting to avoid blocking on input.
Use elcap --json device discover to enumerate connected devices and extract their serial numbers, IP addresses, or aliases before running further commands.
Example:
elcap --json device discover
{
"error": null,
"result": {
"devices": [
{
"connection": "USB",
"serial": "123456789",
"name": "CZ20-1",
"COM": "COM3",
"max_speed_khz": "4000"
}
]
}
}
Example Script: Flash and Program MFG Tokens
The following example shows a complete factory programming flow in Bash. It flashes a firmware image and then writes a set of manufacturing tokens, checking for errors at each step.
#!/usr/bin/env bash
set -euo pipefail
DEVICE_USB="123456789" # USB serial number from device discover
TARGET="T32CZ20B" # Chip part number
FIRMWARE="build\\T32CZ20.Debug\\app\\my_project_dkncz20_usb_t32cz20_signed_combined.hex" # Pre-built firmware image
MFG_TOKENS="mfg_tokens.json" # Token data file (see format below)
# 1. Flash the firmware image
echo "Flashing firmware..."
FLASH_RESULT=$(elcap --json \
flash \
--usb "$DEVICE_USB" \
--target "$TARGET" \
--file "$FIRMWARE")
if [ "$(echo "$FLASH_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['error'])")" != "None" ]; then
echo "Flash failed: $FLASH_RESULT" >&2
exit 1
fi
echo "Flash succeeded."
# 2. Write MFG tokens from a JSON file
echo "Writing MFG tokens..."
TOKENS_RESULT=$(elcap --json \
tokens write \
--usb "$DEVICE_USB" \
--target "$TARGET" \
--file "$MFG_TOKENS")
if [ "$(echo "$TOKENS_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['error'])")" != "None" ]; then
echo "Token write failed: $TOKENS_RESULT" >&2
exit 1
fi
echo "Tokens written successfully."
Example mfg_tokens.json:
{
"TR_MFG_TOKEN_MFG_NAME": {
"value": "My Company",
"value_type": "ascii"
},
"TR_MFG_TOKEN_MODEL_NAME": {
"value": "My Product",
"value_type": "ascii"
},
"TR_MFG_TOKEN_CUSTOM_EUI": {
"value": "0x0011223344556677",
"value_type": "hex"
}
}
See the Tokens page for the full list of available MFG tokens and the complete token write file format.
Alternative: Pre-Patching Tokens Into a Hex File
If per-device token values are known before programming begins, tokens patch can embed them directly into the hex file. This avoids a second hardware connection step and is well-suited to workflows where a unique hex is prepared per device serial number.
# Patch a per-device EUI into a copy of the base firmware
elcap --json \
tokens patch \
--target "$TARGET" \
--name TR_MFG_TOKEN_CUSTOM_EUI \
--value "0x0011223344556677" \
--value-type hex \
firmware_base.hex \
"firmware_device_${DEVICE_USB}.hex"
# Flash the patched image
elcap --json \
flash \
--usb "$DEVICE_USB" \
--target "$TARGET" \
--file "firmware_device_${DEVICE_USB}.hex"
tokens patch operates entirely on files and requires no connected hardware, so it can run on a host system that prepares images in advance of the programming station.
Example: XTAL Calibration Workflow
Crystal (XTAL) frequency calibration is typically performed on the production line to correct for part-to-part variation and ensure the radio meets specification. Your calibration fixture measures the trim value; elcap then writes it to the TR_MFG_TOKEN_XTAL_TRIM token and reads it back to confirm.
The example below assumes a calibration fixture or script produces a 16-bit hex trim value (e.g. 0x00A3). Adapt the XTAL_TRIM assignment to however your tooling exposes the result.
#!/usr/bin/env bash
set -euo pipefail
DEVICE_USB="123456789" # USB serial number from device discover
TARGET="T32CZ20B"
# Step 1: Run your calibration fixture and capture the trim value ---
# Replace this line with however your fixture reports the result.
XTAL_TRIM="0x00A3"
# Step 2: Write the trim value to the device ---
echo "Writing XTAL trim: $XTAL_TRIM"
WRITE_RESULT=$(elcap --json \
tokens write \
--usb "$DEVICE_USB" \
--target "$TARGET" \
--name TR_MFG_TOKEN_XTAL_TRIM \
--value "$XTAL_TRIM" \
--value-type hex)
if [ "$(echo "$WRITE_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['error'])")" != "None" ]; then
echo "XTAL trim write failed: $WRITE_RESULT" >&2
exit 1
fi
# Step 3: Read back and verify ---
READ_RESULT=$(elcap --json \
tokens read \
--usb "$DEVICE_USB" \
--target "$TARGET")
WRITTEN=$(echo "$READ_RESULT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d['result']['data']['TR_MFG_TOKEN_XTAL_TRIM'])
")
echo "Trim value on device: $WRITTEN"
# Step 4: Run calibration fixture again to verify tolerance ---
# Re-run your calibration fixture with the new trim value applied. If the measured
# frequency error is within your acceptance tolerance, calibration is complete.
# If not, return to Step 1 with the updated trim value and repeat until the result
# is satisfactory.
If your workflow pre-patches the trim value into the firmware image rather than writing it live, use tokens patch instead. The --name, --value, and --value-type arguments are the same, but tokens patch takes the input hex file (and an optional output hex file) as positional arguments:
elcap --json \
tokens patch \
--target "$TARGET" \
--name TR_MFG_TOKEN_XTAL_TRIM \
--value "$XTAL_TRIM" \
--value-type hex \
firmware_base.hex \
"firmware_device_${DEVICE_USB}.hex"
If the output file is omitted, the input file is patched in place.
JSON Output Reference
flash
$ elcap -j flash --usb 123456789 --target T32CZ20B
{
"error": null,
"result": {
"success": true,
"file_flashed": "firmware_signed.hex",
"target": "T32CZ20B",
"serial_number": "123456789",
"com_port": "COM4",
"ip_address": null
}
}
tokens write
$ elcap -j tokens write --usb 123456789 --target T32CZ20B --name TR_MFG_TOKEN_MFG_NAME --value "My Company" --value-type ascii
{
"error": null,
"result": {
"success": true,
"type": "MFG",
"target": "T32CZ20B",
"tokens_written": "TR_MFG_TOKEN_MFG_NAME",
"message": "Successfully wrote MFG token(s)"
}
}
tokens read
$ elcap -j tokens read --usb 123456789 --target T32CZ20B
{
"error": null,
"result": {
"type": "MFG",
"target": "T32CZ20B",
"data": {
"TR_MFG_TOKEN_VERSION": "FFFF",
"TR_MFG_TOKEN_CUSTOM_EUI": "0011223344556677",
"TR_MFG_TOKEN_MFG_NAME": "4D7920436F6D70616E79FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_MODEL_NAME": "4D792050726F64756374FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_HW_VERSION": "FF",
"TR_MFG_TOKEN_MANUF_ID": "FFFF",
"TR_MFG_TOKEN_SERIAL_NUM": "FFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_XTAL_TRIM": "FFFF",
"TR_MFG_TOKEN_PHY_CONFIG": "FFFF",
"TR_MFG_TOKEN_CCA_THRESHOLD": "FFFF",
"TR_MFG_TOKEN_CBKE_DATA": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_INSTALLATION_CODE": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_DISTRIBUTED_KEY": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_SECURITY_CONFIG": "FFFF",
"TR_MFG_TOKEN_CBKE_283K1_DATA": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_NVM_CRYPTO_KEY": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_BOOTLOAD_AES_KEY": "4AA78BD2BE159D65C0B244ACD9FB33D6",
"TR_MFG_TOKEN_SECURE_BOOTLOADER_KEY": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_SIGNED_BOOTLOADER_KEY_X": "85B6E561B942A15C10D03422C02C7F0D5D421A34C50F732B177198A4109B414F",
"TR_MFG_TOKEN_SIGNED_BOOTLOADER_KEY_Y": "5A42AD67C8E66A1BD768383EC35FF0424A49D159098D1F5476ACF78E2CD49142",
"TR_MFG_TOKEN_SERIAL_BOOT_DELAY_SEC": "FF",
"TR_MFG_TOKEN_THREAD_JOIN_KEY": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_ZWAVE_COUNTRY_FREQ": "FF",
"TR_MFG_TOKEN_ZWAVE_INITIALIZED": "00",
"TR_MFG_TOKEN_ZWAVE_QR_CODE": "9001425381321711445389306395278346246124163255333230001016385007680220011220000300001002560803003",
"TR_MFG_TOKEN_ZWAVE_PUK": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"TR_MFG_TOKEN_ZWAVE_PRK": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
}
}
}
device lock
$ elcap -j device lock --usb 123456789 --target T32CZ20B
{
"error": null,
"result": {
"success": true
}
}
device unlock
Unlocking a T32CZ20B target requires the ADAC private key that was provisioned during lock. T32CM11C targets do not require a key.
$ elcap -j device unlock --usb 123456789 --target T32CZ20B --adac-private-key-path adac_private.pem
{
"error": null,
"result": {
"success": true
}
}