> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lagerdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# USB Control

> Control USB device power state

Control the power state of USB devices and ports on your testbed using USB hubs with per-port power control.

## Import

```python theme={null}
from lager import Net, NetType

# For exception handling
from lager import (
    USBBackendError,
    LibraryMissingError,
    DeviceNotFoundError,
    PortStateError
)
```

## Methods

| Method                 | Description                                                               |
| ---------------------- | ------------------------------------------------------------------------- |
| `enable()`             | Enable (power on) USB port                                                |
| `disable()`            | Disable (power off) USB port                                              |
| `toggle()`             | Toggle USB port power state; returns the resulting state (`True`=enabled) |
| `state()`              | Read the current power state without changing it (`True`=enabled)         |
| `cycle(off_time=None)` | Power-cycle the port; returns whether the device came back                |
| `recover()`            | Restore power after an interrupted operation left a port off              |

## Exception Classes

| Exception             | Description                       |
| --------------------- | --------------------------------- |
| `USBBackendError`     | Base class for USB hub errors     |
| `LibraryMissingError` | Required vendor SDK not installed |
| `DeviceNotFoundError` | USB hub not found                 |
| `PortStateError`      | Error changing port state         |

## Method Reference

### `Net.get(name, type=NetType.Usb)`

Get a USB net by name.

```python theme={null}
from lager import Net, NetType

usb = Net.get('DUT_USB', type=NetType.Usb)
```

**Parameters:**

| Parameter | Type      | Description           |
| --------- | --------- | --------------------- |
| `name`    | `str`     | Name of the USB net   |
| `type`    | `NetType` | Must be `NetType.Usb` |

**Returns:** USB Net instance

### `enable()`

Enable (power on) the USB port.

```python theme={null}
from lager import Net, NetType

usb = Net.get('CAMERA_USB', type=NetType.Usb)
usb.enable()
print("USB port powered on")
```

### `disable()`

Disable (power off) the USB port.

```python theme={null}
from lager import Net, NetType

usb = Net.get('CAMERA_USB', type=NetType.Usb)
usb.disable()
print("USB port powered off")
```

### `toggle()`

Toggle the power state of the USB port. Returns the resulting state
(`True` if now enabled, `False` if now disabled).

```python theme={null}
from lager import Net, NetType

usb = Net.get('SENSOR_USB', type=NetType.Usb)
now_on = usb.toggle()  # On -> Off or Off -> On
```

### `state()`

Read the current power state of the USB port **without changing it**. Returns
`True` if the port is currently enabled (powered on), `False` if disabled. The
value is read live from the hub, so it always reflects the real port state.

```python theme={null}
from lager import Net, NetType

usb = Net.get('SENSOR_USB', type=NetType.Usb)
if not usb.state():
    usb.enable()   # only power on if it isn't already
```

### `cycle(off_time=None)`

Power-cycle the port: off, wait, on. Returns `True` if the device was seen to
re-enumerate, `False` if it did not come back in time, and `None` if the hub
reports nothing attached or the driver cannot observe re-enumeration.

<Note>
  **`None` does not mean the port is unused.** A hub only sees a device that pulls
  up its data lines, so a **charge-only cable** — power on the other end, no data —
  is indistinguishable from an empty socket. Power is cut and restored either way;
  there is simply nothing on the bus to watch come back. Confirm a DUT on such a
  port by its own behaviour instead: its UART output, or a current measurement.
</Note>

`off_time` is how long the port stays unpowered, defaulting to 1 second and
limited to 0.5-10 seconds. **Too short an off time is the failure that matters**:
the device's rails do not fully discharge and it warm-starts while appearing to
have been reset. Raise it for a device with large bulk capacitance.

```python theme={null}
from lager import Net, NetType

usb = Net.get('DUT_USB', type=NetType.Usb)
if usb.cycle(off_time=2):
    print("DUT cold-booted and came back")
```

Prefer this over a hand-rolled `disable`/`sleep`/`enable`: it holds the hub for
the whole sequence, so nothing else can switch the port while it is dark, and it
restores power on every failure path, so an exception partway through cannot
leave a port stranded.

<Note>
  `cycle` returns on the **hub's** reconnect signal, a few hundred milliseconds
  after power returns — not on Linux finishing enumeration. So `/dev/ttyUSB*` may
  not exist yet when it returns, and a `/sys` read taken immediately still shows
  the pre-cycle device number. Poll for what you need rather than reading once.
</Note>

<Warning>
  **A powered-off port still appears in `lsusb` and keeps its `/dev/ttyUSB*`.**
  Hubs raise no change notification while a port is unpowered, so the kernel does
  not process the disconnect until power returns. Never check for a device's
  absence to decide whether a port is off — use `state()`, which reads the hub's
  own power bit.
