Skip to content

Repository files navigation

WallWart

Fit bulky power adapters on the outlet strip you already own.

WallWart is an offline exact solver for the case where a strip has enough sockets and watts on paper, but several wall-wart bodies block each other in reality. Describe outlet anchors, adapter bodies, off-centre prongs, allowed rotations, keep-out regions, and a watt budget. WallWart finds the best legal layout or proves that the required devices cannot coexist.

WallWart example layout

There is no account, browser UI, telemetry, cloud solver, or runtime dependency. The useful output is a machine-verifiable plan plus a printable SVG and a script-free HTML report.

What it solves

  • Rotates each adapter around its real, possibly off-centre prong anchor.
  • Respects outlet orientation, allowed quarter turns, blocked switches/ports, minimum clearance, one-device-per-outlet, and total watts.
  • Places required devices, then maximizes optional priority and count.
  • Uses bounded exact branch-and-bound; successful plans are optimal.
  • Produces deterministic plan.json, layout.svg, and report.html files.
  • Re-verifies receipts by reconstructing geometry and rerunning optimization.

WallWart does not certify electrical safety. Measure your own hardware, follow manufacturer ratings, and never daisy-chain strips.

Quick start

Requires Python 3.11+ and uv.

git clone https://github.com/KanadeK/wallwart.git
cd wallwart
uv sync --locked --all-extras --cache-dir .uv-cache
uv run --cache-dir .uv-cache --locked --no-sync wallwart validate examples/studio-desk.json
uv run --cache-dir .uv-cache --locked --no-sync wallwart plan examples/studio-desk.json --output-dir studio-plan
uv run --cache-dir .uv-cache --locked --no-sync wallwart verify examples/studio-desk.json studio-plan/plan.json

Expected final lines:

VALID: Harbor synth desk · 4 outlets · 3 devices
PLANNED: 3 devices · 110 W · studio-plan
VERIFIED: studio-plan/plan.json

The planner refuses to overwrite studio-plan. Use a new destination or remove an old result only after confirming it is yours.

Scenario format

The complete example is examples/studio-desk.json. Measurements are millimetres; angles are quarter turns.

{
  "schema_version": 1,
  "name": "Tiny desk",
  "workspace": {"width_mm": 220, "height_mm": 120},
  "strip": {
    "x_mm": 10,
    "y_mm": 35,
    "width_mm": 200,
    "height_mm": 50,
    "max_watts": 100
  },
  "minimum_gap_mm": 4,
  "outlets": [
    {"id": "A1", "x_mm": 60, "y_mm": 60, "rotation_deg": 0},
    {"id": "A2", "x_mm": 150, "y_mm": 60, "rotation_deg": 0}
  ],
  "blocked_regions": [],
  "devices": [
    {
      "id": "router",
      "label": "Router",
      "watts": 30,
      "required": true,
      "priority": 0,
      "plug": {
        "width_mm": 36,
        "height_mm": 28,
        "anchor_x_mm": 10,
        "anchor_y_mm": 14,
        "allowed_turns_deg": [0, 180]
      }
    }
  ]
}

Unknown fields, duplicate IDs, booleans used as numbers, non-finite numbers, non-quarter rotations, and geometry outside the workspace fail fast. Inputs are limited to 1 MiB, 14 devices, and 16 outlets so exact search remains a clear public contract.

Commands and exits

wallwart validate SCENARIO
wallwart plan SCENARIO --output-dir DIRECTORY
wallwart verify SCENARIO PLAN_JSON
wallwart --version
  • Exit 0: validation, optimal planning, or verification succeeded.
  • Exit 1: a valid scenario is infeasible, or a receipt is invalid.
  • Exit 2: usage, JSON, schema, input file, or output path is invalid.

Planning validates and solves before creating the destination. A failed or infeasible run leaves no partial result directory.

How the optimum is chosen

WallWart rotates every adapter around its declared plug anchor and rejects placements outside the workspace or over a keep-out region. It then searches compatible combinations. The objective is:

  1. place every required device;
  2. maximize the sum of placed optional priorities;
  3. maximize the number of placed optional devices; and
  4. choose the smallest (device, outlet, rotation) signature.

The fixed limits favor trustworthy exact answers over silently returning a guess for an arbitrarily large job.

More examples

  • power-budget-choice.json demonstrates a real priority trade-off: the router and synth win over the lower-priority phone charger.
  • impossible-required.json has two wide required adapters that cannot coexist; plan exits 1 and creates no output.

Development and release gate

uv run --cache-dir .uv-cache --locked --no-sync python scripts/check.py

This runs a locked sync, Ruff, strict mypy, pytest with at least 90% branch coverage, dependency audit, wheel/sdist builds, a deterministic demo ZIP, and a clean temporary wheel installation followed by plan/verify acceptance.

Individual commands:

uv run --cache-dir .uv-cache --locked --no-sync ruff check .
uv run --cache-dir .uv-cache --locked --no-sync ruff format --check .
uv run --cache-dir .uv-cache --locked --no-sync mypy
uv run --cache-dir .uv-cache --locked --no-sync pytest --basetemp .pytest-tmp
uv build --cache-dir .uv-cache --out-dir dist
uv run --cache-dir .uv-cache --locked --no-sync pip-audit

CI runs the full gate on Windows and Linux. Releases contain the wheel, source distribution, and a demo ZIP with input and verified output.

Failure recovery

Symptom Meaning Repair
Failed to initialize cache The global uv cache is unusable. Keep --cache-dir .uv-cache, or set UV_CACHE_DIR to a writable project-local directory.
ERROR: ... invalid JSON or a field path Input broke the strict schema. Run validate, fix the first named field, and retry.
required_power_exceeds_limit Required watts exceed the declared budget. Correct measured watts/budget or make a genuinely optional device optional.
required_devices_cannot_coexist Required bodies collide or outlets are insufficient. Recheck body/anchor measurements, turns, gap, and keep-outs.
output path already exists WallWart protected an earlier plan. Choose a new path, or manually remove only the result you intend to replace.
pip-audit cannot reach its source The security gate is incomplete. Restore network and rerun the audit; do not waive the gate.
Installed-wheel smoke test fails Built artifact differs from source. Remove only dist/, rerun the uv-based release gate above, and inspect the first failing command.

If a test fails, rerun that exact command without -q before changing code. The suite includes an independent brute-force oracle, so optimizer mismatches are correctness defects, not flaky assertions.

Project notes

MIT licensed.

About

Exact offline planning for bulky AC adapters on power strips

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages