Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@
},
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"name": "Asynchronous Python client for WLED",
"updateContentCommand": ". ${NVM_DIR}/nvm.sh && nvm install && nvm use && npm install && poetry install && poetry run pre-commit install"
"updateContentCommand": ". ${NVM_DIR}/nvm.sh && nvm install && nvm use && npm install && poetry install --extras cli && poetry run pre-commit install"
}
12 changes: 6 additions & 6 deletions .github/workflows/linting.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install Python dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🚀 Check code for common misspellings
run: poetry run pre-commit run codespell --all-files

Expand All @@ -53,7 +53,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install Python dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🚀 Run ruff linter
run: poetry run ruff check --output-format=github .
- name: 🚀 Run ruff formatter
Expand All @@ -78,7 +78,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install Python dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🚀 Check Python AST
run: poetry run pre-commit run check-ast --all-files
- name: 🚀 Check for case conflicts
Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install Python dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🚀 Run pylint
run: poetry run pre-commit run pylint --all-files

Expand All @@ -146,7 +146,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install Python dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🚀 Run yamllint
run: poetry run yamllint .

Expand All @@ -169,7 +169,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install Python dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🏗 Set up Node.js
uses: actions/setup-node@v4.0.2
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🏗 Set package version
run: |
version="${{ github.event.release.tag_name }}"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🚀 Run pytest
run: poetry run pytest --cov wled tests
- name: ⬆️ Upload coverage artifact
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🚀 Process coverage results
run: |
poetry run coverage combine coverage*/.coverage*
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/typing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ jobs:
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: 🏗 Install dependencies
run: poetry install --no-interaction
run: poetry install --extras cli --no-interaction
- name: 🚀 Run mypy
run: poetry run mypy examples src tests
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ To install all packages, including all development requirements:

```bash
npm install
poetry install
poetry install --extras cli
```

As this repository uses the [pre-commit][pre-commit] framework, all changes
Expand Down
130 changes: 91 additions & 39 deletions poetry.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ backoff = ">=2.2.0"
cachetools = ">=4.0.0"
python = "^3.11"
yarl = ">=1.6.0"
typer = {version = "^0.12.3", optional = true, extras = ["all"]}
zeroconf = {version = "^0.132.2", optional = true, extras = ["all"]}

[tool.poetry.extras]
cli = ["typer", "zeroconf"]

[tool.poetry.scripts]
wled = "wled.cli:cli"

[tool.poetry.urls]
"Bug Tracker" = "https://github.com/frenck/python-wled/issues"
Expand Down Expand Up @@ -60,6 +68,7 @@ source = ["wled"]
[tool.coverage.report]
fail_under = 53
show_missing = true
omit = ["src/wled/cli/*"]

[tool.mypy]
# Specify the target platform details in config, so your developers are
Expand Down
84 changes: 84 additions & 0 deletions src/wled/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Asynchronous Python client for WLED."""

import asyncio

from rich.console import Console
from rich.live import Live
from rich.table import Table
from zeroconf import ServiceStateChange, Zeroconf
from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf

from .async_typer import AsyncTyper

cli = AsyncTyper(help="WLED CLI", no_args_is_help=True, add_completion=False)
console = Console()


@cli.command("scan")
async def test() -> None:
"""Scan for WLED devices on the network."""
zeroconf = AsyncZeroconf()
background_tasks = set()

table = Table(
title="\n\nFound WLED devices", header_style="cyan bold", show_lines=True
)
table.add_column("Addresses")
table.add_column("MAC Address")

def async_on_service_state_change(
zeroconf: Zeroconf,
service_type: str,
name: str,
state_change: ServiceStateChange,
) -> None:
"""Handle service state changes."""
if state_change is not ServiceStateChange.Added:
return

future = asyncio.ensure_future(
async_display_service_info(zeroconf, service_type, name)
)
background_tasks.add(future)
future.add_done_callback(background_tasks.discard)

async def async_display_service_info(
zeroconf: Zeroconf, service_type: str, name: str
) -> None:
"""Retrieve and display service info."""
info = AsyncServiceInfo(service_type, name)
await info.async_request(zeroconf, 3000)
if info is None:
return

console.print(f"[cyan bold]Found service {info.server}: is a WLED device 🎉")

table.add_row(
f"{str(info.server).rstrip('.')}\n"
+ ", ".join(info.parsed_scoped_addresses()),
info.properties[b"mac"].decode(), # type: ignore[union-attr]
)

console.print("[green]Scanning for WLED devices...")
console.print("[red]Press Ctrl-C to exit\n")

with Live(table, console=console, refresh_per_second=4):
browser = AsyncServiceBrowser(
zeroconf.zeroconf,
"_wled._tcp.local.",
handlers=[async_on_service_state_change],
)

try:
while True:
await asyncio.sleep(0.5)
except KeyboardInterrupt:
pass
finally:
console.print("\n[green]Control-C pressed, stopping scan")
await browser.async_cancel()
await zeroconf.async_close()


if __name__ == "__main__":
cli()
Loading