Skip to content

Add JSON support for uhubctl - addresses all PR #575 feedback #575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "mkjson"]
path = mkjson
url = https://github.com/benroeder/mkjson.git
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am against using submodule for this. I would rather copy 2 tiny .c and .h files into uhubctl repo. Its ok to keep updating it manually if mkjson changes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mkjson has been archived, which why I forked and fixed it

17 changes: 11 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ PKG_CONFIG ?= pkg-config

CC ?= gcc
CFLAGS ?= -g -O0
CFLAGS += -Wall -Wextra -Wno-zero-length-array -std=c99 -pedantic
CFLAGS += -Wall -Wextra -Wno-zero-length-array -std=c99 -pedantic -I.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would not be needed if we keep mkjson local copy

GIT_VERSION := $(shell git describe --match "v[0-9]*" --abbrev=8 --dirty --tags | cut -c2-)
ifeq ($(GIT_VERSION),)
GIT_VERSION := $(shell cat VERSION)
Expand All @@ -37,17 +37,22 @@ else
endif

PROGRAM = uhubctl

.PHONY: all install clean
SOURCES = $(PROGRAM).c mkjson/mkjson.c
OBJECTS = $(SOURCES:.c=.o)

all: $(PROGRAM)

$(PROGRAM): $(PROGRAM).c
$(CC) $(CPPFLAGS) $(CFLAGS) [email protected] -o $@ $(LDFLAGS)
$(PROGRAM): $(OBJECTS)
$(CC) $(CPPFLAGS) $(CFLAGS) $(OBJECTS) -o $@ $(LDFLAGS)

%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

install:
$(INSTALL_DIR) $(DESTDIR)$(sbindir)
$(INSTALL_PROGRAM) $(PROGRAM) $(DESTDIR)$(sbindir)

clean:
$(RM) $(PROGRAM).o $(PROGRAM).dSYM $(PROGRAM)
$(RM) $(OBJECTS) $(PROGRAM).dSYM $(PROGRAM)

.PHONY: all install clean
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,12 @@ To fetch uhubctl source and compile it:

git clone https://github.com/mvp/uhubctl
cd uhubctl
git submodule init
git submodule update
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets not use submodule. But if we did, perhaps cleaner approach is

git clone --recurse-submodules https://github.com/mvp/uhubctl

make