</Warning>

### `recover()`

Restore power after an interrupted operation left a port unpowered. On hubs where
lager can identify the whole physical device, this re-powers every port on it.

```python theme={null}
from lager import Net, NetType

Net.get('DUT_USB', type=NetType.Usb).recover()
```

## Examples

### Basic Power Control

```python theme={null}
from lager import Net, NetType

# Get USB net
usb = Net.get('CAMERA', type=NetType.Usb)

# Power on USB device
usb.enable()
print("Camera powered on")

# Power off USB device
usb.disable()
print("Camera powered off")
```

### Power Cycle Device

```python theme={null}
from lager import Net, NetType
import time

def power_cycle(net_name, delay=2):
    """Power cycle a USB device."""
    usb = Net.get(net_name, type=NetType.Usb)
    print(f"Power cycling {net_name}...")
    came_back = usb.cycle(off_time=delay)
    if came_back is False:
        print(f"{net_name} did not re-enumerate")
    else:
        print(f"{net_name} restarted")

power_cycle('DUT_USB')
```

### Error Handling

```python theme={null}
from lager import Net, NetType
from lager import (
    USBBackendError,
    LibraryMissingError,
    DeviceNotFoundError,
    PortStateError
)

try:
    usb = Net.get('SENSOR_USB', type=NetType.Usb)
    usb.enable()
    print("Sensor powered on")

except LibraryMissingError:
    print("USB hub SDK not installed")

except DeviceNotFoundError:
    print("USB hub not found - check connection")

except PortStateError as e:
    print(f"Port error: {e}")

except USBBackendError as e:
    print(f"USB error: {e}")
```

### Automated Test Setup

```python theme={null}
from lager import Net, NetType
import time

def setup_test():
    """Power on all USB peripherals for testing."""
    usb_devices = ['PROGRAMMER', 'SENSOR', 'DEBUGGER']

    for name in usb_devices:
        try:
            usb = Net.get(name, type=NetType.Usb)
            usb.enable()
            print(f"{name} powered on")
        except Exception as e:
            print(f"Warning: {name} - {e}")

    time.sleep(1)  # Wait for USB enumeration

    # Enable main power
    psu = Net.get('VDD', type=NetType.PowerSupply)
    psu.set_voltage(3.3)
    psu.enable()

    return True

def teardown_test():
    """Power off all USB peripherals."""
    # Disable main power first
    psu = Net.get('VDD', type=NetType.PowerSupply)
    psu.disable()

    # Power off USB peripherals
    for name in ['PROGRAMMER', 'SENSOR', 'DEBUGGER']:
        try:
            usb = Net.get(name, type=NetType.Usb)
            usb.disable()
        except Exception:
            pass
```

### USB Device Reset

```python theme={null}
from lager import Net, NetType
import time

def reset_usb_device(net_name, reset_time=2):
    """
    Reset a USB device by power cycling.

    Args:
        net_name: USB net name
        reset_time: Time to keep power off (seconds)
    """
    usb = Net.get(net_name, type=NetType.Usb)
    print(f"Resetting {net_name} (power off for {reset_time}s)...")

    # cycle() waits for the port to re-enumerate itself, so there is no
    # settle_time to guess at -- and no risk of the guess being too short.
    came_back = usb.cycle(off_time=reset_time)

    if came_back is False:
        raise RuntimeError(f"{net_name} did not come back after a power cycle")
    print(f"  {net_name} reset complete")

# Usage
reset_usb_device('DUT_USB', reset_time=2)
```

### Toggle for Quick State Change

```python theme={null}
from lager import Net, NetType
import time

# Get USB net
usb = Net.get('LED_USB', type=NetType.Usb)

# Quick on/off cycle using toggle
for i in range(5):
    usb.toggle()
    time.sleep(0.5)
```

## Supported Hardware

| Hardware                   | Features                                                     |
| -------------------------- | ------------------------------------------------------------ |
| Acroname BrainStem USB Hub | Individual port power control, current monitoring            |
| YKUSH USB Hub              | Per-port power switching                                     |
| Plugable RTS5411 dock      | Per-port power switching on the four external Type-A sockets |

## Notes

* USB nets must be configured on the Lager Box with hub serial number and port mapping
* Power state changes take effect immediately
* Allow time for USB enumeration after powering on (\~1-3 seconds). `cycle()`
  does this waiting for you and tells you whether the device returned
* Power cycling can be useful for device reset/recovery; prefer `cycle()` over a
  hand-rolled `disable`/`sleep`/`enable` so a failure cannot leave a port off
* A port that is powered off still appears in `lsusb` and keeps its device
  nodes, so never use device presence to test whether a port is off
* The `toggle()` function is useful for quick state changes
* Use exception handling for robust error recovery
