Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

PowerView Connect

An async Python client for Hunter Douglas / Luxaflex PowerView Gen 3 gateways, over their local HTTP API and shade event stream.

It is built on what a real Gen 3 gateway was measured to do (see the protocol notes):

  • A move sends only the rails it names. The gateway leaves a rail that is not sent where it is, so a command to the top rail of a top-down/bottom-up shade can never undo a command to the bottom rail sent a moment before.
  • The event stream says when a shade starts and stops moving, whatever moved it: this client, the PowerView app, a scene, a voice assistant. Nothing needs polling while nothing moves.
  • A moving shade reports its live position, so a shade can be followed by polling it, and only it, while it moves. ShadeTracker decides when a move has ended.

aiohttp is its only dependency. It is unofficial: PowerView is a trademark of Hunter Douglas, which has nothing to do with this library.

Installation

pip install powerview-connect

Usage

import asyncio
import aiohttp
from powerview_connect import EventKind, PowerViewClient, Rail

async def main():
    async with aiohttp.ClientSession() as session:
        client = PowerViewClient(session, "<gateway-ip>")

        home = await client.get_home()                 # gateways, shades, scenes
        for shade in home.shades.values():
            print(shade.id, shade.name, shade.capability, dict(shade.positions))

        await client.set_positions([154], secondary=0.5)   # the top rail only
        await client.set_positions([154], primary=0.2)     # the bottom rail only

        async with client.shade_events(idle_timeout=360) as events:
            async for event in events:
                if event.kind == EventKind.MOTION_STOPPED:
                    print(event.shade_id, "stopped at", dict(event.current))

asyncio.run(main())

Use the address of the primary gateway: it lists the whole home, auxiliary gateways and their shades included. home.gateway_at(host) says which gateway an address is.

What a shade reports

A Shade carries its name, room, capabilities (named by capability), type, power_type (1 is hard-wired), firmware, positions, and signal_strength: how well the gateway hears it, in dBm, closer to 0 being stronger. battery names the battery status (BatteryStatus); a hard-wired shade reports one too, so it means something only where hard_wired is false.

Positions

Every rail runs from 0.0, fully closed, to 1.0, fully open:

Rail Is 0.0 1.0
Rail.PRIMARY the bottom rail of a top-down/bottom-up shade, the only rail of most others down up
Rail.SECONDARY the top rail of a top-down/bottom-up shade up down
Rail.TILT the vanes of a shade that tilts closed open

A top-down/bottom-up shade's own firmware stops a rail where it meets the other one, so a move that would cross them is safe to send.

Commands

Method Sends
set_positions(ids, primary=, secondary=, tilt=, velocity=) only the rails given, to every shade in ids, in one request
stop(ids) stop where they are
activate_scene(scene_id) a scene stored on the gateway
jog(shade_id) a short move back and forth, to show which shade it is
move_to_favorite(shade_id) to the favorite position stored in the shade (the remote's heart button)

A command the gateway reports an error for raises CommandError. A client made with read_only=True refuses every command with ReadOnlyError before anything is sent.

Events

shade_events(idle_timeout) opens the event stream; iterating it yields a ShadeEvent for each event, with kind, shade_id, current and, for a motion start, target and eta:

EventKind When
MOTION_STARTED a move starts, from any source; the motor follows some seconds later
MOTION_STOPPED a move ends, with the final positions
BATTERY_ALERT right before every move, by hard-wired shades too
SHADE_ONLINE, SHADE_OFFLINE a shade is reachable again, or no longer

The stream ends when the gateway closes it, and raises CannotConnectError when it breaks or sends nothing for idle_timeout seconds; the gateway sends nothing while nothing moves, so reopen it. eta is the gateway's estimate of the move's time, which real moves took about twice as long as.

Following a move

ShadeTracker follows one shade. It does no I/O and reads no clock: give it the events, the positions polled while the shade moves, and the commands sent, each with the time.

tracker = ShadeTracker(shade.positions)
tracker.motion_started(event, now=time.monotonic())        # or tracker.command(now, {Rail.PRIMARY: 0.2})
while tracker.poll((await client.get_shade(shade.id)).positions, now=time.monotonic()):
    print(tracker.current[Rail.PRIMARY], tracker.direction(Rail.PRIMARY))
    await asyncio.sleep(1)

A move ends with whichever comes first: motion-stopped, the target reached and still for 1.5 s, no change for 6 s after the shade moved, no move at all 25 s after the command, or a timeout.

Testing your code

powerview_connect.testing.SimulatedGateway is a gateway on 127.0.0.1 that answers as a real one was measured to, for your own tests:

from powerview_connect import Capabilities, PowerViewClient, Rail
from powerview_connect.testing import SimulatedGateway

async with SimulatedGateway() as gateway:
    gateway.add_room(1, "Living Room")
    gateway.add_shade(10, "Window", room_id=1, capabilities=Capabilities.TOP_DOWN_BOTTOM_UP)
    client = PowerViewClient(session, gateway.host)
    await client.set_positions([10], primary=0.5)    # motion-started on every stream
    gateway.move(10, {Rail.PRIMARY: 0.2})            # part of the way, as a poll sees it
    gateway.finish(10)                               # at the target; motion-stopped
    print(gateway.requests[-1].body)                 # {'positions': {'primary': 0.5}}

It also drops streams (drop_streams), goes offline and comes back (close, start), reports a shade offline (set_online) and reports errors for commands (command_errors).

Metadata

Release files for powerview-connect 0.1.0rc3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for powerview-connect 0.1.0rc3
File Size Uploaded
powerview_connect-0.1.0rc3.tar.gz 47.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for powerview-connect 0.1.0rc3
File Interpreter ABI Platform
powerview_connect-0.1.0rc3-py3-none-any.whl Python 3 none any Details

Total release size: 66.5 kB

Release files / powerview_connect-0.1.0rc3.tar.gz

Download URL powerview_connect-0.1.0rc3.tar.gz
Size 47.0 kB
Tags Source
SHA-256 checksum
How to use checksums
54bf164c59dc7799df1fe11158c27314ed3906b2a8a8d96af3afcd9f68469114
BLAKE2b-256 checksum
How to use checksums
7ead7dfb384b4042cf8548a14ae1daf35002b20127fe9abb5518327661646395
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / powerview_connect-0.1.0rc3-py3-none-any.whl

Download URL powerview_connect-0.1.0rc3-py3-none-any.whl
Size 19.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
82c28781c18a3a9dce804aa6d7bc9346c7fc28e665ce94f1d817e7b4fab49f0b
BLAKE2b-256 checksum
How to use checksums
ffcde26c204df4859f7602e796fb79f5c4a0c61edd77bd84eeaf0c1712013bac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page