Note: uhubctl uses a forked version of mkjson for JSON output generation. The fork includes a critical bug fix for 64-bit integer handling. See [mkjson fork](https://github.com/benroeder/mkjson) for details.

This should generate `uhubctl` binary.
You can install it in your system as `/usr/sbin/uhubctl` using:

Expand Down Expand Up @@ -239,6 +243,14 @@ are port numbers for all hubs in chain, starting from root hub for a given USB b
This address is semi-stable - it will not change if you unplug/replug (or turn off/on)
USB device into the same physical USB port (this method is also used in Linux kernel).

To get the status in machine-readable JSON format, use `-j` option:

uhubctl -j

This will output status of all hubs and ports in JSON format, making it easy to integrate
uhubctl with other tools and scripts. The JSON output includes all the same information
as the text output, including hub info, port status, connected devices, and their properties.


Linux USB permissions
=====================
Expand Down
149 changes: 149 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# uhubctl JSON Output Examples

This directory contains examples of how to use uhubctl's JSON output feature (`-j` flag) to programmatically work with USB hub information.

## Requirements

- uhubctl compiled with JSON support
- jq (command-line JSON processor) - install with:
- macOS: `brew install jq`
- Ubuntu/Debian: `sudo apt-get install jq`
- RedHat/Fedora: `sudo yum install jq`

## Running the Examples

```bash
./json_usage_examples.sh
```

## JSON Output Format

The JSON output provides complete information about all USB hubs and connected devices:

```json
{
"hubs": [
{
"location": "3-1",
"description": "Hub description string",
"hub_info": {
"vid": "0x05e3",
"pid": "0x0608",
"usb_version": "2.00",
"nports": 4,
"ppps": "ppps"
},
"ports": [
{
"port": 1,
"status": "0x0103",
"flags": {
"connection": true,
"enable": true,
"power": true
},
"human_readable": {
"connection": "Device is connected",
"enable": "Port is enabled",
"power": "Port power is enabled"
},
"speed": "USB2.0 High Speed 480Mbps",
"speed_bps": 480000000,
"vid": "0x0781",
"pid": "0x5567",
"vendor": "SanDisk",
"product": "Cruzer Blade",
"device_class": 0,
"class_name": "Mass Storage",
"usb_version": "2.00",
"device_version": "1.00",
"nconfigs": 1,
"serial": "4C530001234567891234",
"is_mass_storage": true,
"interfaces": [...],
"description": "0781:5567 SanDisk Cruzer Blade 4C530001234567891234"
}
]
}
]
}
```

## Common Use Cases

### 1. Find Device by Serial Number
```bash
SERIAL="4C530001234567891234"
uhubctl -j | jq -r --arg s "$SERIAL" '.hubs[] | . as $h | .ports[] | select(.serial == $s) | "uhubctl -l \($h.location) -p \(.port) -a cycle"'
```

### 2. List All Mass Storage Devices
```bash
uhubctl -j | jq -r '.hubs[].ports[] | select(.is_mass_storage == true) | .description'
```

### 3. Find Empty Ports
```bash
uhubctl -j | jq -r '.hubs[] | . as $h | .ports[] | select(.vid == null) | "Hub \($h.location) Port \(.port)"'
```

### 4. Generate Control Commands for Device Type
```bash
# Power off all FTDI devices
uhubctl -j | jq -r '.hubs[] | . as $h | .ports[] | select(.vendor == "FTDI") | "uhubctl -l \($h.location) -p \(.port) -a off"' | bash
```

### 5. Monitor for Device Changes
```bash
# Save baseline
uhubctl -j > baseline.json

# Later, check what changed
uhubctl -j | jq -r --argjson old "$(cat baseline.json)" '
. as $new |
$old.hubs[].ports[] as $op |
$new.hubs[] | . as $h |
.ports[] |
select(.port == $op.port and $h.location == $op.hub_location and .vid != $op.vid) |
"Change at \($h.location):\(.port)"'
```

## JSON Fields Reference

### Hub Object
- `location` - Hub location (e.g., "3-1", "1-1.4")
- `description` - Full hub description string
- `hub_info` - Parsed hub information
- `vid` - Vendor ID in hex
- `pid` - Product ID in hex
- `usb_version` - USB version string
- `nports` - Number of ports
- `ppps` - Power switching mode
- `ports` - Array of port objects

### Port Object
- `port` - Port number
- `status` - Raw status value in hex
- `flags` - Boolean flags (only true values included)
- `human_readable` - Human-readable flag descriptions
- `speed` - Speed description string
- `speed_bps` - Speed in bits per second (numeric)
- `vid`, `pid` - Device vendor/product IDs
- `vendor`, `product` - Device vendor/product names
- `serial` - Device serial number
- `device_class` - USB device class code
- `class_name` - Device class name
- `is_mass_storage` - Boolean flag for mass storage devices
- `interfaces` - Array of interface descriptors

### USB3-Specific Fields
- `port_speed` - Link speed (e.g., "5gbps")
- `link_state` - USB3 link state (e.g., "U0", "U3")

## Tips

1. Use `jq -r` for raw output (no quotes)
2. Use `select()` to filter results
3. Use `. as $var` to save context when diving into nested objects
4. Use `// "default"` to provide default values for missing fields
5. Combine with shell scripts for automation
74 changes: 74 additions & 0 deletions examples/json_usage_examples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/bash
#
# Examples of using $UHUBCTL JSON output with jq
#
# This script demonstrates various ways to parse and use the JSON output
# from $UHUBCTL to find devices and generate control commands.
#
# Requirements: uhubctl with -j support and jq installed
#

# Use local uhubctl if available, otherwise use system version
UHUBCTL="uhubctl"
if [ -x "./uhubctl" ]; then
UHUBCTL="./uhubctl"
elif [ -x "../uhubctl" ]; then
UHUBCTL="../uhubctl"
fi

echo "=== Example 1: Find all FTDI devices and their control paths ==="
echo "# This finds all FTDI devices and shows how to control them"
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.vendor == "FTDI") | "Device: \(.description)\nControl with: $UHUBCTL -l \($hub.location) -p \(.port) -a off\n"'

