chore: Projektgeruest, Manifest, Konstanten und CI
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
checks:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
cache-dependency-glob: "requirements_dev.txt"
|
||||||
|
- run: uv venv --python 3.14 .venv && uv pip install --python .venv/bin/python -r requirements_dev.txt
|
||||||
|
- run: .venv/bin/ruff check . && .venv/bin/ruff format --check .
|
||||||
|
- run: .venv/bin/pytest -q --cov --cov-report=term-missing
|
||||||
|
hassfest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: home-assistant/actions/hassfest@master
|
||||||
|
hacs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: hacs/action@main
|
||||||
|
with:
|
||||||
|
category: integration
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""The ninlight integration."""
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""Constants for the ninlight integration. Deliberately free of Home Assistant imports."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
DOMAIN: Final = "ninlight"
|
||||||
|
|
||||||
|
# Public default token of the ninlight firmware (CMakeLists.txt of the firmware repo).
|
||||||
|
DEFAULT_TOKEN: Final = "aabbccddeeff0011223344"
|
||||||
|
|
||||||
|
CONF_SCAN_INTERVAL: Final = "scan_interval"
|
||||||
|
CONF_LAMP_BASE_URL: Final = "lamp_base_url"
|
||||||
|
|
||||||
|
DEFAULT_SCAN_INTERVAL: Final = 30
|
||||||
|
MIN_SCAN_INTERVAL: Final = 10
|
||||||
|
MAX_SCAN_INTERVAL: Final = 300
|
||||||
|
|
||||||
|
MAX_TRANSITION_MS: Final = 60_000
|
||||||
|
|
||||||
|
FIRMWARE_DIR_NAME: Final = "ninlight_firmware"
|
||||||
|
CATALOG_SCAN_INTERVAL: Final = timedelta(minutes=5)
|
||||||
|
SIGNAL_CATALOG_UPDATED: Final = f"{DOMAIN}_catalog_updated"
|
||||||
|
|
||||||
|
FIRMWARE_URL_PREFIX: Final = "/api/ninlight/firmware/"
|
||||||
|
# len(secrets.token_urlsafe(32))
|
||||||
|
FIRMWARE_KEY_LENGTH: Final = 43
|
||||||
|
# The lamp rejects fw_update URLs longer than this (nl_api.c: get_string(cmd, "url", 8, 255)).
|
||||||
|
MAX_LAMP_URL_LENGTH: Final = 255
|
||||||
|
|
||||||
|
OTA_POLL_INTERVAL_S: Final = 5.0
|
||||||
|
# The firmware aborts a stalled download after 10 min; a dead peer can hang ~7 min in TCP.
|
||||||
|
OTA_TIMEOUT_S: Final = 12 * 60.0
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"domain": "ninlight",
|
||||||
|
"name": "ninlight",
|
||||||
|
"codeowners": ["@ninharp"],
|
||||||
|
"config_flow": true,
|
||||||
|
"dependencies": ["http"],
|
||||||
|
"documentation": "https://github.com/ninharp/ha-ninlight",
|
||||||
|
"integration_type": "device",
|
||||||
|
"iot_class": "local_polling",
|
||||||
|
"issue_tracker": "https://github.com/ninharp/ha-ninlight/issues",
|
||||||
|
"requirements": [],
|
||||||
|
"version": "0.1.0",
|
||||||
|
"zeroconf": [{ "type": "_ninlight._tcp.local." }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "ninlight",
|
||||||
|
"render_readme": true,
|
||||||
|
"homeassistant": "2026.9.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests_unit", "tests"]
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
addopts = "-p no:cacheprovider"
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = ["custom_components/ninlight"]
|
||||||
|
branch = true
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
fail_under = 95
|
||||||
|
show_missing = true
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py314"
|
||||||
|
line-length = 100
|
||||||
|
exclude = ["docs/"]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "ASYNC"]
|
||||||
|
# E501: line length is the formatter's job; long literals in test tables stay on one line.
|
||||||
|
# RUF012: HA entities declare _attr_* lists as plain class attributes by convention.
|
||||||
|
ignore = ["E501", "RUF012"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
known-first-party = ["custom_components.ninlight", "tests", "tests_unit"]
|
||||||
|
force-sort-within-sections = true
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
homeassistant==2026.9.0
|
||||||
|
pytest-homeassistant-custom-component==0.13.363
|
||||||
|
pytest-cov
|
||||||
|
ruff>=0.9
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
"""Fixtures for tests that run against a Home Assistant instance."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def auto_enable_custom_integrations(enable_custom_integrations: None) -> Generator[None]:
|
||||||
|
"""Load custom_components/ in every Home Assistant test."""
|
||||||
|
yield
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""The manifest must agree with the constants the code relies on."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from custom_components.ninlight.const import DOMAIN
|
||||||
|
|
||||||
|
MANIFEST = Path(__file__).parents[1] / "custom_components" / "ninlight" / "manifest.json"
|
||||||
|
|
||||||
|
|
||||||
|
def test_manifest_matches_constants() -> None:
|
||||||
|
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||||
|
assert manifest["domain"] == DOMAIN
|
||||||
|
assert manifest["zeroconf"] == [{"type": "_ninlight._tcp.local."}]
|
||||||
|
assert manifest["iot_class"] == "local_polling"
|
||||||
|
assert manifest["config_flow"] is True
|
||||||
|
assert manifest["requirements"] == []
|
||||||
|
assert manifest["dependencies"] == ["http"]
|
||||||
Reference in New Issue
Block a user