Power Control Example

This example demonstrates how to control device power using both VeSync smart outlets and CyberPower PDUs. Power control is fundamental for device testing - you’ll use it to boot devices, trigger resets, and safely power down hardware.

VeSync Smart Outlet Control

VeSync provides cloud-connected smart outlets that can be controlled remotely. This example shows how to use them with labgrid.

What You’ll Need:

  • VeSync account with one or more smart outlets

  • VeSync outlet name (configured in your VeSync account)

  • Account credentials (email and password)

Configuration File (target.yaml):

targets:
  my_fpga_board:
    resources:
      VesyncOutlet:
        outlet_names: 'FPGA Board Power'  # Name of outlet in VeSync app
        username: 'your_email@example.com'
        password: 'your_password'
        delay: 5.0  # Seconds to wait between off and on during cycle

    drivers:
      VesyncPowerDriver: {}

Basic Usage Script:

from labgrid import Environment

# Load target configuration
env = Environment("target.yaml")
target = env.get_target("my_fpga_board")

# Get the power driver
power = target.get_driver("VesyncPowerDriver")

# Activate the driver
target.activate(power)

# Turn on the device
print("Powering on device...")
power.on()

# Do some work with the device...
# (run tests, interact with serial console, etc.)

# Power off the device
print("Powering off device...")
power.off()

# Deactivate the driver
target.deactivate(power)

Power Cycle Operation (reset device):

from labgrid import Environment
import time

env = Environment("target.yaml")
target = env.get_target("my_fpga_board")
power = target.get_driver("VesyncPowerDriver")
target.activate(power)

# Power cycle: off -> wait -> on
print("Performing power cycle (reset)...")
power.cycle()  # Automatically handles delay configured in resource

# Wait for device to fully boot
time.sleep(10)

print("Device reset complete")
target.deactivate(power)

Multiple Outlets:

VeSync supports controlling multiple outlets from a single driver:

targets:
  test_rack:
    resources:
      VesyncOutlet:
        outlet_names: 'Board A,Board B,Board C'  # Comma-separated outlet names
        username: 'your_email@example.com'
        password: 'your_password'
        delay: 5.0

    drivers:
      VesyncPowerDriver: {}
from labgrid import Environment

env = Environment("target.yaml")
target = env.get_target("test_rack")
power = target.get_driver("VesyncPowerDriver")
target.activate(power)

# All configured outlets are controlled together
print("Powering on all outlets...")
power.on()   # Powers on: Board A, Board B, Board C

print("Powering off all outlets...")
power.off()  # Powers off: Board A, Board B, Board C

target.deactivate(power)

CyberPower PDU Control

CyberPower PDUs provide industrial-grade power distribution with SNMP control. This is more reliable than cloud-connected outlets for critical testing environments.

What You’ll Need:

  • CyberPower PDU (tested on PDU15SWHVIEC8FNET and similar)

  • PDU hostname/IP address

  • SNMP community string (default is typically “public”)

  • Outlet number (usually 1-8)

Configuration File (target.yaml):

targets:
  lab_device:
    resources:
      CyberPowerOutlet:
        hostname: '192.168.1.100'
        outlet_number: 1  # Outlet 1-8
        snmp_version: '2c'
        community: 'public'

    drivers:
      CyberPowerDriver: {}

Basic Usage Script:

from labgrid import Environment

# Load target configuration
env = Environment("target.yaml")
target = env.get_target("lab_device")

# Get the power driver
power = target.get_driver("CyberPowerDriver")

# Activate the driver
target.activate(power)

# Turn on the outlet
print("Powering on via CyberPower PDU outlet 1...")
power.on()

# Work with device...

# Power off
print("Powering off...")
power.off()

target.deactivate(power)

Multi-Outlet Control:

