Toggle Fan Speed Randomly

  • DEFAULT_SPEEDS = [60, 69, 63, 72, 75, 79, 85, 83, 85, 86, 90, 91, 93, 95, 100]
  • Fan Speed Random Toggle Script
  • Randomly selects fan speed from a given list every interval seconds
  • Applies the selected speed to trays 0..4 using Junos 'cli -c' commands
  • Ctrl+C to stop gracefully

Example:

Trays 0-3, run 10 iterations, 2-minute interval

  • python3 fan_toggle_random.py --trays 0,1,2,3 --iterations 10 --interval 120
#fan_toggle_random.py
#!/usr/bin/env python3
"""
Fan Speed Random Toggle Script
- Randomly selects fan speed from a given list every interval seconds
- Applies the selected speed to trays 0..4 using Junos 'cli -c' commands
- Ctrl+C to stop gracefully
"""

import argparse
import random
import subprocess
import sys
import time
from datetime import datetime


DEFAULT_SPEEDS = [60, 69, 63, 72, 75, 79, 85, 83, 85, 86, 90, 91, 93, 95, 100]


def ts() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def run_cmd(cmd: str, dry_run: bool = False) -> int:
    if dry_run:
        print(f"{ts()} - DRYRUN: {cmd}")
        return 0
    # Using shell=True because 'cli' is typically a shell command on Junos
    p = subprocess.run(cmd, shell=True)
    return p.returncode


def set_fan_speed(speed: int, trays: list[int], dry_run: bool = False) -> None:
    print(f"{ts()} - Setting fan speed to {speed}%...")
    for tray in trays:
        cmd = f'cli -c "request chassis fan speed {speed} tray {tray}"'
        rc = run_cmd(cmd, dry_run=dry_run)
        if rc != 0:
            raise RuntimeError(f"Command failed (rc={rc}): {cmd}")
    print(f"{ts()} - Fan speed set to {speed}% for trays: {', '.join(map(str, trays))}")


def pick_speed(speeds: list[int], avoid_repeat: bool, last_speed: int | None) -> int:
    if not avoid_repeat or last_speed is None:
        return random.choice(speeds)

    # Try to avoid repeating the same speed back-to-back
    candidates = [s for s in speeds if s != last_speed]
    return random.choice(candidates) if candidates else last_speed


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--interval", type=int, default=120, help="Seconds between changes (default: 120)")
    parser.add_argument("--iterations", type=int, default=0,
                        help="Number of iterations (0 = run forever)")
    parser.add_argument("--trays", default="0,1,2,3,4", help="Comma-separated tray IDs (default: 0,1,2,3,4)")
    parser.add_argument("--no-repeat", action="store_true", help="Avoid repeating the same speed consecutively")
    parser.add_argument("--dry-run", action="store_true", help="Print commands without executing")
    args = parser.parse_args()

    trays = [int(x.strip()) for x in args.trays.split(",") if x.strip() != ""]
    speeds = DEFAULT_SPEEDS

    print("Fan Speed Random Toggle Script Started")
    print(f"Speed list: {speeds}")
    print(f"Trays: {trays}")
    print(f"Interval: {args.interval} seconds")
    print("Press Ctrl+C to stop")
    print("========================================")

    last_speed = None
    iteration = 1

    try:
        while True:
            if args.iterations and iteration > args.iterations:
                print(f"{ts()} - Completed {args.iterations} iterations. Exiting.")
                break

            speed = pick_speed(speeds, args.no_repeat, last_speed)

            print(f"\n--- Iteration {iteration} ---")
            set_fan_speed(speed, trays, dry_run=args.dry_run)
            last_speed = speed

            print(f"{ts()} - Waiting {args.interval} seconds before next change...")
            time.sleep(args.interval)

            iteration += 1

    except KeyboardInterrupt:
        print(f"\n{ts()} - Script stopped by user (Ctrl+C)")
        return 0
    except Exception as e:
        print(f"{ts()} - ERROR: {e}", file=sys.stderr)
        return 1

    return 0


if __name__ == "__main__":
    raise SystemExit(main())