echo -e "\n=== Example 2: Find a device by serial number ==="
echo "# This is useful when you have multiple identical devices"
SERIAL="A10KZP45" # Change this to your device's serial
$UHUBCTL -j | jq -r --arg serial "$SERIAL" '.hubs[] | . as $hub | .ports[] | select(.serial == $serial) | "Found device with serial \($serial):\n Location: \($hub.location)\n Port: \(.port)\n Control: $UHUBCTL -l \($hub.location) -p \(.port) -a cycle"'

echo -e "\n=== Example 3: List all mass storage devices with their control commands ==="
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.is_mass_storage == true) | "Mass Storage: \(.description)\n Power off: $UHUBCTL -l \($hub.location) -p \(.port) -a off\n Power on: $UHUBCTL -l \($hub.location) -p \(.port) -a on\n"'

echo -e "\n=== Example 4: Find all USB3 devices (5Gbps or faster) ==="
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.speed_bps >= 5000000000) | "\(.description) at \($hub.location):\(.port) - Speed: \(.speed)"'

echo -e "\n=== Example 5: Create a device map for documentation ==="
echo "# This creates a sorted list of all connected devices"
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.vid) | "[\($hub.location):\(.port)] \(.vendor // "Unknown") \(.product // .description) (Serial: \(.serial // "N/A"))"' | sort

echo -e "\n=== Example 6: Find empty ports where you can plug devices ==="
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.vid == null) | "Empty port available at hub \($hub.location) port \(.port)"'

echo -e "\n=== Example 7: Generate power control script for specific device type ==="
echo "# This generates a script to control all FTDI devices"
echo "#!/bin/bash"
echo "# Script to control all FTDI devices"
echo "# Usage: $0 [on|off|cycle]"
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.vendor == "FTDI") | "# \(.description)\n$UHUBCTL -l \($hub.location) -p \(.port) -a $1"'

echo -e "\n=== Example 8: Monitor device changes ==="
echo "# Save current state and compare later to see what changed"
echo "# Save current state:"
echo "$UHUBCTL -j > devices_before.json"
echo "# Later, check what changed:"
echo "$UHUBCTL -j > devices_after.json"
echo "diff <(jq -S . devices_before.json) <(jq -S . devices_after.json)"

echo -e "\n=== Example 9: Find devices by class ==="
echo "# Find all Hub devices"
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.class_name == "Hub") | "Hub: \(.description) at \($hub.location):\(.port)"'

echo -e "\n=== Example 10: Export to CSV ==="
echo "# Export device list to CSV format"
echo "Location,Port,VID,PID,Vendor,Product,Serial,Speed"
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.vid) | "\($hub.location),\(.port),\(.vid),\(.pid),\(.vendor // ""),\(.product // ""),\(.serial // ""),\(.speed)"'

echo -e "\n=== Example 11: Find devices on specific hub ==="
echo "# Find all devices on hub 3-1"
LOCATION="3-1"
$UHUBCTL -j | jq -r --arg loc "$LOCATION" '.hubs[] | select(.location == $loc) | .ports[] | select(.vid) | "Port \(.port): \(.description)"'

echo -e "\n=== Example 12: Power cycle all devices of a specific type ==="
echo "# This creates a one-liner to power cycle all USB mass storage devices"
echo -n "$UHUBCTL"
$UHUBCTL -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.is_mass_storage == true) | " -l \($hub.location) -p \(.port)"' | tr '\n' ' '
echo " -a cycle"
1 change: 1 addition & 0 deletions mkjson
Submodule mkjson added at 4d5a8f
Loading