Skip to content

Repository files navigation

Python: Asynchronous client for Elgato Lights

GitHub Release Python Versions Project Stage Project Maintenance License

Build Status Code Coverage OpenSSF Scorecard Open in Dev Containers

Sponsor Frenck via GitHub Sponsors

Support Frenck on Patreon

Asynchronous Python client for Elgato Lights.

About

This package allows you to control and monitor Elgato Light devices programmatically. It is mainly created to allow third-party programs to automate the behavior of an Elgato Light device.

Known compatible and tested Elgato devices:

  • Elgato Key Light
  • Elgato Key Light Air
  • Elgato Key Light Mini
  • Elgato Light Strip

Installation

pip install elgato

To install with the optional CLI:

pip install "elgato[cli]"

CLI

The optional CLI lets you control Elgato Lights directly from the terminal. The --host option can also be set via the ELGATO_HOST environment variable.

# Show device information
elgato info --host elgato-key-light.local

# Show the current light state
elgato state --host elgato-key-light.local

# Turn the light on (optionally set brightness and temperature)
elgato on --host elgato-key-light.local --brightness 80 --temperature 200

# Turn the light off
elgato off --host elgato-key-light.local

# Identify the light (makes it blink)
elgato identify --host elgato-key-light.local

# Restart the device
elgato restart --host elgato-key-light.local

# Compare the installed firmware against what Elgato ships
elgato firmware --host elgato-key-light.local

# Install the newest firmware Elgato ships for this device
elgato update --host elgato-key-light.local

# Emit machine-readable JSON
elgato state --host elgato-key-light.local --json

# Scan the network for Elgato Lights (uses mDNS/Zeroconf)
elgato scan

Usage

The client is an async context manager; every API call is a coroutine. A quick status check and toggle looks like this:

import asyncio

from elgato import Elgato, Info, State


async def main() -> None:
    """Show example on controlling your Elgato Light device."""
    async with Elgato("elgato-key-light.local") as elgato:
        info: Info = await elgato.info()
        print(info)

        state: State = await elgato.state()
        print(state)

        # Toggle the light
        await elgato.light(on=(not state.on))


if __name__ == "__main__":
    asyncio.run(main())

Light control

The light() method supports both color-temperature mode and full-color (hue/saturation) mode. Parameters can be combined in a single call:

async with Elgato("elgato-key-light.local") as elgato:
    # Set brightness and color temperature (in mired, 143-344)
    await elgato.light(on=True, brightness=80, temperature=200)

    # Set brightness with hue/saturation (Light Strip only)
    await elgato.light(on=True, brightness=50, hue=240.0, saturation=100.0)

Battery-powered devices

The Key Light Mini runs on battery. Battery-specific methods are guarded and raise ElgatoNoBatteryError when called on a device without one:

from elgato import Elgato, BatteryInfo

async with Elgato("elgato-key-light-mini.local") as elgato:
    battery: BatteryInfo = await elgato.battery()
    print(f"Level: {battery.level}%, Power: {battery.charge_power}W")

    # Toggle studio mode (bypass the battery)
    await elgato.battery_bypass(on=True)

    # Configure energy saving
    await elgato.energy_saving(
        on=True,
        brightness=10,
        minimum_battery_level=15,
        adjust_brightness=True,
        disable_wifi=False,
    )

Wi-Fi signal

Devices report their signal in dBm, which is awkward to put in front of a user. signal_strength maps it onto a percentage, from nothing at -100 dBm to as good as it gets at -50 dBm.

async with Elgato("elgato-key-light.local") as elgato:
    info = await elgato.info()
    if info.wifi:
        print(f"{info.wifi.rssi} dBm ({info.wifi.signal_strength}%)")

Device management

async with Elgato("elgato-key-light.local") as elgato:
    # Make the light blink to identify it
    await elgato.identify()

    # Change the display name
    await elgato.display_name("Studio Left")

    # Reboot the device
    await elgato.restart()

Firmware updates

Elgato runs no firmware download service. Every image ships inside the Control Center application, and new firmware only arrives with a new Control Center release. FirmwareCatalog reads those images straight out of the archive Elgato publishes, using HTTP range requests rather than downloading all sixteen megabytes of it.

Results are cached, and a refresh stops at the small release index unless Elgato actually shipped a new Control Center, so polling on a slow cadence is cheap:

Call Requests Bytes
versions(), first time 13 ~210 KB
versions(), again 0 0
versions(refresh=True), nothing new 1 ~12 KB
download(), when installing 2 ~600 KB

