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
6 changes: 5 additions & 1 deletion .devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@
"remoteUser": "vscode",
"features": {
"ghcr.io/devcontainers-extra/features/apt-packages:1": {
"packages": "ffmpeg,libturbojpeg0,libpcap-dev"
"packages": [
"ffmpeg",
"libturbojpeg0",
"libpcap-dev"
]
}
}
}
10 changes: 6 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ jobs:
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.13"
cache: "pip"

- name: Install uv
run: pip install uv

- name: Install requirements
run: python3 -m pip install -r requirements.txt
run: uv pip install --system -r pyproject.toml

- name: Lint
run: python3 -m ruff check .
run: uv run ruff check .

- name: Format
run: python3 -m ruff format . --check
run: uv run ruff format . --check
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
__pycache__
.pytest*
*.egg-info
*/.venv/*
*/build/*
*/dist/*


# misc
.coverage
.vscode
Expand Down
18 changes: 18 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml

target-version = "py313"
required-version = ">=0.12.1"

[lint]
select = [
Expand All @@ -11,15 +12,32 @@ ignore = [
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
"D203", # no-blank-line-before-class (incompatible with formatter)
"D212", # multi-line-summary-first-line (incompatible with formatter)
"EXE002", # incompatible with Windows
"COM812", # incompatible with formatter
"ISC001", # incompatible with formatter
]

[lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false

[lint.flake8-tidy-imports.banned-api]
"async_timeout".msg = "use asyncio.timeout instead"
"pytz".msg = "use zoneinfo instead"
"tests".msg = "You should not import tests"

[lint.isort]
force-sort-within-sections = true
known-first-party = ["homeassistant"]
combine-as-imports = true
split-on-trailing-comma = false

[lint.pyupgrade]
keep-runtime-typing = true

[lint.pydocstyle]
convention = "google"
property-decorators = ["propcache.api.cached_property"]

[lint.mccabe]
max-complexity = 25
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,19 @@ _Control your BigTreeTech Panda Status via Home Assistant_
- **WiFi connection state**: Indicates connection status.
- **Printer name**: Displays the connected printer's name.
- **Printer IP address**: Shows the printer's IP.
- **Printer S/N**: Shows the printer's Serial Number.
- **Printer state**: Indicates printer status.
- **Firmware version**: Shows the firmware version.

### Switches

- **Enable/disable WiFi AP**
- **Enable/disable RGB Idle Light**
- **WiFi AP** - Allows you to enable/disable the AP.
- **RGB Idle Light** - Allows you enable/disable the idle light
- It just sets the brightness to 0% or 100%.

### Select Entities

- **Light effect mode**: Lets you swap from mode on the fly (Music/H2D style).

## Installation

Expand Down
4 changes: 1 addition & 3 deletions config/configuration.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# https://www.home-assistant.io/integrations/default_config/
# default_config:

# Simplified setup for Home Assistant
bluetooth:
config:
dhcp:
Expand Down
3 changes: 1 addition & 2 deletions custom_components/panda_status/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from typing import TYPE_CHECKING

from homeassistant.const import CONF_URL, Platform
from homeassistant.loader import async_get_loaded_integration

from .const import DOMAIN, LOGGER
from .coordinator import PandaStatusDataUpdateCoordinator
Expand All @@ -24,6 +23,7 @@

PLATFORMS: list[Platform] = [
Platform.SENSOR,
Platform.SELECT,
Platform.SWITCH,
]

Expand All @@ -41,7 +41,6 @@ async def async_setup_entry(
)
entry.runtime_data = PandaStatusData(
client=PandaStatusWebSocket(url=entry.data[CONF_URL], session=None),
integration=async_get_loaded_integration(hass, entry.domain),
coordinator=coordinator,
)

Expand Down
81 changes: 44 additions & 37 deletions custom_components/panda_status/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,75 @@

from __future__ import annotations

from typing import Any

import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_URL

from custom_components.panda_status import tools
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_NAME, CONF_URL
from homeassistant.helpers import selector
from slugify import slugify

from .const import DOMAIN, LOGGER
from .websocket import (
PandaStatusWebSocket,
PandaStatusWebsocketCommunicationError,
PandaStatusWebsocketError,
from .websocket import PandaStatusWebsocketCommunicationError, PandaStatusWebsocketError

OPTIONS_SCHEMA = vol.Schema(
{
vol.Required(CONF_URL): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
),
vol.Optional(CONF_NAME): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
),
}
)


class PandaStatusFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
class PandaStatusFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for PandaStatus."""

VERSION = 1
MINOR_VERSION = 1

async def async_step_user(
self,
user_input: dict | None = None,
) -> config_entries.ConfigFlowResult:
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
_errors = {}
errors = {}
if user_input is not None:
try:
await self._test_credentials(
url=user_input[CONF_URL],
# Try and get initial data to validate the connection
initial_data = await tools.test_credentials(
url=user_input.get(CONF_URL)
)
except PandaStatusWebsocketCommunicationError as exception:
LOGGER.error(exception)
_errors["base"] = "connection"
errors["base"] = "connection"
except PandaStatusWebsocketError as exception:
LOGGER.exception(exception)
_errors["base"] = "unknown"
errors["base"] = "unknown"
else:
await self.async_set_unique_id(unique_id=slugify(user_input[CONF_URL]))
self._abort_if_unique_id_configured()
# Check if a config entry already exists
await self._async_handle_discovery_without_unique_id()

# Use tools to extract printer and device name
printer_name = tools.get_printer_name(initial_data=initial_data)
device_name = user_input.get(CONF_NAME, None) or tools.get_device_name(
initial_data=initial_data
)

# If user provided a name, use it; otherwise, use the device name
return self.async_create_entry(
title=user_input[CONF_URL], data=user_input
title=device_name,
description=f"Panda status for {printer_name}",
data={
CONF_URL: user_input.get(CONF_URL),
CONF_NAME: device_name,
},
)

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(
CONF_URL,
default=(user_input or {}).get(CONF_URL, vol.UNDEFINED),
): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.TEXT,
),
),
},
),
errors=_errors,
data_schema=OPTIONS_SCHEMA,
errors=errors,
)

async def _test_credentials(self, url: str) -> None:
"""Validate credentials."""
client = PandaStatusWebSocket(url=url, session=None)
await client.async_get_data()
2 changes: 0 additions & 2 deletions custom_components/panda_status/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

if TYPE_CHECKING:
from homeassistant.config_entries import ConfigEntry
from homeassistant.loader import Integration

from .coordinator import PandaStatusDataUpdateCoordinator
from .websocket import PandaStatusWebSocket
Expand All @@ -22,4 +21,3 @@ class PandaStatusData:

client: PandaStatusWebSocket
coordinator: PandaStatusDataUpdateCoordinator
integration: Integration
18 changes: 13 additions & 5 deletions custom_components/panda_status/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from typing import TYPE_CHECKING

from custom_components.panda_status import tools
from homeassistant.const import CONF_NAME
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

Expand All @@ -24,14 +26,20 @@ def __init__(
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_unique_id = (
f"{DOMAIN}_{coordinator.config_entry.entry_id}_{entity_description.key}"
)

self._attr_device_info = DeviceInfo(
manufacturer="BigTreeTech",
model="Panda Status",
sw_version=tools.extract_value(coordinator.data, "settings.fw_version"),
name=coordinator.config_entry.data[CONF_NAME],
identifiers={
(
coordinator.config_entry.domain,
coordinator.config_entry.entry_id,
),
}
)
},
)

self._attr_unique_id = (
f"{DOMAIN}_{coordinator.config_entry.unique_id}_{entity_description.key}"
)
4 changes: 2 additions & 2 deletions custom_components/panda_status/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"requirements": [
"websockets"
],
"version": "1.0.0"
}
"version": "1.2.0"
}
Loading