targets:
  multi_outlet_system:
    resources:
      CyberPowerOutlet:
        hostname: '192.168.1.100'
        outlet_number: 1  # Can create multiple resources for different outlets
        snmp_version: '2c'
        community: 'public'

      CyberPowerOutlet@board_b_power:
        hostname: '192.168.1.100'
        outlet_number: 2
        snmp_version: '2c'
        community: 'public'

    drivers:
      CyberPowerDriver: {}
      CyberPowerDriver@board_b:
        cyberpoweroutlet: CyberPowerOutlet@board_b_power
from labgrid import Environment

env = Environment("target.yaml")
target = env.get_target("multi_outlet_system")

# Get drivers for each outlet
power_a = target.get_driver("CyberPowerDriver")
power_b = target.get_driver("CyberPowerDriver@board_b")

target.activate(power_a)
target.activate(power_b)

# Control outlets independently
print("Powering on board A...")
power_a.on()

print("Powering on board B...")
power_b.on()

# Later...
print("Powering off board A...")
power_a.off()

target.deactivate(power_a)
target.deactivate(power_b)

Error Handling Patterns

Graceful Power Management with Error Recovery:

from labgrid import Environment
import time

def safe_power_control(target, outlet_name, command, retries=3):
    """Safely control power with error handling and retries."""
    for attempt in range(retries):
        try:
            power = target.get_driver("VesyncPowerDriver")
            target.activate(power)

            if command == "on":
                power.on()
            elif command == "off":
                power.off()
            elif command == "cycle":
                power.cycle()

            target.deactivate(power)
            print(f"Power {command} successful for {outlet_name}")
            return True

        except Exception as e:
            print(f"Power control failed (attempt {attempt+1}/{retries}): {e}")
            time.sleep(5)  # Wait before retry

    print(f"Failed to power {command} {outlet_name} after {retries} attempts")
    return False

# Usage
env = Environment("target.yaml")
target = env.get_target("my_fpga_board")
safe_power_control(target, "my_fpga_board", "cycle")

Power Down with Validation:

from labgrid import Environment
import time

def ensure_powered_off(target, timeout=30):
    """Ensure device is powered off with validation."""
    power = target.get_driver("VesyncPowerDriver")
    target.activate(power)

    try:
        # Try graceful shutdown first
        shell = target.get_driver("ADIShellDriver")
        if target.get(shell):
            try:
                shell.sendline("poweroff")
                time.sleep(5)
            except:
                pass

        # Force power off if needed
        power.off()
        time.sleep(2)

        print("Device powered off successfully")

    except Exception as e:
        print(f"Error powering off: {e}")
    finally:
        target.deactivate(power)

Monitoring Power State During Test:

from labgrid import Environment
import time

def run_test_with_power_monitoring(target, test_func, timeout=300):
    """Run test with periodic power state checks."""
    power = target.get_driver("VesyncPowerDriver")
    target.activate(power)

    start_time = time.time()
    try:
        # Run test function
        test_func()

    except Exception as e:
        print(f"Test failed: {e}")
        # Power cycle on test failure
        print("Performing emergency power cycle...")
        power.cycle()
        raise

    finally:
        elapsed = time.time() - start_time
        print(f"Test completed in {elapsed:.1f} seconds")
        target.deactivate(power)

Integration with Boot Strategies

Power control is usually managed automatically by boot strategies, but you can also use it directly:

Manual Power Control Before Strategy:

from labgrid import Environment
from adi_lg_plugins.strategies.bootfpgasoc import Status

env = Environment("target.yaml")
target = env.get_target("my_device")

# Ensure device is powered off before boot
power = target.get_driver("VesyncPowerDriver")
target.activate(power)
power.off()
target.deactivate(power)

# Now use strategy to boot (which handles power on)
strategy = target.get_strategy("BootFPGASoC")
strategy.transition("shell")

# Device now booted and at shell
shell = target.get_driver("ADIShellDriver")
shell.run_command("uname -a")

# Cleanup - strategy handles power down
strategy.transition("soft_off")

Power Cycling Between Tests:

from labgrid import Environment
import time

env = Environment("target.yaml")
target = env.get_target("my_device")
power = target.get_driver("VesyncPowerDriver")
strategy = target.get_strategy("BootFPGASoC")

def run_test_cycle(test_number):
    """Run a single test cycle with power management."""
    print(f"\nTest cycle {test_number}")

    # Ensure clean start with power cycle
    target.activate(power)
    power.cycle()
    target.deactivate(power)

    time.sleep(5)

    # Boot device
    strategy.transition("shell")

    # Run tests
    shell = target.get_driver("ADIShellDriver")
    result = shell.run_command("./test_suite.sh")
    print(f"Test result: {result}")

    # Clean shutdown
    strategy.transition("soft_off")

    return "PASS" in result

# Run multiple test cycles
for cycle in range(5):
    try:
        success = run_test_cycle(cycle + 1)
        print(f"Cycle {cycle+1}: {'PASSED' if success else 'FAILED'}")
    except Exception as e:
        print(f"Cycle {cycle+1}: ERROR - {e}")
        # Ensure power is off on error
        try:
            target.get_driver("VesyncPowerDriver").off()
        except:
            pass

Complete Working Example

target.yaml:

targets:
  test_device:
    resources:
      VesyncOutlet:
        outlet_names: 'Test Device'
        username: 'your_email@example.com'
        password: 'your_password'
        delay: 5.0

      SerialPort:
        port: '/dev/ttyUSB0'
        baudrate: 115200

    drivers:
      VesyncPowerDriver: {}
      ADIShellDriver:
        console: SerialPort
        prompt: 'root@.*:.*#'
        login_prompt: 'login:'
        username: 'root'
        password: 'analog'
        login_timeout: 60

test_power_control.py:

from labgrid import Environment
import time

def test_power_on_off():
    """Test basic power on/off operations."""
    env = Environment("target.yaml")
    target = env.get_target("test_device")
    power = target.get_driver("VesyncPowerDriver")

    # Test power on
    print("Test 1: Power on")
    target.activate(power)
    power.on()
    time.sleep(2)
    assert power is not None  # Device should be on
    print("  PASSED")

    # Test power off
    print("Test 2: Power off")
    power.off()
    time.sleep(2)
    assert power is not None  # Device should be off
    print("  PASSED")

    target.deactivate(power)

def test_power_cycle():
    """Test power cycle (reset) operation."""
    env = Environment("target.yaml")
    target = env.get_target("test_device")
    power = target.get_driver("VesyncPowerDriver")

    print("Test 3: Power cycle")
    target.activate(power)

    # Perform cycle
    power.cycle()
    time.sleep(15)  # Wait for device to boot

    # Check device is booted
    shell = target.get_driver("ADIShellDriver")
    target.activate(shell)

    output = shell.run_command("uname -a")
    assert len(output) > 0
    print("  Device booted after cycle")
    print("  PASSED")

    target.deactivate(shell)
    target.deactivate(power)

if __name__ == "__main__":
    test_power_on_off()
    test_power_cycle()
    print("\nAll tests passed!")

Troubleshooting

VeSync Login Fails:

Error: Failed to login to VeSync account

Solutions:
- Verify email and password are correct
- Check that account is not locked (too many login attempts)
- Ensure outlet is visible in VeSync app
- Verify outlet name matches exactly (case-sensitive)

CyberPower PDU Connection Failed:

Error: SNMP connection to PDU failed

Solutions:
- Verify PDU hostname/IP is reachable: ping 192.168.1.100
- Confirm SNMP is enabled on PDU
- Check community string (usually "public")
- Verify outlet number (typically 1-8)
- Check firewall allows SNMP (UDP port 161)

Power Command Times Out:

Error: Power command did not complete in time

Solutions:
- Check network connectivity
- Verify VeSync/PDU is accessible
- Try cycling power manually to verify hardware works
- Increase timeout in configuration

See Also