Refresh once a day. Elgato publishes new firmware a few times a year, and nothing else needs to talk to their servers.

from elgato import Elgato, FirmwareCatalog

async with Elgato("elgato-key-light.local") as elgato:
    info = await elgato.info()

    async with FirmwareCatalog() as catalog:
        available = await catalog.latest(info.hardware_board_type)
        print(f"installed {info.firmware_build_number}, available {available.full_version}")

        if available.build_number > info.firmware_build_number:
            image = await catalog.download(info.hardware_board_type)
            await elgato.update_firmware(image)

Every image is verified against Elgato's Ed25519 signing key before a single byte reaches the device, and update_firmware() refuses an image built for a different board. The device holds two firmware slots and keeps running the old one until the final step, so an upload that fails leaves a working light. update_firmware() returns as soon as the device accepts the reboot; coming back takes it about a minute.

Pass on_progress to follow along:

await elgato.update_firmware(
    image,
    on_progress=lambda sent, total: print(f"{sent / total:.0%}"),
)

Power-on behavior

Configure what the light does when it powers on:

from elgato import Elgato, PowerOnBehavior

async with Elgato("elgato-key-light.local") as elgato:
    await elgato.power_on_behavior(
        behavior=PowerOnBehavior.USE_DEFAULTS,
        brightness=50,
        temperature=230,
    )

Transition durations

How long the light takes to fade when it is switched or changed, in milliseconds. Values left out keep whatever the device already had.

async with Elgato("elgato-key-light.local") as elgato:
    await elgato.transition_durations(
        switch_on=100,
        switch_off=300,
        color_change=100,
    )

The device does not check these. It answers 200 to a negative number and stores a 0, so anything negative is refused here instead.

Connection options

All constructor arguments are keyword-only (except host):

Elgato(
    "elgato-key-light.local",
    port=9123,             # default Elgato API port
    request_timeout=8,     # per-request timeout in seconds
)

You may also pass your own aiohttp.ClientSession via session=... to share a connection pool across multiple clients.

Error handling

All exceptions inherit from ElgatoError, so a single except covers every failure mode:

from elgato import Elgato, ElgatoConnectionError, ElgatoError, ElgatoNoBatteryError

try:
    async with Elgato("elgato-key-light.local") as elgato:
        await elgato.light(on=True)
except ElgatoConnectionError:
    # Timeout or network issue
    ...
except ElgatoNoBatteryError:
    # Battery method called on a device without one
    ...
except ElgatoError:
    # Any other Elgato-specific error (invalid parameters, HTTP errors, etc.)
    ...

The library does not retry failed requests. If you need retry logic, wrap calls with a library like tenacity:

from tenacity import retry, stop_after_attempt, wait_exponential

from elgato import Elgato, ElgatoConnectionError


@retry(
    retry=retry_if_exception_type(ElgatoConnectionError),
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, max=10),
)
async def toggle_light(elgato: Elgato) -> None:
    """Toggle the light with retries."""
    state = await elgato.state()
    await elgato.light(on=(not state.on))

Changelog & Releases

This repository keeps a change log using GitHub's releases functionality. The format of the log is based on Keep a Changelog.

Releases are based on Semantic Versioning, and use the format of MAJOR.MINOR.PATCH. In a nutshell, the version will be incremented based on the following:

  • MAJOR: Incompatible or major changes.
  • MINOR: Backwards-compatible new features and enhancements.
  • PATCH: Backwards-compatible bugfixes and package updates.

Contributing

This is an active open-source project. We are always open to people who want to use the code or contribute to it.

We've set up a separate document for our contribution guidelines.

Thank you for being involved! 😍

Setting up development environment

The easiest way to start, is by opening a CodeSpace here on GitHub, or by using the Dev Container feature of Visual Studio Code.

Open in Dev Containers

This Python project is fully managed using the Poetry dependency manager. But also relies on the use of NodeJS for certain checks during development.

You need at least:

  • Python 3.11+
  • Poetry
  • NodeJS 24+ (including NPM)

To install all packages, including all development requirements:

npm install
poetry install

As this repository uses the prek framework, all changes are linted and tested with each commit. You can run all checks and tests manually, using the following command:

poetry run prek run --all-files

To run just the Python tests:

poetry run pytest

Authors & contributors

The original setup of this repository is by Franck Nijhof.

For a full list of all authors and contributors, check the contributor's page.

License

MIT License

Copyright (c) 2019-2026 Franck Nijhof

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

Asynchronous Python client for Elgato Key Lights.

Resources

Code of conduct

Contributing

Security policy

Stars

35 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages