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 all 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
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.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
142 changes: 142 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,148 @@ 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.


JSON Output
===========

The `-j` option enables JSON output for all commands, including status queries and power actions.
The JSON is pretty-printed with proper indentation for human readability.

Status Query JSON Format
------------------------

When querying hub status, the output follows this structure:

The `status` field provides three levels of detail:
- `raw`: Original hex value from USB hub
- `decoded`: Human-readable interpretation (e.g., "device_active", "powered_no_device")
- `bits`: Individual status bits broken down by name

```json
{
"hubs": [
{
"location": "3-1.4",
"description": "05e3:0610 GenesysLogic USB2.1 Hub, USB 2.10, 4 ports, ppps",
"hub_info": {
"vid": "0x05e3",
"pid": "0x0610",
"usb_version": "2.10",
"nports": 4,
"ppps": "ppps"
},
"ports": [
{
"port": 1,
"status": {
"raw": "0x0103",
"decoded": "device_active",
"bits": {
"connection": true,
"enabled": true,
"powered": true,
"suspended": false,
"overcurrent": false,
"reset": false,
"highspeed": false,
"lowspeed": false
}
},
"flags": {
"connection": true,
"enable": true,
"power": true
},
"human_readable": {
"connection": "Device is connected",
"enable": "Port is enabled",
"power": "Port power is enabled"
},
"speed": "USB1.1 Full Speed 12Mbps",
"speed_bps": 12000000,
"vid": "0x0403",
"pid": "0x6001",
"vendor": "FTDI",
"product": "FT232R USB UART",
"device_class": 0,
"class_name": "Composite Device",
"usb_version": "2.00",
"device_version": "6.00",
"serial": "A10KZP45",
"description": "0403:6001 FTDI FT232R USB UART A10KZP45"
}
]
}
]
}
```

Power Action JSON Events
------------------------

When performing power actions (on/off/toggle/cycle), uhubctl outputs real-time JSON events:

```bash
uhubctl -j -a cycle -l 3-1.4 -p 1 -d 2
```

Outputs events like:

```json
{"event": "hub_status", "hub": "3-1.4", "description": "05e3:0610 GenesysLogic USB2.1 Hub, USB 2.10, 4 ports, ppps"}
{"event": "power_change", "hub": "3-1.4", "port": 1, "action": "off", "from_state": true, "to_state": false, "success": true}
{"event": "delay", "reason": "power_cycle", "duration_seconds": 2.0}
{"event": "power_change", "hub": "3-1.4", "port": 1, "action": "on", "from_state": false, "to_state": true, "success": true}
```

Event types include:
- `hub_status`: Initial hub information
- `power_change`: Port power state change
- `delay`: Wait period during power cycling
- `hub_reset`: Hub reset operation (when using `-R`)

JSON Usage Examples
-------------------

Find all FTDI devices and show how to control them:
```bash
uhubctl -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.vendor == "FTDI") |
"Device: \(.description)\nControl: uhubctl -l \($hub.location) -p \(.port) -a off\n"'
```

Find a device by serial number:
```bash
SERIAL="A10KZP45"
uhubctl -j | jq -r --arg serial "$SERIAL" '.hubs[] | . as $hub | .ports[] |
select(.serial == $serial) | "Found at hub \($hub.location) port \(.port)"'
```

List all empty ports:
```bash
uhubctl -j | jq -r '.hubs[] | . as $hub | .ports[] |
select(.vid == null) | "Empty: hub \($hub.location) port \(.port)"'
```

Generate CSV of all connected devices:
```bash
echo "Location,Port,VID,PID,Vendor,Product,Serial"
uhubctl -j | jq -r '.hubs[] | . as $hub | .ports[] | select(.vid) |
"\($hub.location),\(.port),\(.vid),\(.pid),\(.vendor // ""),\(.product // ""),\(.serial // "")"'
```

Monitor power action results:
```bash
uhubctl -j -a cycle -l 3-1 -p 2 | jq 'select(.event == "power_change")'
```


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
Loading