From bba0ef31d81b2216c1e2e301164cff15263ae04e Mon Sep 17 00:00:00 2001 From: Casey Date: Thu, 21 May 2026 10:23:04 +0200 Subject: [PATCH] Delete upstream docs apparatus; rewrite README for the fork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CDiT fork is single-household and was carrying an entire upstream docs/ mkdocs site documenting features the fork has removed (sleep mode, take-over-control, brightness_mode selector, YAML config). The README mostly auto-generated from those pages via markdown-code-runner. What's gone: - docs/ — all 11 pages + assets + run_markdown_code_runner.py - .github/workflows/docs.yml — zensical+shinylive GitHub Pages build - .github/workflows/markdown-code-runner.yml — auto-edit of README from docs/ - .github/update-services.py / update-strings.py — would clobber the hand-edited services.yaml / strings.json - scripts/update-generated-content — orchestrator for the above - custom_components/adaptive_lighting/_docs_helpers.py + docs_gen.py — only used by the deleted markdown-code-runner - zensical.toml — docs-site config - "docs" dependency group in pyproject.toml (markdown-code-runner, shinylive, zensical, etc.) + lock regenerated What's new: - README.md rewritten to describe the fork's actual surface — 3 switches, 4 number entities, 3 sensor entities per profile, 2 services (apply + change_switch_settings), entry-only setup, UI tuning, sun source. Preserves the existing "What's new in 2.1" / "2.2" block quotes. - webapp/README.md annotated to flag the Shiny simulator models the upstream curve (with brightness_mode, sleep mode, etc.) and does not reflect the fork. - CLAUDE.md command table no longer references update-generated-content. Sensor.py + test_sensor_platform.py: incidental black reformat from the lint pass (multi-arg function calls split per line). Tests still pass (123/123). Lint clean. Sensors implementation unchanged. --- .github/update-services.py | 27 - .github/update-strings.py | 70 -- .github/workflows/docs.yml | 64 -- .github/workflows/markdown-code-runner.yml | 48 -- .gitignore | 3 - CLAUDE.md | 1 - README.md | 792 ++++-------------- .../adaptive_lighting/_docs_helpers.py | 122 --- .../adaptive_lighting/docs_gen.py | 35 - custom_components/adaptive_lighting/sensor.py | 4 +- docs/CNAME | 1 - docs/advanced/brightness-modes.md | 122 --- docs/advanced/manual-control.md | 161 ---- docs/advanced/sleep-mode.md | 43 - docs/assets/logo.png | Bin 17642 -> 0 bytes docs/assets/stylesheets/extra.css | 46 - docs/automation-examples.md | 115 --- docs/configuration.md | 150 ---- docs/getting-started.md | 117 --- docs/index.md | 85 -- .../integrations/analytics/custom.html | 6 - docs/run_markdown_code_runner.py | 71 -- docs/see-also.md | 73 -- docs/services.md | 201 ----- docs/troubleshooting.md | 106 --- pyproject.toml | 12 - scripts/update-generated-content | 11 - tests/test_sensor_platform.py | 24 +- uv.lock | 539 +----------- webapp/README.md | 15 + zensical.toml | 120 --- 31 files changed, 175 insertions(+), 3009 deletions(-) delete mode 100644 .github/update-services.py delete mode 100644 .github/update-strings.py delete mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/markdown-code-runner.yml delete mode 100644 custom_components/adaptive_lighting/_docs_helpers.py delete mode 100644 custom_components/adaptive_lighting/docs_gen.py delete mode 100644 docs/CNAME delete mode 100644 docs/advanced/brightness-modes.md delete mode 100644 docs/advanced/manual-control.md delete mode 100644 docs/advanced/sleep-mode.md delete mode 100644 docs/assets/logo.png delete mode 100644 docs/assets/stylesheets/extra.css delete mode 100644 docs/automation-examples.md delete mode 100644 docs/configuration.md delete mode 100644 docs/getting-started.md delete mode 100644 docs/index.md delete mode 100644 docs/overrides/partials/integrations/analytics/custom.html delete mode 100755 docs/run_markdown_code_runner.py delete mode 100644 docs/see-also.md delete mode 100644 docs/services.md delete mode 100644 docs/troubleshooting.md delete mode 100755 scripts/update-generated-content delete mode 100644 zensical.toml diff --git a/.github/update-services.py b/.github/update-services.py deleted file mode 100644 index 9ea1860f..00000000 --- a/.github/update-services.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Creates a services.yaml file with the latest docs.""" - -import sys -from pathlib import Path - -import yaml - -sys.path.append(str(Path(__file__).parent.parent)) - -from custom_components.adaptive_lighting import const - -services_filename = Path("custom_components") / "adaptive_lighting" / "services.yaml" -with open(services_filename) as f: # noqa: PTH123 - services = yaml.safe_load(f) - -for service_name, dct in services.items(): - _docs = {"set_manual_control": const.DOCS_MANUAL_CONTROL, "apply": const.DOCS_APPLY} - alternative_docs = _docs.get(service_name, const.DOCS) - for field_name, field in dct["fields"].items(): - description = alternative_docs.get(field_name, const.DOCS[field_name]) - field["description"] = description - -comment = "# This file is auto-generated by .github/update-services.py." - -with services_filename.open("w") as f: - f.write(comment + "\n") - yaml.dump(services, f, sort_keys=False, width=1000, allow_unicode=True) diff --git a/.github/update-strings.py b/.github/update-strings.py deleted file mode 100644 index bfb329a0..00000000 --- a/.github/update-strings.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Update strings.json and en.json from const.py.""" - -import json -import sys -from pathlib import Path - -import homeassistant.helpers.config_validation as cv -import yaml - -sys.path.append(str(Path(__file__).parent.parent)) - -from custom_components.adaptive_lighting import const - -folder = Path("custom_components") / "adaptive_lighting" -strings_fname = folder / "strings.json" -en_fname = folder / "translations" / "en.json" -with strings_fname.open() as f: - strings = json.load(f) - -# Set "options" -data = {} -data_description = {} -for k, _, typ in const.VALIDATION_TUPLES: - desc = const.DOCS[k] - if len(desc) > 40 and typ not in (bool, cv.entity_ids): - data[k] = k - data_description[k] = desc - else: - data[k] = f"{k}: {desc}" -strings["options"]["step"]["init"]["data"] = data -strings["options"]["step"]["init"]["data_description"] = data_description - -# Set "services" -services_filename = Path("custom_components") / "adaptive_lighting" / "services.yaml" -with open(services_filename) as f: # noqa: PTH123 - services = yaml.safe_load(f) -services_json = {} -for service_name, dct in services.items(): - services_json[service_name] = { - "name": service_name, - "description": dct["description"], - "fields": {}, - } - for field_name, field in dct["fields"].items(): - services_json[service_name]["fields"][field_name] = { - "description": field["description"], - "name": field_name, - } -strings["services"] = services_json - -# Write changes to strings.json -with strings_fname.open("w") as f: - json.dump(strings, f, indent=2, ensure_ascii=False) - f.write("\n") - -# Sync changes from strings.json to en.json -with en_fname.open() as f: - en = json.load(f) - -en["config"]["step"]["user"] = strings["config"]["step"]["user"] -en["options"]["step"]["init"]["data"] = data -en["options"]["step"]["init"]["data_description"] = data_description -en["options"]["step"]["init"]["description"] = strings["options"]["step"]["init"][ - "description" -] -en["services"] = services_json - -with en_fname.open("w") as f: - json.dump(en, f, indent=2, ensure_ascii=False) - f.write("\n") diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index d359b54a..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Documentation - -on: - push: - branches: [main] - pull_request: - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.14.2' - - - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 - - - name: Install dependencies - run: uv sync --group docs - - - name: Build documentation - run: uv run zensical build - - - name: Build webapp (simulator) - run: uv run shinylive export webapp webapp-site - - - name: Integrate webapp into docs - run: | - # Copy webapp into docs site at /simulator/ - mkdir -p site/simulator - cp -r webapp-site/* site/simulator/ - echo "Webapp integrated at site/simulator/" - - - name: Upload artifact - uses: actions/upload-pages-artifact@v5 - with: - path: ./site - - deploy: - if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5 diff --git a/.github/workflows/markdown-code-runner.yml b/.github/workflows/markdown-code-runner.yml deleted file mode 100644 index 4ea1bb81..00000000 --- a/.github/workflows/markdown-code-runner.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: markdown-code-runner - -on: - push: - branches: - - main - pull_request: - -jobs: - markdown-code-runner: - runs-on: ubuntu-latest - steps: - - name: Check out code from GitHub - uses: actions/checkout@v6 - with: - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - ref: ${{ github.head_ref || github.ref }} - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.14.2" - - - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 - - - name: Update generated content - run: ./scripts/update-generated-content - - - name: Check for changes - run: | - if [ -n "$(git status --porcelain)" ]; then - if [ "${{ github.event_name }}" == "pull_request" ]; then - echo "::error::Auto-generated files are not up to date. Please run './scripts/update-generated-content' locally and push the changes." - exit 1 - else - echo "Changes detected, committing and pushing..." - git add -u . - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git commit -m "Update auto-generated content" - git pull --rebase - git push - fi - else - echo "No changes detected." - fi diff --git a/.gitignore b/.gitignore index 0db5bc61..c5fee036 100644 --- a/.gitignore +++ b/.gitignore @@ -68,9 +68,6 @@ instance/ # Scrapy stuff: .scrapy -# Sphinx documentation -docs/_build/ - # PyBuilder target/ diff --git a/CLAUDE.md b/CLAUDE.md index 40e8a183..c17552f8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,7 +33,6 @@ Python tooling is `uv` (not `pip` or `poetry`). All commands assume an activated | Run a single test | `uv run pytest tests/test_config_flow.py::test_options_flow_init` | | Lint everything (pre-commit) | `./scripts/lint` | | Run HA locally with the custom component loaded | `./scripts/develop` (boots HA on `localhost:8123` from `./config/`) | -| Update generated docs/content in README | `./scripts/update-generated-content` | | Refresh test matrix | `./scripts/update-test-matrix.py` | Ruff config lives in `.ruff.toml`. Pre-commit hooks in `.pre-commit-config.yaml`. Both enforce on push via `./scripts/lint`. diff --git a/README.md b/README.md index 409eddfe..f1fb85c0 100644 --- a/README.md +++ b/README.md @@ -125,697 +125,191 @@ > - entity: sensor.dining_mvp_output_color_temp > - entity: sensor.dining_mvp_sun_elevation > ``` -> -> The rest of this README is from upstream and may describe features that no -> longer exist in this fork. See `CHANGELOG.md` for the canonical list of -> CDiT-specific changes. --- -# 🌞 Adaptive Lighting: Enhance Your Home's Atmosphere with Smart, Sun-Synchronized Lighting 🌙 +# 🌞 Adaptive Lighting (CDiT fork) -logo +A Home Assistant custom component that adapts light brightness and color +temperature to a sun-driven tanh curve. Each profile owns a set of lights +and exposes three switches, four live-tunable sliders, and three graphable +sensors per profile. -[Adaptive Lighting](https://github.com/basnijholt/adaptive-lighting) is a custom component for [Home Assistant](https://www.home-assistant.io/) that intelligently adjusts the brightness and color of your lights 💡 based on the sun's position, while still allowing for manual control. +## Install -Download and install directly through [HACS (Home Assistant Community Store)](https://hacs.xyz/): +Via HACS as a custom repository (this fork is not in the HACS default index): -[![Open your Home Assistant instance and open the Adaptive Lighting integration inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=basnijholt&repository=adaptive-lighting&category=integration) +1. HACS → Integrations → ⋮ → Custom repositories. +2. Add `https://github.com/CaseyRo/adaptive-lighting` as an **Integration**. +3. Install **Adaptive Lighting (CDiT)**. +4. Restart Home Assistant. -By automatically adapting the settings of your lights throughout the day, Adaptive Lighting helps maintain your natural circadian rhythm 😴, which can lead to improved sleep, mood, and overall well-being. Experience cooler color temperatures at noon, gradually transitioning to warmer colors at sunset and sunrise. +Or manually copy `custom_components/adaptive_lighting/` into your HA +config's `custom_components/` directory and restart. -In addition to its regular mode, Adaptive Lighting also offers a "sleep mode" 🌜 which sets your lights to minimal brightness and a very warm color, perfect for winding down at night. +## Setup -> 🌈 Visualize Adaptive Lighting's settings with the [_🌞 Adaptive Lighting Simulator WebApp 🌛_](https://basnijholt.github.io/adaptive-lighting) +UI-only — there is no YAML schema in this fork. Existing +`adaptive_lighting:` blocks in `configuration.yaml` are ignored. -https://github.com/basnijholt/adaptive-lighting/assets/6897215/68908f7d-fbf1-4991-98ce-3f2af6df996f +1. Settings → Devices & Services → **Add Integration** → **Adaptive Lighting**. +2. Name the profile (e.g., `Kitchen`). +3. Click ⚙️ Configure on the new entry to open the sectioned options dialog + and pick lights, sun-time entities, and curve bounds. -[[ToC](#books-table-of-contents)] +A profile can be **edited** any time via the same ⚙️ Configure menu — saving +reloads the integration via `OptionsFlowWithReload` (no restart needed). - -## :bulb: Features +## What each profile creates -When initially turning on a light that is controlled by Adaptive Lighting, the `light.turn_on` service call is intercepted, and the light's brightness and color are automatically adjusted based on the sun's position. -After that, the light's brightness and color are automatically adjusted at a regular interval. +For a profile named `Kitchen`: -Adaptive Lighting provides four switches (using "living_room" as an example component name): +### Switches (3) -- `switch.adaptive_lighting_living_room`: Turn Adaptive Lighting on or off and view current light settings through its attributes. -- `switch.adaptive_lighting_sleep_mode_living_room`: Activate "sleep mode" 😴 and set custom sleep_brightness and sleep_color_temp. -- `switch.adaptive_lighting_adapt_brightness_living_room`: Enable or disable brightness adaptation 🔆 for supported lights. -- `switch.adaptive_lighting_adapt_color_living_room`: Enable or disable color adaptation 🌈 for supported lights. - +| Entity | Role | +|---|---| +| `switch.kitchen` | Master on/off — when off, no adaptation runs | +| `switch.kitchen_brightness` | Toggle brightness adaptation only | +| `switch.kitchen_color` | Toggle color-temperature adaptation only | - -### :control_knobs: Regain Manual Control +### Number entities (4) — live-tunable sliders -Adaptive Lighting is designed to automatically detect when you or another source (e.g., automation) manually changes light settings 🕹️. -When this occurs, the affected light is marked as "manually controlled," and Adaptive Lighting will not make further adjustments until the light is turned off and back on or reset using the `adaptive_lighting.set_manual_control` service call. -This feature is available when `take_over_control` is enabled. +| Entity | Range | Step | Persist | +|---|---|---|---| +| `number.kitchen_min_brightness` | 1–100 % | 1 | RestoreNumber | +| `number.kitchen_max_brightness` | 1–100 % | 1 | RestoreNumber | +| `number.kitchen_min_color_temp` | 1000–10000 K | 100 | RestoreNumber | +| `number.kitchen_max_color_temp` | 1000–10000 K | 100 | RestoreNumber | -Additionally, enabling `detect_non_ha_changes` allows Adaptive Lighting to detect all state changes, including those made outside of Home Assistant, by comparing the light's state to its previously used settings. -The `adaptive_lighting.manual_control` event is fired when a light is marked as "manually controlled," allowing for integration with automations 🤖. +Slider position is the runtime truth — the curve reads from the entity on +every tick. No integration reload on slider change. Values survive HA +restarts. The options dialog re-seeds these values from the entities on +open and overwrites them on save (explicit gesture wins). -> ⚠️ **_Caution: Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Disable `detect_non_ha_changes` if you encounter such issues._** - +### Sensor entities (3) — graphable outputs -## :books: Table of Contents +| Entity | Unit | Source | +|---|---|---| +| `sensor.kitchen_output_brightness` | % | `self._settings["brightness_pct"]` | +| `sensor.kitchen_output_color_temp` | K | `self._settings["color_temp_kelvin"]` | +| `sensor.kitchen_sun_elevation` | ° | `sun.sun.attributes.elevation` | - - +All three are `state_class: measurement` so HA's recorder graphs them as +continuous numeric series. Drop them into `apexcharts-card`, +`mini-graph-card`, or the built-in History panel. -- [:gear: Configuration](#gear-configuration) - - [:memo: Options](#memo-options) - - [:hammer_and_wrench: Services](#hammer_and_wrench-services) - - [`adaptive_lighting.apply`](#adaptive_lightingapply) - - [`adaptive_lighting.set_manual_control`](#adaptive_lightingset_manual_control) - - [`adaptive_lighting.change_switch_settings`](#adaptive_lightingchange_switch_settings) -- [:robot: Automation examples](#robot-automation-examples) -- [Additional Information](#additional-information) -- [:sos: Troubleshooting](#sos-troubleshooting) - - [:exclamation: Common Problems & Solutions](#exclamation-common-problems--solutions) - - [:bulb: Lights Not Responding or Turning On by Themselves](#bulb-lights-not-responding-or-turning-on-by-themselves) - - [:signal_strength: WiFi Networks](#signal_strength-wifi-networks) - - [:spider_web: Zigbee, Z-Wave, and Other Mesh Networks](#spider_web-zigbee-z-wave-and-other-mesh-networks) - - [:rainbow: Light Colors Not Matching](#rainbow-light-colors-not-matching) - - [:bulb: Bulb-Specific Issues](#bulb-bulb-specific-issues) -- [:bar_chart: Graphs!](#bar_chart-graphs) - - [:sunny: Sun Position](#sunny-sun-position) - - [:thermometer: Color Temperature](#thermometer-color-temperature) - - [:high_brightness: Brightness](#high_brightness-brightness) - - [While using `transition_until_sleep: true`](#while-using-transition_until_sleep-true) - - [Custom brightness ramps using `brightness_mode` with `"linear"` and `"tanh"`](#custom-brightness-ramps-using-brightness_mode-with-linear-and-tanh) -- [:eyes: See also](#eyes-see-also) -- [:busts_in_silhouette: Contributors](#busts_in_silhouette-contributors) -- [Translating Adaptive Lighting](#translating-adaptive-lighting) +The master switch also exposes the curve's internal values as attributes +(`brightness_pct`, `color_temp_kelvin`, synthetic `sun_position` in +[-1, +1], etc.) — those are unchanged from prior versions for diagnostic +use. - +## Tuning live -## :gear: Configuration +Three ways to change what the curve does — pick the right gesture for the +right scope: -Adaptive Lighting supports configuration through both YAML and the frontend (**Settings** -> **Devices and Services** -> **Adaptive Lighting**, **Adaptive Lighting** -> **Options**), with identical option names in both methods. +| Gesture | Scope | Persistence | +|---|---|---| +| Drag a number slider on a dashboard | Live, this profile, these bounds | RestoreNumber (survives restart) | +| Open the options dialog and save | All profile settings | `entry.options` (canonical) | +| Toggle the master / adapt switches | On/off for this profile | RestoreEntity (survives restart) | + +## Services + +This fork exposes two services. The upstream +`adaptive_lighting.set_manual_control` service is **removed** (take-over-control +was dropped — see Differences from upstream). + +### `adaptive_lighting.apply` + +Apply this profile's current curve values to its lights immediately. Useful +in automations that turn on a scene and want AL to take over. ```yaml -# Example configuration.yaml entry -adaptive_lighting: +service: adaptive_lighting.apply +data: + entity_id: switch.kitchen lights: - - light.living_room_lights + - light.kitchen_ceiling + turn_on_lights: true # also flip the lights on if currently off + transition: 2 ``` -Note: If you plan to strictly use the UI, the `adaptive_lighting:` entry must still be added to the YAML. -Transform your home's atmosphere with Adaptive Lighting 🏠, and experience the benefits of intelligent, sun-synchronized lighting today! +### `adaptive_lighting.change_switch_settings` -### :memo: Options - -All of the configuration options are listed below, along with their default values. -The YAML and frontend configuration methods support all of the options listed below. - - - - - - - - -| Variable name | Description | Default | Type | -|:-------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------|:----------------------------------------| -| `lights` | List of light entity_ids to be controlled (may be empty). 🌟 | `[]` | list of `entity_id`s | -| `interval` | Frequency to adapt the lights, in seconds. 🔄 | `90` | `int > 0` | -| `transition` | Duration of transition when lights change, in seconds. 🕑 | `45` | `float` 0-6553 | -| `initial_transition` | Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️ | `1` | `float` 0-6553 | -| `min_brightness` | Minimum brightness percentage. 💡 | `1` | `int` 1-100 | -| `max_brightness` | Maximum brightness percentage. 💡 | `100` | `int` 1-100 | -| `min_color_temp` | Warmest color temperature in Kelvin. 🔥 | `2000` | `int` 1000-10000 | -| `max_color_temp` | Coldest color temperature in Kelvin. ❄️ | `5500` | `int` 1000-10000 | -| `prefer_rgb_color` | Whether to prefer RGB color adjustment over light color temperature when possible. 🌈 | `False` | `bool` | -| `sleep_brightness` | Brightness percentage of lights in sleep mode. 😴 | `1` | `int` 1-100 | -| `sleep_rgb_or_color_temp` | Use either `"rgb_color"` or `"color_temp"` in sleep mode. 🌙 | `color_temp` | one of `['color_temp', 'rgb_color']` | -| `sleep_color_temp` | Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is `color_temp`) in Kelvin. 😴 | `1000` | `int` 1000-10000 | -| `sleep_rgb_color` | RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is "rgb_color"). 🌈 | `[255, 56, 0]` | RGB color | -| `sleep_transition` | Duration of transition when "sleep mode" is toggled in seconds. 😴 | `1` | `float` 0-6553 | -| `transition_until_sleep` | When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙 | `False` | `bool` | -| `sunrise_time` | Set a fixed time (HH:MM:SS) for sunrise. 🌅 | `None` | `str` | -| `min_sunrise_time` | Set the earliest virtual sunrise time (HH:MM:SS), allowing for later sunrises. 🌅 | `None` | `str` | -| `max_sunrise_time` | Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier sunrises. 🌅 | `None` | `str` | -| `sunrise_offset` | Adjust sunrise time with a positive or negative offset in seconds. ⏰ | `0` | `int` | -| `sunset_time` | Set a fixed time (HH:MM:SS) for sunset. 🌇 | `None` | `str` | -| `min_sunset_time` | Set the earliest virtual sunset time (HH:MM:SS), allowing for later sunsets. 🌇 | `None` | `str` | -| `max_sunset_time` | Set the latest virtual sunset time (HH:MM:SS), allowing for earlier sunsets. 🌇 | `None` | `str` | -| `sunset_offset` | Adjust sunset time with a positive or negative offset in seconds. ⏰ | `0` | `int` | -| `brightness_mode` | Brightness mode to use. Possible values are `default`, `linear`, and `tanh` (uses `brightness_mode_time_dark` and `brightness_mode_time_light`). 📈 | `default` | one of `['default', 'linear', 'tanh']` | -| `brightness_mode_time_dark` | (Ignored if `brightness_mode='default'`) The duration in seconds to ramp up/down the brightness before/after sunrise/sunset. 📈📉 | `900` | `int` | -| `brightness_mode_time_light` | (Ignored if `brightness_mode='default'`) The duration in seconds to ramp up/down the brightness after/before sunrise/sunset. 📈📉. | `3600` | `int` | -| `take_over_control` | Pause adaptation of individual lights and hand over (manual) control to other sources that issue `light.turn_on` calls for lights that are on. 🔒 | `True` | `bool` | -| `take_over_control_mode` | The adaptation pausing mode when other sources change brightness and/or color of lights. `pause_all` always pauses both brightness and color adaptation. `pause_changed` pauses the adaptation of only the changed attributes and continues adapting unchanged attributes, e.g., continues color adaptation when only brightness was changed. | `pause_all` | one of `['pause_all', 'pause_changed']` | -| `detect_non_ha_changes` | Detects and halts adaptations for non-`light.turn_on` state changes. Needs `take_over_control` enabled. 🕵️ Caution: ⚠️ Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Note that this calls `homeassistant.update_entity` every `interval`! Disable this feature if you encounter such issues. | `False` | `bool` | -| `autoreset_control_seconds` | Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️ | `0` | `int` 0-31536000 | -| `only_once` | Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄 | `False` | `bool` | -| `adapt_only_on_bare_turn_on` | When turning lights on initially. If set to `true`, AL adapts only if `light.turn_on` is invoked without specifying color or brightness. ❌🌈 This e.g., prevents adaptation when activating a scene and marks the light as manually controlled. If `false`, AL adapts regardless of the presence of color or brightness in the initial `service_data`. Needs `take_over_control` enabled. 🕵️ | `False` | `bool` | -| `separate_turn_on_commands` | Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀 | `False` | `bool` | -| `send_split_delay` | Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️ | `0` | `int` 0-10000 | -| `adapt_delay` | Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️ | `0` | `float > 0` | -| `skip_redundant_commands` | Skip sending adaptation commands whose target state already equals the light's known state. Minimizes network traffic and improves the adaptation responsivity in some situations. 📉Disable if physical light states get out of sync with HA's recorded state. | `False` | `bool` | -| `intercept` | Intercept and adapt `light.turn_on` calls to enabling instantaneous color and brightness adaptation. 🏎️ Disable for lights that do not support `light.turn_on` with color and brightness. | `True` | `bool` | -| `multi_light_intercept` | Intercept and adapt `light.turn_on` calls that target multiple lights. ➗⚠️ This might result in splitting up a single `light.turn_on` call into multiple calls, e.g., when lights are in different switches. Requires `intercept` to be enabled. | `True` | `bool` | -| `include_config_in_attributes` | Show all options as attributes on the switch in Home Assistant when set to `true`. 📝 | `False` | `bool` | - - - - -Full example: +Change one or more of this profile's settings at runtime. The change +persists until the next options-flow save or HA restart. ```yaml -# Example configuration.yaml entry -adaptive_lighting: -- name: "default" - lights: [] - prefer_rgb_color: false - transition: 45 - initial_transition: 1 - interval: 90 - min_brightness: 1 - max_brightness: 100 - min_color_temp: 2000 - max_color_temp: 5500 - sleep_brightness: 1 - sleep_color_temp: 1000 - sunrise_time: "08:00:00" # override the sunrise time - sunrise_offset: - sunset_time: - sunset_offset: 1800 # in seconds or '00:30:00' - take_over_control: true - detect_non_ha_changes: false - only_once: false - -``` - - -### :hammer_and_wrench: Services - -#### `adaptive_lighting.apply` - -`adaptive_lighting.apply` applies Adaptive Lighting settings to lights on demand. - - - - - - - - -| Service data attribute | Description | Required | Type | -|:-------------------------|:--------------------------------------------------------------------------------------|:-----------|:---------------------| -| `entity_id` | The `entity_id` of the switch with the settings to apply. 📝 | ✅ | list of `entity_id`s | -| `lights` | A light (or list of lights) to apply the settings to. 💡 | ❌ | list of `entity_id`s | -| `transition` | Duration of transition when lights change, in seconds. 🕑 | ❌ | `float` 0-6553 | -| `adapt_brightness` | Whether to adapt the brightness of the light. 🌞 | ❌ | bool | -| `adapt_color` | Whether to adapt the color on supporting lights. 🌈 | ❌ | bool | -| `prefer_rgb_color` | Whether to prefer RGB color adjustment over light color temperature when possible. 🌈 | ❌ | bool | -| `turn_on_lights` | Whether to turn on lights that are currently off. 🔆 | ❌ | bool | - - -#### `adaptive_lighting.set_manual_control` - -`adaptive_lighting.set_manual_control` can mark (or unmark) whether a light is "manually controlled", meaning that when a light has `manual_control`, the light is not adapted. - - - - - - - - -| Service data attribute | Description | Required | Type | -|:-------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------|:-----------------------------------------| -| `entity_id` | The `entity_id` of the switch in which to (un)mark the light as being `manually controlled`. 📝 | ✅ | list of `entity_id`s | -| `lights` | entity_id(s) of lights, if not specified, all lights in the switch are selected. 💡 | ❌ | list of `entity_id`s | -| `manual_control` | Whether to add ("true") or remove ("false") all adapted attributes of the light from the "manual_control" list, or the name of an attribute for selective addition. 🔒 | ❌ | bool or one of `['brightness', 'color']` | - - - - -#### `adaptive_lighting.change_switch_settings` - -`adaptive_lighting.change_switch_settings` (new in 1.7.0) Change any of the above configuration options of Adaptive Lighting (such as `sunrise_time` or `prefer_rgb_color`) with a service call directly from your script/automation. - -> [!WARNING] -> These settings will **not** be written to your config and will be reset on restart of Home Assistant! You can see the current settings in the `switch.adaptive_lighting_XXX` attributes if `include_config_in_attributes` is enabled. - -| Service data attribute | Required | Description | -| --------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `use_defaults` | ❌ | (default: `current` for current settings) Choose from `factory`, `configuration`, or `current` to reset variables not being set with this service call. `current` leaves them as they are, `configuration` resets to initial startup values, `factory` resets to default values listed in the documentation. | -| **all other keys** (except the ones in the table below ⚠️) | ❌ | See the table below for disallowed keys. | - -The following keys are disallowed: - -| **DISALLOWED** service data | Description | -| --------------------------- | ----------------------------------------------------------------------------------------------- | -| `entity_id` | You cannot change the switch's `entity_id`, as it has already been registered. | -| `lights` | You may call `adaptive_lighting.apply` with your lights or create a new config instead. | -| `name` | You can rename your switch's display name in Home Assistant's UI. | -| `interval` | The interval is used only once when the config loads. A config change and restart are required. | - - - -## :robot: Automation examples - -
-Reset the manual_control status of a light after an hour. - -```yaml -- alias: "Adaptive lighting: reset manual_control after 1 hour" - mode: parallel - trigger: - platform: event - event_type: adaptive_lighting.manual_control - variables: - light: "{{ trigger.event.data.entity_id }}" - switch: "{{ trigger.event.data.switch }}" - action: - - delay: "01:00:00" - - condition: template - value_template: "{{ light in state_attr(switch, 'manual_control') }}" - - service: adaptive_lighting.set_manual_control - data: - entity_id: "{{ switch }}" - lights: "{{ light }}" - manual_control: false +service: adaptive_lighting.change_switch_settings +data: + entity_id: switch.kitchen + use_defaults: current # | factory | configuration + min_brightness: 20 # any subset of options-flow fields + max_color_temp: 4500 ``` -
+The full per-service argument list lives in `services.yaml` and is +rendered into the HA Developer Tools → Services UI. -
-Toggle multiple Adaptive Lighting switches to "sleep mode" using an input_boolean.sleep_mode. +## Sun source -```yaml -- alias: "Adaptive lighting: toggle 'sleep mode'" - trigger: - - platform: state - entity_id: input_boolean.sleep_mode - - platform: homeassistant - event: start # in case the states aren't properly restored - variables: - sleep_mode: "{{ states('input_boolean.sleep_mode') }}" - action: - service: "switch.turn_{{ sleep_mode }}" - entity_id: - - switch.adaptive_lighting_sleep_mode_living_room - - switch.adaptive_lighting_sleep_mode_bedroom +The curve is anchored at two `device_class: timestamp` sensors — +`sunrise_entity` and `sunset_entity` — configured in the options dialog. + +**Defaults**: HA's built-in `sensor.sun_next_rising` and +`sensor.sun_next_setting`. These are always present. + +**Recommended**: install [Sun2](https://github.com/pnbruckner/ha-sun2) and +point at `sensor.sun2_dawn` (civil twilight) or `sensor.sun2_astro_dawn` +(astronomical twilight) for a smoother fade from deep night to dawn. The +integration reads `hass.states.get().state` and parses the +timestamp; any sensor with `device_class: timestamp` works. + +The new `sensor._sun_elevation` reads from HA's built-in `sun.sun` +entity's `elevation` attribute — independent of which timestamp source you +picked for the curve. + +## Development + +This is a CDiT-internal fork; PRs from outside are not the workflow. If +you want to dig in: + +| Task | Command | +|---|---| +| Boot a dev HA with the component loaded | `./scripts/develop` | +| Run the test suite | `uv run pytest` | +| Run a specific test | `uv run pytest tests/test_sensor_platform.py` | +| Lint | `./scripts/lint` | +| Validate active OpenSpec changes | `openspec validate --strict` | + +Architecture, conventions, OpenSpec workflow, and the upstream-sync recipe +are documented in [`CLAUDE.md`](CLAUDE.md). The `openspec/` tree holds the +proposal/design/spec/tasks artifacts for every non-trivial change; archived +changes under `openspec/changes/archive/` are the historical record. + +## Sync with upstream + +```bash +git fetch upstream +git merge upstream/main # or rebase, depending on local policy +# resolve conflicts under custom_components/adaptive_lighting/ ``` -Set your sunrise and sunset time based on your alarm. The below script sets sunset_time exactly 12 hours after the custom sunrise time. +Remote setup: -```yaml -iphone_carly_wakeup: - alias: iPhone Carly Wakeup - sequence: - - condition: state - entity_id: input_boolean.carly_iphone_wakeup - state: "off" - - service: input_datetime.set_datetime - target: - entity_id: input_datetime.carly_iphone_wakeup - data: - time: '{{ now().strftime("%H:%M:%S") }}' - - service: input_boolean.turn_on - target: - entity_id: input_boolean.carly_iphone_wakeup - - repeat: - count: > - {{ (states.switch - | map(attribute="entity_id") - | select(">","switch.adaptive_lighting_al_") - | select("<", "switch.adaptive_lighting_al_z") - | join(",") - ).split(",") | length }} - sequence: - - service: adaptive_lighting.change_switch_settings - data: - entity_id: switch.adaptive_lighting_al_den_ceilingfan_lights - sunrise_time: '{{ now().strftime("%H:%M:%S") }}' - sunset_time: > - {{ (as_timestamp(now()) + 12*60*60) | timestamp_custom("%H:%M:%S") }} - - service: script.turn_on - target: - entity_id: script.run_wakeup_routine - - service: input_boolean.turn_off - target: - entity_id: - - input_boolean.carly_iphone_winddown - - input_boolean.carly_iphone_bedtime - - service: input_datetime.set_datetime - target: - entity_id: input_datetime.wakeup_time - data: - time: '{{ now().strftime("%H:%M:%S") }}' - - service: script.adaptive_lighting_disable_sleep_mode - mode: queued - icon: mdi:weather-sunset - max: 10 +``` +origin git@github.com:CaseyRo/adaptive-lighting.git (this fork, read-write) +upstream https://github.com/basnijholt/adaptive-lighting.git (read-only) ``` -
- +## Credits -## Additional Information +This fork is a thin opinion on top of +[@basnijholt](https://github.com/basnijholt)'s work and the 130+ upstream +contributors. The curve math, the `light.turn_on` interception, the HACS +plumbing — that's *their* work. If you've never starred the upstream repo, +do it now: [github.com/basnijholt/adaptive-lighting](https://github.com/basnijholt/adaptive-lighting). -For more details on adding the integration and setting options, refer to the [documentation of the PR](https://deploy-preview-14877--home-assistant-docs.netlify.app/integrations/adaptive_lighting/) and [this video tutorial on Reddit](https://www.reddit.com/r/homeassistant/comments/jabhso/ha_has_it_before_apple_has_even_finished_it_i/). - -Adaptive Lighting was initially inspired by @claytonjn's [hass-circadian\_lighting](https://github.com/claytonjn/hass-circadian_lighting), but has since been entirely rewritten and expanded with new features. - -## :sos: Troubleshooting - - -Encountering issues? Enable debug logging in your `configuration.yaml`: - -```yaml -logger: - default: warning - logs: - custom_components.adaptive_lighting: debug -``` - -After the issue occurs, create a new issue report with the log (`/config/home-assistant.log`). - - - -### :exclamation: Common Problems & Solutions - -#### :bulb: Lights Not Responding or Turning On by Themselves - -Adaptive Lighting sends more commands to lights than a typical human user would. If your light control network is unhealthy, you may experience: - -- Laggy manual commands (e.g., turning lights on or off). -- Unresponsive lights. -- Home Assistant reporting incorrect light states, causing Adaptive Lighting to inadvertently turn lights back on. - -Most issues that appear to be caused by Adaptive Lighting are actually due to unrelated problems. -Addressing these issues will significantly improve your Home Assistant experience. - -In case lights are suddenly turning on by themselves, this is most likely due to the light incorrectly reporting an "on" state to Home Assistant, leading to an undesired Adaptive Lighting action. -To prevent adapting in cases *where the state of the light is suddenly "on" and only adapt if there is an associated `light.turn_on` service call*, set `detect_non_ha_changes: false`. - -#### :signal_strength: WiFi Networks - -Ensure your light bulbs have a strong WiFi connection. If the signal strength is less than -70dBm, the connection may be weak and prone to dropping messages. - -#### :spider_web: Zigbee, Z-Wave, and Other Mesh Networks - -Mesh networks typically require powered devices to act as routers, relaying messages back to the central coordinator (the radio connected to Home Assistant). -Most modern lights function as routers, very early models may not. -If devices become unresponsive or fail to respond to commands, Adaptive Lighting can exacerbate the issue. -Use network maps (available in ZHA, zigbee2mqtt, deCONZ, and ZWaveJS UI) to evaluate your network health. -Smart plugs can be an affordable way to add more routers to your network. - -For most Zigbee networks, **using groups is essential for optimal performance**. -For example, if you want to use Adaptive Lighting in a hallway with six bulbs, adding each bulb individually to the Adaptive Lighting configuration could overwhelm the network with commands. -Instead, create a group in your Zigbee software (not a regular Home Assistant group) and add that single group to the Adaptive Lighting configuration. -This sends a single broadcast command to adjust all bulbs, improving response times and keeping the bulbs in sync. - -As a rule of thumb, if you always control lights together (e.g., bulbs in a ceiling fixture), they should be in a Zigbee group. -Expose only the group (not individual bulbs) in Home Assistant Dashboards and external systems like Google Home or Apple HomeKit. - -> :warning: **If you control lights individually, `manual_control` cannot behave correctly! If you need to control lights individually as well, use a [Home Assistant Light Group](https://www.home-assistant.io/integrations/group/).** - -#### :rainbow: Light Colors Not Matching - -Bulbs from different manufacturers or models may have varying color temperature specifications. For instance, if you have two Adaptive Lighting configurations—one with only Philips Hue White Ambiance bulbs and another with a mix of Philips Hue White Ambiance and Sengled bulbs—the Philips Hue bulbs may appear to have different color temperatures despite having identical settings. - -To resolve this: - -1. Include only bulbs of the same make and model in a single Adaptive Lighting configuration. -2. Rearrange bulbs so that different color temperatures are not visible simultaneously. - -#### :bulb: Bulb-Specific Issues - -These lights are known to exhibit disadvantageous behaviour due to firmware bugs, insufficient functionality, or hardware limitations: - -- [Sengled Z01-A19NAE26](https://www.zigbee2mqtt.io/devices/Z01-A19NAE26.html#sengled-z01-a19nae26) - - Unexpected turn-ons: If Adaptive Lighting sends a long transition time (like the default 45 seconds), and the bulb is turned off during that time, it may turn back on after approximately 10 seconds to continue the transition command. Since the bulb is turning itself on, there will be no obvious trigger in Home Assistant or other logs indicating the cause of the light turning on. To fix this, set a much shorter `transition` time, such as 1 second. - - Heat sensitivity: Additionally, these bulbs may perform poorly in enclosed "dome" style ceiling lights, particularly when hot. While most LEDs (even non-smart ones) state in the fine print that they do not support working in enclosed fixtures, in practice, more expensive bulbs like Philips Hue generally perform better. To resolve this issue, move the problematic bulbs to open-air fixtures. -- Ikea Tradfri bulbs/drivers (and related Ikea smart light products) - - Unsupported simultaneous transition of brightness and color: When receiving such a command, they switch the brightness instantly and only transition the color. To get smooth transitions of both brightness and color, enable `separate_turn_on_commands`. - - Unresponsiveness during color transitions: No other commands are processed during an ongoing color transition, e.g., turn-off commands are ignored and lights stay on despite being reported as off to Home Assistant. The default config with long transitions thus results in long periods of unresponsiveness. To work around this, disable transitions by setting `transition` to `0`, and increase the adaptation frequency by setting `interval` to a short time, e.g., `15` seconds, to retain the impression of smooth continuous adaptations. Keeping the `initial_transition` is recommended for a smooth fade-in (lights are usually not turned off momentarily after being turned on, in which case a short period of unresponsiveness is tolerable). - - - -## :bar_chart: Graphs! -These graphs were generated using the values calculated by the Adaptive Lighting sensor/switch(es). - -### :sunny: Sun Position -![cl_percent|690x131](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/6/5/657ff98beb65a94598edeb4bdfd939095db1a22c.PNG) - -### :thermometer: Color Temperature -![cl_color_temp|690x129](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/9/59e84263cbecd8e428cb08777a0413672c48dfcd.PNG) - -### :high_brightness: Brightness -![cl_brightness|690x130](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/8/58ebd994b62a8b1abfb3497a5288d923ff4e2330.PNG) - -### While using `transition_until_sleep: true` -![image](https://user-images.githubusercontent.com/2219836/228949675-f9699624-8abc-466c-bb04-250ce0f495b8.png) - - - -### Custom brightness ramps using `brightness_mode` with `"linear"` and `"tanh"` - -
-Enhance your control over brightness transitions during sunrise and sunset with brightness_mode (click here to learn more 🧠). - -With Adaptive Lighting, you can set a `brightness_mode` to specify how the brightness changes during sunrise and sunset. The `brightness_mode` can be set to `"default"` ([as illustrated in other graphs above](#high_brightness-brightness)), `"linear"`, or `"tanh"`. If you choose to deviate from the `"default"` mode, you can adjust `brightness_mode_time_dark` and `brightness_mode_time_light` to further customize the lighting transitions. - -When `brightness_mode` is set to `"linear"`: - -- During **_sunset_**, the brightness begins to gradually decrease from `max_brightness` starting at `time=sunset_time - brightness_mode_time_light`, until it reaches `min_brightness` at `time=sunset_time + brightness_mode_time_dark`. -- During **_sunrise_**, the brightness begins to gradually increase from `min_brightness` starting at `time=sunrise_time - brightness_mode_time_dark`, until it reaches `max_brightness` at `time=sunrise_time + brightness_mode_time_light`. - -When `brightness_mode` is set to `"tanh"`, it uses the smooth transition of a [hyperbolic tangent function](https://mathworld.wolfram.com/HyperbolicTangent.html): - -- During **_sunset_**, the brightness starts to decrease from 95% of `max_brightness` starting at `time=sunset_time - brightness_mode_time_light`, until it reaches 5% of `min_brightness` at `time=sunset_time + brightness_mode_time_dark`. -- During **_sunrise_**, the brightness starts to increase from 5% of `min_brightness` starting at `time=sunrise_time - brightness_mode_time_dark`, until it reaches 95% of `max_brightness` at `time=sunrise_time + brightness_mode_time_light`. -
- -Notice the values of `brightness_mode_time_light` and `brightness_mode_time_dark` in the text box. -![image](https://github.com/basnijholt/adaptive-lighting/assets/6897215/15143580-13cd-4ab2-a603-89f2b7830afd) -![image](https://github.com/basnijholt/adaptive-lighting/assets/6897215/f61fdac9-6d47-48c9-84ed-cbb451d5de5d) -![image](https://github.com/basnijholt/adaptive-lighting/assets/6897215/e5fc5d27-3c37-4e3d-93d1-6e7cf4b48e7c) -![image](https://github.com/basnijholt/adaptive-lighting/assets/6897215/3dcbdc42-63c4-49df-8651-d2fae53dd08d) - -> Check out the interactive webapp on https://basnijholt.github.io/adaptive-lighting/ to play with the parameters and see how the brightness changes! - - - -## :eyes: See also - -- [*Sleep better with Adaptive Lighting in Home Assistant*](https://wartner.io/sleep-better-with-adaptive-lightning-in-home-assistant/) by Florian Wartner on 2023-02-23 (blog post 📜) -- [*Automatic smart light brightness and color based on the sun*](https://www.youtube.com/watch?v=Rg3zI1Oyk3c) by Home Automation Guy on 2022-08-31 (YouTube video 📺) -- [*Adaptive Lighting Blew My Mind in Home Assistant - How to set it up*](https://www.youtube.com/watch?v=c1cnccmgl3k) by Smart Home Junkie on 2022-06-26 (YouTube video 📺) - - -## :busts_in_silhouette: Contributors - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Bas Nijholt
Bas Nijholt

💻 🚧 🐛
Sven Serlier
Sven Serlier

📖
Will Puckett
Will Puckett

📖
vapescherov
vapescherov

💻
Travis Pew
Travis Pew

📖
Sindre Broch
Sindre Broch

📖
Denis Shulyaka
Denis Shulyaka

💻
@RubenKelevra
@RubenKelevra

📖 💻
Jüri Rebane
Jüri Rebane

🌍
quantumlemur
quantumlemur

💻
Michael Kirsch
Michael Kirsch

💻
Nicholai Nissen
Nicholai Nissen

🌍
Martin Myhrman
Martin Myhrman

🌍
Michel Peterson
Michel Peterson

💻
MangoScango
MangoScango

💻
Lynilia
Lynilia

🌍
LukaszP2
LukaszP2

🌍
Joscha Wagner
Joscha Wagner

🌍
skdzzz
skdzzz

🌍
Simon Gurcke
Simon Gurcke

💻
Sören Beye
Sören Beye

💻
Hudson Brendon
Hudson Brendon

🌍
Gabriel Visser
Gabriel Visser

📖
Gleb
Gleb

🌍
Deleted user
Deleted user

🌍
Avi Miller
Avi Miller

📖 💻
Denys Dovhan
Denys Dovhan

🌍
David Stenbeck
David Stenbeck

📖
Kevin Addeman
Kevin Addeman

💻
covid10
covid10

🌍 💻
Michael Chisholm
Michael Chisholm

💻
Justin Paupore
Justin Paupore

💻
bedaes
bedaes

💻
awashingmachine
awashingmachine

🌍
Clayton Nummer
Clayton Nummer

💻
Robert Crandall
Robert Crandall

💻
Matt Forster
Matt Forster

💻
Mark Niemeyer
Mark Niemeyer

🌍 💻
Elliott Plack
Elliott Plack

📖
ngommers
ngommers

🌍
Andrew Berry
Andrew Berry

📖
Tomáš Valigura
Tomáš Valigura

🌍
Benjamin Auquite
Benjamin Auquite

💻 🐛 🚧
Skyler Carlson
Skyler Carlson

📖
Chris
Chris

💻
Raman Gupta
Raman Gupta

💻
igiannakas
igiannakas

💻
Mario Guggenberger
Mario Guggenberger

💻
Kendell R
Kendell R

🎨
lukerix
lukerix

🌍
Maxime Bailleul
Maxime Bailleul

🌍
Michel Balzer
Michel Balzer

🌍
Enrico Gambini
Enrico Gambini

🌍
MirCore
MirCore

🌍
Fernando Belaza
Fernando Belaza

🌍
Vladimir Cravero
Vladimir Cravero

🌍
Julien Quiévreux
Julien Quiévreux

🌍
lightrabbit
lightrabbit

🌍
Arie6414
Arie6414

🌍
luixcaetano
luixcaetano

🌍
fmarcu
fmarcu

🌍
michaelkmoch
michaelkmoch

🌍
Fred
Fred

🌍
Z-weapon
Z-weapon

🌍
Kyle Bjordahl
Kyle Bjordahl

💻 🐛
Olek Bruks
Olek Bruks

🌍
Gabriele Baldassarre
Gabriele Baldassarre

🌍
Pepijn Baart
Pepijn Baart

🌍
Artem Pastukhov
Artem Pastukhov

🌍
Martin Štefany
Martin Štefany

🌍
quenthal
quenthal

🌍
Luki72
Luki72

🌍
pantan-cymk
pantan-cymk

🌍
yousaf465
yousaf465

🌍
Pierre Belanger
Pierre Belanger

📖
Jan-Sigurd Sørensen
Jan-Sigurd Sørensen

🌍
EF01
EF01

🌍
Mr Snake
Mr Snake

🌍
hungrymachine1
hungrymachine1

🌍
4D4M-Github
4D4M-Github

🌍
Ivan
Ivan

🌍
Florent Cardoen
Florent Cardoen

🌍
moemeli
moemeli

🌍
saya6k
saya6k

🌍
droans
droans

💻
Jonathan Kang
Jonathan Kang

💻
scuricvladimir
scuricvladimir

🌍
Pieter
Pieter

🌍
san80068259
san80068259

🌍
Frosh
Frosh

💻
Rafael Miranda
Rafael Miranda

🌍
rVlad93
rVlad93

🌍
Björn Ebbinghaus
Björn Ebbinghaus

💻
Marck
Marck

💻
Lucho Gizdov
Lucho Gizdov

🌍
MizterB
MizterB

💻
brietman
brietman

🌍
தமிழ் நேரம்
தமிழ் நேரம்

🌍
Thunderstrike116
Thunderstrike116

🌍
immeteor2
immeteor2

🌍
Patrick Bassut
Patrick Bassut

🌍
Ricky Tigg
Ricky Tigg

🌍
Márton Maráz
Márton Maráz

💻
Sara492
Sara492

🌍
enpaga
enpaga

🌍
xuars
xuars

🌍
tinutac
tinutac

🌍
Default User
Default User

🌍
amelenty
amelenty

🌍
Rostyslav Dudka
Rostyslav Dudka

🌍
Helder Ferreira
Helder Ferreira

🌍
Piotr Laszczkowski
Piotr Laszczkowski

🌍
Reza
Reza

🌍
Luna Jernberg
Luna Jernberg

🌍
Jeff Wilson
Jeff Wilson

💻
Rasmus Lundsgaard
Rasmus Lundsgaard

💻
Tom Matheussen
Tom Matheussen

💻
ams2990
ams2990

💻
DataGhost
DataGhost

💻
Furkan Kaya
Furkan Kaya

🌍
Rafael do Amaral Porciuncula
Rafael do Amaral Porciuncula

🌍
hhjuhl
hhjuhl

🌍
B.Athish
B.Athish

🌍
Горпиніч Максим Олександрович
Горпиніч Максим Олександрович

🌍
Masayuki Sugahara
Masayuki Sugahara

🌍
therealmate
therealmate

🌍
Dobby
Dobby

💻
lenucksi
lenucksi

💻
edgimar
edgimar

💻
Andrei LAZAROV
Andrei LAZAROV

📖
Adam DeMuri
Adam DeMuri

💻
Natanael
Natanael

🌍
Yllelder Bamir
Yllelder Bamir

🌍
Esspel
Esspel

🌍
- - Add your contributions - -
- - - - - - -## Translating Adaptive Lighting - -Help to translate Adaptive Lighting into your language on [Hosted Weblate](https://hosted.weblate.org/engage/adaptive-lighting/)! - -Translating can be done from your webbrowser, no programming knowledge -is needed! - - -Translation status - +See [`CHANGELOG.md`](CHANGELOG.md) for the canonical list of CDiT-fork +changes since forking from upstream. diff --git a/custom_components/adaptive_lighting/_docs_helpers.py b/custom_components/adaptive_lighting/_docs_helpers.py deleted file mode 100644 index 4adb0aa6..00000000 --- a/custom_components/adaptive_lighting/_docs_helpers.py +++ /dev/null @@ -1,122 +0,0 @@ -from typing import Any - -import homeassistant.helpers.config_validation as cv -import pandas as pd -import voluptuous as vol -from homeassistant.helpers import selector - -from .const import ( - DOCS, - DOCS_APPLY, - VALIDATION_TUPLES, - apply_service_schema, -) - -# Stubs for upstream docs sections that no longer exist in the CDiT fork. -# The docs generator script reads these by name; provide empty placeholders -# so the script does not crash. The section in the rendered README will be -# empty and can be deleted from the template separately. -DOCS_MANUAL_CONTROL: dict[str, str] = {} -SET_MANUAL_CONTROL_SCHEMA = None - - -def _format_voluptuous_instance(instance: vol.All) -> str: - coerce_type = None - min_val = None - max_val = None - - for validator in instance.validators: - if isinstance(validator, vol.Coerce): - coerce_type = validator.type.__name__ - elif isinstance(validator, vol.Clamp | vol.Range): - min_val = validator.min - max_val = validator.max - - if min_val is not None and max_val is not None: - return f"`{coerce_type}` {min_val}-{max_val}" - if min_val is not None: - return f"`{coerce_type} > {min_val}`" - if max_val is not None: - return f"`{coerce_type} < {max_val}`" - return f"`{coerce_type}`" - - -def _type_to_str(type_: Any) -> str: # noqa: PLR0911 - """Convert a (voluptuous) type to a string.""" - if type_ == cv.entity_ids: - return "list of `entity_id`s" - if type_ in (bool, int, float, str): - return f"`{type_.__name__}`" - if type_ == cv.boolean: - return "bool" - if isinstance(type_, vol.All): - return _format_voluptuous_instance(type_) - if isinstance(type_, vol.Any): - return " or ".join(_type_to_str(t) for t in type_.validators) - if isinstance(type_, vol.In): - return f"one of `{type_.container}`" - if isinstance(type_, selector.SelectSelector): - return f"one of `{type_.config['options']}`" - if isinstance(type_, selector.ColorRGBSelector): - return "RGB color" - msg = f"Unknown type: {type_}" - raise ValueError(msg) - - -def generate_config_markdown_table() -> str: - rows: list[dict[str, str]] = [] - for k, default, type_ in VALIDATION_TUPLES: - description = DOCS[k] - row = { - "Variable name": f"`{k}`", - "Description": description, - "Default": f"`{default}`", - "Type": _type_to_str(type_), - } - rows.append(row) - - df = pd.DataFrame(rows) - return df.to_markdown(index=False) - - -def _schema_to_dict(schema: vol.Schema) -> dict[str, tuple[Any, Any]]: - result: dict[str, tuple[Any, Any]] = {} - for key, value in schema.schema.items(): - if isinstance(key, vol.Optional): - default_value = key.default - result[key.schema] = (default_value, value) - return result - - -def _generate_service_markdown_table( - schema: dict[str, tuple[Any, Any]] | vol.Schema, - alternative_docs: dict[str, str] | None = None, -) -> str: - schema_dict = _schema_to_dict(schema) if isinstance(schema, vol.Schema) else schema - rows: list[dict[str, str]] = [] - for k, (default, type_) in schema_dict.items(): - if alternative_docs is not None and k in alternative_docs: - description = alternative_docs[k] - else: - description = DOCS[k] - row = { - "Service data attribute": f"`{k}`", - "Description": description, - "Required": "✅" if default == vol.UNDEFINED else "❌", - "Type": _type_to_str(type_), - } - rows.append(row) - - df = pd.DataFrame(rows) - return df.to_markdown(index=False) - - -def generate_apply_markdown_table() -> str: - return _generate_service_markdown_table(apply_service_schema(), DOCS_APPLY) - - -def generate_set_manual_control_markdown_table() -> str: - return _generate_service_markdown_table( - SET_MANUAL_CONTROL_SCHEMA, - DOCS_MANUAL_CONTROL, - ) diff --git a/custom_components/adaptive_lighting/docs_gen.py b/custom_components/adaptive_lighting/docs_gen.py deleted file mode 100644 index 9a10a68b..00000000 --- a/custom_components/adaptive_lighting/docs_gen.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Documentation generation utilities for Adaptive Lighting. - -Provides functions to transform content for the documentation site. -Used by markdown-code-runner to generate documentation pages from README content. -""" - -from __future__ import annotations - -import re - - -def _transform_readme_links(content: str) -> str: - """Transform README internal links to docs site links.""" - # Map README anchors to doc pages - link_map = { - "#gear-configuration": "configuration.md", - "#memo-options": "configuration.md#all-options", - "#hammer_and_wrench-services": "services.md", - "#adaptive_lightingapply": "services.md#adaptive_lightingapply", - "#adaptive_lightingset_manual_control": "services.md#adaptive_lightingset_manual_control", - "#adaptive_lightingchange_switch_settings": "services.md#adaptive_lightingchange_switch_settings", - "#robot-automation-examples": "automation-examples.md", - "#sos-troubleshooting": "troubleshooting.md", - "#exclamation-common-problems--solutions": "troubleshooting.md#common-problems-solutions", - "#bar_chart-graphs": "advanced/brightness-modes.md#graphs", - "#bulb-features": "index.md#features", - "#control_knobs-regain-manual-control": "advanced/manual-control.md", - "#eyes-see-also": "see-also.md", - } - - for old_link, new_link in link_map.items(): - content = content.replace(f"]({old_link})", f"]({new_link})") - - # Remove ToC link pattern [[ToC](#...)] - return re.sub(r"\[\[ToC\]\([^)]+\)\]", "", content) diff --git a/custom_components/adaptive_lighting/sensor.py b/custom_components/adaptive_lighting/sensor.py index d30f058d..f1dc77bf 100644 --- a/custom_components/adaptive_lighting/sensor.py +++ b/custom_components/adaptive_lighting/sensor.py @@ -112,9 +112,7 @@ class AdaptiveOutputSensor(SensorEntity): def _handle_outputs_updated(self) -> None: """Read this sensor's value from the cache and write state.""" outputs = ( - self._hass.data.get(DOMAIN, {}) - .get(self._entry.entry_id, {}) - .get("outputs") + self._hass.data.get(DOMAIN, {}).get(self._entry.entry_id, {}).get("outputs") ) if not outputs: # Early signal (e.g., setup race) — leave state as unknown. diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 31586af9..00000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -adaptive-lighting.nijho.lt diff --git a/docs/advanced/brightness-modes.md b/docs/advanced/brightness-modes.md deleted file mode 100644 index 10408fb2..00000000 --- a/docs/advanced/brightness-modes.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -icon: lucide/trending-up ---- - -# Brightness Modes - -Enhance your control over brightness transitions during sunrise and sunset with the `brightness_mode` option. - -## Available Modes - -Adaptive Lighting supports three brightness modes: - -| Mode | Description | -|------|-------------| -| `default` | Standard behavior based on sun position | -| `linear` | Linear ramp between min/max brightness | -| `tanh` | Smooth S-curve using hyperbolic tangent | - -## Detailed Explanation - - - - - - -
-Enhance your control over brightness transitions during sunrise and sunset with brightness_mode (click here to learn more 🧠). - -With Adaptive Lighting, you can set a `brightness_mode` to specify how the brightness changes during sunrise and sunset. The `brightness_mode` can be set to `"default"` ([as illustrated in other graphs above](#high_brightness-brightness)), `"linear"`, or `"tanh"`. If you choose to deviate from the `"default"` mode, you can adjust `brightness_mode_time_dark` and `brightness_mode_time_light` to further customize the lighting transitions. - -When `brightness_mode` is set to `"linear"`: - -- During **_sunset_**, the brightness begins to gradually decrease from `max_brightness` starting at `time=sunset_time - brightness_mode_time_light`, until it reaches `min_brightness` at `time=sunset_time + brightness_mode_time_dark`. -- During **_sunrise_**, the brightness begins to gradually increase from `min_brightness` starting at `time=sunrise_time - brightness_mode_time_dark`, until it reaches `max_brightness` at `time=sunrise_time + brightness_mode_time_light`. - -When `brightness_mode` is set to `"tanh"`, it uses the smooth transition of a [hyperbolic tangent function](https://mathworld.wolfram.com/HyperbolicTangent.html): - -- During **_sunset_**, the brightness starts to decrease from 95% of `max_brightness` starting at `time=sunset_time - brightness_mode_time_light`, until it reaches 5% of `min_brightness` at `time=sunset_time + brightness_mode_time_dark`. -- During **_sunrise_**, the brightness starts to increase from 5% of `min_brightness` starting at `time=sunrise_time - brightness_mode_time_dark`, until it reaches 95% of `max_brightness` at `time=sunrise_time + brightness_mode_time_light`. -
- -Notice the values of `brightness_mode_time_light` and `brightness_mode_time_dark` in the text box. -![image](https://github.com/basnijholt/adaptive-lighting/assets/6897215/15143580-13cd-4ab2-a603-89f2b7830afd) -![image](https://github.com/basnijholt/adaptive-lighting/assets/6897215/f61fdac9-6d47-48c9-84ed-cbb451d5de5d) -![image](https://github.com/basnijholt/adaptive-lighting/assets/6897215/e5fc5d27-3c37-4e3d-93d1-6e7cf4b48e7c) -![image](https://github.com/basnijholt/adaptive-lighting/assets/6897215/3dcbdc42-63c4-49df-8651-d2fae53dd08d) - -> Check out the interactive webapp on https://basnijholt.github.io/adaptive-lighting/ to play with the parameters and see how the brightness changes! - - - -## Configuration Parameters - -When using `linear` or `tanh` modes, you can fine-tune the transition with these parameters: - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `brightness_mode_time_dark` | 900 (15 min) | Duration to ramp brightness before/after sunrise/sunset | -| `brightness_mode_time_light` | 3600 (1 hour) | Duration to ramp brightness after/before sunrise/sunset | - -## Example Configurations - -### Quick Transition (Linear) - -```yaml -adaptive_lighting: - - name: "Quick transitions" - lights: - - light.living_room - brightness_mode: linear - brightness_mode_time_dark: 600 # 10 minutes - brightness_mode_time_light: 1800 # 30 minutes -``` - -### Smooth Transition (Tanh) - -```yaml -adaptive_lighting: - - name: "Smooth transitions" - lights: - - light.bedroom - brightness_mode: tanh - brightness_mode_time_dark: 1200 # 20 minutes - brightness_mode_time_light: 3600 # 1 hour -``` - -## Graphs - -These graphs show how brightness changes throughout the day based on calculated values: - - - - - - -These graphs were generated using the values calculated by the Adaptive Lighting sensor/switch(es). - -### :sunny: Sun Position -![cl_percent|690x131](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/6/5/657ff98beb65a94598edeb4bdfd939095db1a22c.PNG) - -### :thermometer: Color Temperature -![cl_color_temp|690x129](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/9/59e84263cbecd8e428cb08777a0413672c48dfcd.PNG) - -### :high_brightness: Brightness -![cl_brightness|690x130](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/8/58ebd994b62a8b1abfb3497a5288d923ff4e2330.PNG) - -### While using `transition_until_sleep: true` -![image](https://user-images.githubusercontent.com/2219836/228949675-f9699624-8abc-466c-bb04-250ce0f495b8.png) - - - -## Interactive Simulator - -The best way to understand brightness modes is to experiment with the interactive simulator: - -
- - Try the Simulator - -
- -Adjust the `brightness_mode`, `brightness_mode_time_dark`, and `brightness_mode_time_light` parameters to see how they affect the brightness curve in real-time. diff --git a/docs/advanced/manual-control.md b/docs/advanced/manual-control.md deleted file mode 100644 index 36720f3c..00000000 --- a/docs/advanced/manual-control.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -icon: lucide/hand ---- - -# Manual Control - -Adaptive Lighting is designed to work seamlessly with manual adjustments, detecting when you or another source changes light settings and pausing adaptation accordingly. - -## How It Works - - - - - - -Adaptive Lighting is designed to automatically detect when you or another source (e.g., automation) manually changes light settings 🕹️. -When this occurs, the affected light is marked as "manually controlled," and Adaptive Lighting will not make further adjustments until the light is turned off and back on or reset using the `adaptive_lighting.set_manual_control` service call. -This feature is available when `take_over_control` is enabled. - -Additionally, enabling `detect_non_ha_changes` allows Adaptive Lighting to detect all state changes, including those made outside of Home Assistant, by comparing the light's state to its previously used settings. -The `adaptive_lighting.manual_control` event is fired when a light is marked as "manually controlled," allowing for integration with automations 🤖. - -> ⚠️ **_Caution: Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Disable `detect_non_ha_changes` if you encounter such issues._** - - - -## Configuration Options - -### take_over_control - -When enabled (default: `true`), Adaptive Lighting detects `light.turn_on` service calls that specify brightness or color values. If such a call is detected for a light that's already on, that light is marked as "manually controlled". - -```yaml -adaptive_lighting: - - name: "With manual control detection" - lights: - - light.living_room - take_over_control: true # default -``` - -### take_over_control_mode - -Controls how adaptation pauses when manual changes are detected: - -| Mode | Behavior | -|------|----------| -| `pause_all` | Pause both brightness and color adaptation (default) | -| `pause_changed` | Only pause adaptation of the changed attribute | - -```yaml -adaptive_lighting: - - name: "Selective pause" - lights: - - light.living_room - take_over_control: true - take_over_control_mode: pause_changed # Only pause changed attributes -``` - -### detect_non_ha_changes - -When enabled, Adaptive Lighting detects state changes made outside of Home Assistant by comparing the light's current state to its previously applied settings. - -> [!WARNING] -> **Use with caution.** Some lights may falsely report an "on" state, which could result in lights turning on unexpectedly. Disable this option if you encounter such issues. - -```yaml -adaptive_lighting: - - name: "Detect external changes" - lights: - - light.living_room - take_over_control: true - detect_non_ha_changes: true -``` - -### autoreset_control_seconds - -Automatically resets the manual control flag after a specified number of seconds. Set to `0` to disable (default). - -```yaml -adaptive_lighting: - - name: "Auto-reset after 2 hours" - lights: - - light.living_room - take_over_control: true - autoreset_control_seconds: 7200 # 2 hours -``` - -### adapt_only_on_bare_turn_on - -When enabled, Adaptive Lighting only adapts lights when `light.turn_on` is called without specifying brightness or color. This is useful when you want scenes to work without interference. - -```yaml -adaptive_lighting: - - name: "Respect scenes" - lights: - - light.living_room - take_over_control: true - adapt_only_on_bare_turn_on: true -``` - -## Checking Manual Control Status - -You can see which lights are marked as manually controlled by checking the switch attributes: - -1. Go to **Developer Tools** → **States** -2. Find your Adaptive Lighting switch (e.g., `switch.adaptive_lighting_living_room`) -3. Look at the `manual_control` attribute - it lists all manually controlled lights - -## Resetting Manual Control - -### Via Service Call - -```yaml -service: adaptive_lighting.set_manual_control -data: - entity_id: switch.adaptive_lighting_living_room - lights: - - light.floor_lamp - manual_control: false # Resume adaptation -``` - -### By Turning Light Off and On - -Simply turning a light off and then back on will reset its manual control status. - -### Via Automation - -See [Automation Examples](../automation-examples.md) for automation recipes that automatically reset manual control. - -## Events - -When a light is marked as manually controlled, Adaptive Lighting fires an event: - -**Event type:** `adaptive_lighting.manual_control` - -**Event data:** -```yaml -entity_id: light.living_room -switch: switch.adaptive_lighting_living_room -``` - -You can use this event to trigger automations: - -```yaml -automation: - - alias: "Notify on manual control" - trigger: - platform: event - event_type: adaptive_lighting.manual_control - action: - - service: notify.mobile_app - data: - message: "{{ trigger.event.data.entity_id }} was manually adjusted" -``` - -## Best Practices - -1. **Use Zigbee groups** when controlling multiple bulbs together - this ensures consistent manual control detection -2. **Set reasonable autoreset times** if you want lights to eventually resume adaptation -3. **Use `pause_changed` mode** if you only adjust brightness or color individually -4. **Disable `detect_non_ha_changes`** if you experience unexpected light turn-ons diff --git a/docs/advanced/sleep-mode.md b/docs/advanced/sleep-mode.md deleted file mode 100644 index 40736340..00000000 --- a/docs/advanced/sleep-mode.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -icon: lucide/moon ---- - -# Sleep Mode - -Sleep mode is a special operating mode that sets your lights to minimal brightness and very warm color, perfect for winding down at night without disrupting your circadian rhythm. - -## Activating Sleep Mode - -Each Adaptive Lighting configuration creates a sleep mode switch: - -``` -switch.adaptive_lighting_sleep_mode_ -``` - -Turn it on to activate sleep mode: - -```yaml -service: switch.turn_on -target: - entity_id: switch.adaptive_lighting_sleep_mode_living_room -``` - -## Configuration Options - -Sleep mode is configured through the main Adaptive Lighting configuration. See the [Configuration](../configuration.md) page for the full options table. The sleep-related options are: - -| Option | Default | Description | -|--------|---------|-------------| -| `sleep_brightness` | 1 | Brightness percentage in sleep mode | -| `sleep_rgb_or_color_temp` | `color_temp` | Use `rgb_color` or `color_temp` in sleep mode | -| `sleep_color_temp` | 1000 | Color temperature in Kelvin for sleep mode | -| `sleep_rgb_color` | `[255, 56, 0]` | RGB color for sleep mode | -| `sleep_transition` | 1 | Transition duration in seconds | -| `transition_until_sleep` | false | Gradually transition to sleep settings after sunset | - -## Automation Examples - -See [Automation Examples](../automation-examples.md) for sleep mode automation recipes, including: - -- Toggle sleep mode using an `input_boolean` -- Set sunrise/sunset based on alarm time diff --git a/docs/assets/logo.png b/docs/assets/logo.png deleted file mode 100644 index d055aca3da7d8586f9872b7abcf331631629c9fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17642 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 h1:first-child { - font-size: 2.5rem; - margin-bottom: 0.5rem; -} - -/* Logo in header - make it slightly larger */ -.md-header__button.md-logo img { - height: 1.8rem; -} - -/* Simulator link button styling */ -.simulator-link { - display: inline-block; - padding: 0.75rem 1.5rem; - background: linear-gradient(135deg, var(--md-primary-fg-color), var(--md-accent-fg-color)); - color: white !important; - text-decoration: none; - border-radius: 2rem; - font-weight: 600; - font-size: 1.1rem; - transition: transform 0.2s ease, box-shadow 0.2s ease; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -.simulator-link:hover { - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); - text-decoration: none; -} - -/* Code block improvements */ -.highlight code { - font-size: 0.85rem; -} - -/* Table improvements */ -.md-typeset table:not([class]) { - font-size: 0.8rem; -} - -.md-typeset table:not([class]) th { - font-weight: 700; -} diff --git a/docs/automation-examples.md b/docs/automation-examples.md deleted file mode 100644 index e2d136f6..00000000 --- a/docs/automation-examples.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -icon: lucide/bot ---- - -# Automation Examples - -Real-world automation examples showing how to integrate Adaptive Lighting with your Home Assistant setup. - - - - - - - -
-Reset the manual_control status of a light after an hour. - -```yaml -- alias: "Adaptive lighting: reset manual_control after 1 hour" - mode: parallel - trigger: - platform: event - event_type: adaptive_lighting.manual_control - variables: - light: "{{ trigger.event.data.entity_id }}" - switch: "{{ trigger.event.data.switch }}" - action: - - delay: "01:00:00" - - condition: template - value_template: "{{ light in state_attr(switch, 'manual_control') }}" - - service: adaptive_lighting.set_manual_control - data: - entity_id: "{{ switch }}" - lights: "{{ light }}" - manual_control: false -``` - -
- -
-Toggle multiple Adaptive Lighting switches to "sleep mode" using an input_boolean.sleep_mode. - -```yaml -- alias: "Adaptive lighting: toggle 'sleep mode'" - trigger: - - platform: state - entity_id: input_boolean.sleep_mode - - platform: homeassistant - event: start # in case the states aren't properly restored - variables: - sleep_mode: "{{ states('input_boolean.sleep_mode') }}" - action: - service: "switch.turn_{{ sleep_mode }}" - entity_id: - - switch.adaptive_lighting_sleep_mode_living_room - - switch.adaptive_lighting_sleep_mode_bedroom -``` - -Set your sunrise and sunset time based on your alarm. The below script sets sunset_time exactly 12 hours after the custom sunrise time. - -```yaml -iphone_carly_wakeup: - alias: iPhone Carly Wakeup - sequence: - - condition: state - entity_id: input_boolean.carly_iphone_wakeup - state: "off" - - service: input_datetime.set_datetime - target: - entity_id: input_datetime.carly_iphone_wakeup - data: - time: '{{ now().strftime("%H:%M:%S") }}' - - service: input_boolean.turn_on - target: - entity_id: input_boolean.carly_iphone_wakeup - - repeat: - count: > - {{ (states.switch - | map(attribute="entity_id") - | select(">","switch.adaptive_lighting_al_") - | select("<", "switch.adaptive_lighting_al_z") - | join(",") - ).split(",") | length }} - sequence: - - service: adaptive_lighting.change_switch_settings - data: - entity_id: switch.adaptive_lighting_al_den_ceilingfan_lights - sunrise_time: '{{ now().strftime("%H:%M:%S") }}' - sunset_time: > - {{ (as_timestamp(now()) + 12*60*60) | timestamp_custom("%H:%M:%S") }} - - service: script.turn_on - target: - entity_id: script.run_wakeup_routine - - service: input_boolean.turn_off - target: - entity_id: - - input_boolean.carly_iphone_winddown - - input_boolean.carly_iphone_bedtime - - service: input_datetime.set_datetime - target: - entity_id: input_datetime.wakeup_time - data: - time: '{{ now().strftime("%H:%M:%S") }}' - - service: script.adaptive_lighting_disable_sleep_mode - mode: queued - icon: mdi:weather-sunset - max: 10 -``` - -
- - - -> [!TIP] -> **Have a useful automation?** Share your automation examples by [opening an issue](https://github.com/basnijholt/adaptive-lighting/issues) or submitting a pull request to the README. diff --git a/docs/configuration.md b/docs/configuration.md deleted file mode 100644 index 21c7028e..00000000 --- a/docs/configuration.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -icon: lucide/settings ---- - -# Configuration - -Adaptive Lighting supports configuration through both YAML and the Home Assistant UI, with identical option names in both methods. - -## Basic Configuration - -The minimal configuration requires only adding the integration to your `configuration.yaml`: - -```yaml -adaptive_lighting: -``` - -You can then configure everything through the UI at **Settings** → **Devices & Services** → **Adaptive Lighting** → **Configure**. - -## YAML Configuration - -For YAML configuration, you can specify lights and options directly: - -```yaml -adaptive_lighting: - - name: "Living Room" - lights: - - light.living_room_ceiling - - light.living_room_lamp -``` - -## All Options - -All configuration options are listed below with their default values. These options work identically in both YAML and the UI. - - - - - - - -| Variable name | Description | Default | Type | -|:-------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------|:----------------------------------------| -| `lights` | List of light entity_ids to be controlled (may be empty). 🌟 | `[]` | list of `entity_id`s | -| `interval` | Frequency to adapt the lights, in seconds. 🔄 | `90` | `int > 0` | -| `transition` | Duration of transition when lights change, in seconds. 🕑 | `45` | `float` 0-6553 | -| `initial_transition` | Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️ | `1` | `float` 0-6553 | -| `min_brightness` | Minimum brightness percentage. 💡 | `1` | `int` 1-100 | -| `max_brightness` | Maximum brightness percentage. 💡 | `100` | `int` 1-100 | -| `min_color_temp` | Warmest color temperature in Kelvin. 🔥 | `2000` | `int` 1000-10000 | -| `max_color_temp` | Coldest color temperature in Kelvin. ❄️ | `5500` | `int` 1000-10000 | -| `prefer_rgb_color` | Whether to prefer RGB color adjustment over light color temperature when possible. 🌈 | `False` | `bool` | -| `sleep_brightness` | Brightness percentage of lights in sleep mode. 😴 | `1` | `int` 1-100 | -| `sleep_rgb_or_color_temp` | Use either `"rgb_color"` or `"color_temp"` in sleep mode. 🌙 | `color_temp` | one of `['color_temp', 'rgb_color']` | -| `sleep_color_temp` | Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is `color_temp`) in Kelvin. 😴 | `1000` | `int` 1000-10000 | -| `sleep_rgb_color` | RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is "rgb_color"). 🌈 | `[255, 56, 0]` | RGB color | -| `sleep_transition` | Duration of transition when "sleep mode" is toggled in seconds. 😴 | `1` | `float` 0-6553 | -| `transition_until_sleep` | When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙 | `False` | `bool` | -| `sunrise_time` | Set a fixed time (HH:MM:SS) for sunrise. 🌅 | `None` | `str` | -| `min_sunrise_time` | Set the earliest virtual sunrise time (HH:MM:SS), allowing for later sunrises. 🌅 | `None` | `str` | -| `max_sunrise_time` | Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier sunrises. 🌅 | `None` | `str` | -| `sunrise_offset` | Adjust sunrise time with a positive or negative offset in seconds. ⏰ | `0` | `int` | -| `sunset_time` | Set a fixed time (HH:MM:SS) for sunset. 🌇 | `None` | `str` | -| `min_sunset_time` | Set the earliest virtual sunset time (HH:MM:SS), allowing for later sunsets. 🌇 | `None` | `str` | -| `max_sunset_time` | Set the latest virtual sunset time (HH:MM:SS), allowing for earlier sunsets. 🌇 | `None` | `str` | -| `sunset_offset` | Adjust sunset time with a positive or negative offset in seconds. ⏰ | `0` | `int` | -| `brightness_mode` | Brightness mode to use. Possible values are `default`, `linear`, and `tanh` (uses `brightness_mode_time_dark` and `brightness_mode_time_light`). 📈 | `default` | one of `['default', 'linear', 'tanh']` | -| `brightness_mode_time_dark` | (Ignored if `brightness_mode='default'`) The duration in seconds to ramp up/down the brightness before/after sunrise/sunset. 📈📉 | `900` | `int` | -| `brightness_mode_time_light` | (Ignored if `brightness_mode='default'`) The duration in seconds to ramp up/down the brightness after/before sunrise/sunset. 📈📉. | `3600` | `int` | -| `take_over_control` | Pause adaptation of individual lights and hand over (manual) control to other sources that issue `light.turn_on` calls for lights that are on. 🔒 | `True` | `bool` | -| `take_over_control_mode` | The adaptation pausing mode when other sources change brightness and/or color of lights. `pause_all` always pauses both brightness and color adaptation. `pause_changed` pauses the adaptation of only the changed attributes and continues adapting unchanged attributes, e.g., continues color adaptation when only brightness was changed. | `pause_all` | one of `['pause_all', 'pause_changed']` | -| `detect_non_ha_changes` | Detects and halts adaptations for non-`light.turn_on` state changes. Needs `take_over_control` enabled. 🕵️ Caution: ⚠️ Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Note that this calls `homeassistant.update_entity` every `interval`! Disable this feature if you encounter such issues. | `False` | `bool` | -| `autoreset_control_seconds` | Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️ | `0` | `int` 0-31536000 | -| `only_once` | Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄 | `False` | `bool` | -| `adapt_only_on_bare_turn_on` | When turning lights on initially. If set to `true`, AL adapts only if `light.turn_on` is invoked without specifying color or brightness. ❌🌈 This e.g., prevents adaptation when activating a scene and marks the light as manually controlled. If `false`, AL adapts regardless of the presence of color or brightness in the initial `service_data`. Needs `take_over_control` enabled. 🕵️ | `False` | `bool` | -| `separate_turn_on_commands` | Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀 | `False` | `bool` | -| `send_split_delay` | Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️ | `0` | `int` 0-10000 | -| `adapt_delay` | Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️ | `0` | `float > 0` | -| `skip_redundant_commands` | Skip sending adaptation commands whose target state already equals the light's known state. Minimizes network traffic and improves the adaptation responsivity in some situations. 📉Disable if physical light states get out of sync with HA's recorded state. | `False` | `bool` | -| `intercept` | Intercept and adapt `light.turn_on` calls to enabling instantaneous color and brightness adaptation. 🏎️ Disable for lights that do not support `light.turn_on` with color and brightness. | `True` | `bool` | -| `multi_light_intercept` | Intercept and adapt `light.turn_on` calls that target multiple lights. ➗⚠️ This might result in splitting up a single `light.turn_on` call into multiple calls, e.g., when lights are in different switches. Requires `intercept` to be enabled. | `True` | `bool` | -| `include_config_in_attributes` | Show all options as attributes on the switch in Home Assistant when set to `true`. 📝 | `False` | `bool` | - - - -## Full Configuration Example - - - - - - - -Full example: - -```yaml -# Example configuration.yaml entry -adaptive_lighting: -- name: "default" - lights: [] - prefer_rgb_color: false - transition: 45 - initial_transition: 1 - interval: 90 - min_brightness: 1 - max_brightness: 100 - min_color_temp: 2000 - max_color_temp: 5500 - sleep_brightness: 1 - sleep_color_temp: 1000 - sunrise_time: "08:00:00" # override the sunrise time - sunrise_offset: - sunset_time: - sunset_offset: 1800 # in seconds or '00:30:00' - take_over_control: true - detect_non_ha_changes: false - only_once: false - -``` - - - -## Multiple Configurations - -You can create multiple Adaptive Lighting configurations for different areas or use cases: - -```yaml -adaptive_lighting: - - name: "Daytime Spaces" - lights: - - light.living_room - - light.kitchen - - light.office - min_brightness: 30 - max_brightness: 100 - - - name: "Bedroom" - lights: - - light.bedroom_ceiling - - light.bedroom_lamp - min_brightness: 5 - max_brightness: 80 - sleep_brightness: 1 - sleep_color_temp: 1000 -``` - -## Related Topics - -- [Brightness Modes](advanced/brightness-modes.md) - Detailed explanation of brightness calculation modes -- [Sleep Mode](advanced/sleep-mode.md) - Sleep mode configuration -- [Manual Control](advanced/manual-control.md) - How manual control detection works diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 68c253eb..00000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -icon: lucide/rocket ---- - -# Getting Started - -This guide will help you install and configure Adaptive Lighting for the first time. - -## Prerequisites - -- [Home Assistant](https://www.home-assistant.io/) 2024.12.0 or newer -- [HACS](https://hacs.xyz/) (Home Assistant Community Store) installed - -## Installation - -### Via HACS (Recommended) - -1. Open HACS in your Home Assistant instance -2. Click on **Integrations** -3. Click the **+ Explore & Download Repositories** button -4. Search for "Adaptive Lighting" -5. Click **Download** -6. Restart Home Assistant - -Or use this button to open HACS directly: - -[![Open your Home Assistant instance and open the Adaptive Lighting integration inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=basnijholt&repository=adaptive-lighting&category=integration) - -### Manual Installation - -1. Download the latest release from [GitHub](https://github.com/basnijholt/adaptive-lighting/releases) -2. Extract the `adaptive_lighting` folder to your `config/custom_components/` directory -3. Restart Home Assistant - -## Configuration - -### Step 1: Add to configuration.yaml - -Add the following to your `configuration.yaml`: - -```yaml -adaptive_lighting: -``` - -> [!NOTE] -> This entry is required even if you plan to configure everything through the UI. - -### Step 2: Restart Home Assistant - -Restart Home Assistant for the changes to take effect. - -### Step 3: Add the Integration - -1. Go to **Settings** → **Devices & Services** -2. Click **+ Add Integration** -3. Search for "Adaptive Lighting" -4. Follow the setup wizard to select your lights - -### Step 4: Configure Your Lights - -You can configure Adaptive Lighting in two ways: - -=== "Via UI" - - 1. Go to **Settings** → **Devices & Services** - 2. Find Adaptive Lighting and click **Configure** - 3. Adjust settings as needed - -=== "Via YAML" - - ```yaml - adaptive_lighting: - - name: "Living Room" - lights: - - light.living_room_ceiling - - light.living_room_lamp - min_brightness: 20 - max_brightness: 100 - min_color_temp: 2200 - max_color_temp: 5500 - ``` - -## Basic Configuration Example - -Here's a simple configuration to get you started: - -```yaml -adaptive_lighting: - - name: "Main Lights" - lights: - - light.living_room - - light.bedroom - - light.kitchen - transition: 30 - min_brightness: 10 - max_brightness: 100 - min_color_temp: 2000 - max_color_temp: 5500 -``` - -## Verifying Installation - -After configuration, you should see new switches in Home Assistant: - -- `switch.adaptive_lighting_main_lights` -- `switch.adaptive_lighting_sleep_mode_main_lights` -- `switch.adaptive_lighting_adapt_brightness_main_lights` -- `switch.adaptive_lighting_adapt_color_main_lights` - -Turn on `switch.adaptive_lighting_main_lights` to start adapting your lights! - -## Next Steps - -- [Configuration Reference](configuration.md) - Explore all available options -- [Services](services.md) - Learn about service calls for automations -- [Automation Examples](automation-examples.md) - See real-world automation recipes -- [Troubleshooting](troubleshooting.md) - Common issues and solutions diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index bc3acebe..00000000 --- a/docs/index.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -icon: lucide/sun ---- - -# Adaptive Lighting - -**Enhance Your Home's Atmosphere with Smart, Sun-Synchronized Lighting** - -
- Adaptive Lighting Logo -
- -[Adaptive Lighting](https://github.com/basnijholt/adaptive-lighting) is a custom component for [Home Assistant](https://www.home-assistant.io/) that intelligently adjusts the brightness and color of your lights based on the sun's position, while still allowing for manual control. - -
- -By automatically adapting the settings of your lights throughout the day, Adaptive Lighting helps maintain your natural circadian rhythm, which can lead to improved sleep, mood, and overall well-being. Experience cooler color temperatures at noon, gradually transitioning to warmer colors at sunset and sunrise. - -## Features - - - - - - - -When initially turning on a light that is controlled by Adaptive Lighting, the `light.turn_on` service call is intercepted, and the light's brightness and color are automatically adjusted based on the sun's position. -After that, the light's brightness and color are automatically adjusted at a regular interval. - -Adaptive Lighting provides four switches (using "living_room" as an example component name): - -- `switch.adaptive_lighting_living_room`: Turn Adaptive Lighting on or off and view current light settings through its attributes. -- `switch.adaptive_lighting_sleep_mode_living_room`: Activate "sleep mode" 😴 and set custom sleep_brightness and sleep_color_temp. -- `switch.adaptive_lighting_adapt_brightness_living_room`: Enable or disable brightness adaptation 🔆 for supported lights. -- `switch.adaptive_lighting_adapt_color_living_room`: Enable or disable color adaptation 🌈 for supported lights. - - - -## Quick Start - -1. **Install via HACS**: Search for "Adaptive Lighting" in the [Home Assistant Community Store](https://hacs.xyz/) -2. **Add to configuration**: Add `adaptive_lighting:` to your `configuration.yaml` -3. **Configure**: Go to **Settings** → **Devices & Services** → **Add Integration** → **Adaptive Lighting** -4. **Select your lights**: Choose which lights to control and enjoy automatic adaptation! - -```yaml -# Minimal configuration.yaml entry -adaptive_lighting: - lights: - - light.living_room -``` - -> [!TIP] -> **Using the UI exclusively?** Even if you plan to configure everything through the UI, the `adaptive_lighting:` entry must still be present in your `configuration.yaml`. - -[Get Started →](getting-started.md){ .md-button .md-button--primary } -[View All Options →](configuration.md){ .md-button } - -## How It Works - -Adaptive Lighting provides four switches for each configuration (using "living_room" as an example): - -| Switch | Purpose | -|--------|---------| -| `switch.adaptive_lighting_living_room` | Main on/off control | -| `switch.adaptive_lighting_sleep_mode_living_room` | Activate sleep mode | -| `switch.adaptive_lighting_adapt_brightness_living_room` | Enable/disable brightness adaptation | -| `switch.adaptive_lighting_adapt_color_living_room` | Enable/disable color adaptation | - -## Interactive Simulator - -Visualize how Adaptive Lighting will work with your settings using the interactive simulator: - - diff --git a/docs/overrides/partials/integrations/analytics/custom.html b/docs/overrides/partials/integrations/analytics/custom.html deleted file mode 100644 index c5e49c15..00000000 --- a/docs/overrides/partials/integrations/analytics/custom.html +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/docs/run_markdown_code_runner.py b/docs/run_markdown_code_runner.py deleted file mode 100755 index 3a4b59f8..00000000 --- a/docs/run_markdown_code_runner.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -# ruff: noqa: T201, S603, S607 -"""Update all markdown files that use markdown-code-runner for auto-generation. - -Run from repo root: uv run python docs/run_markdown_code_runner.py -""" - -from __future__ import annotations - -import subprocess -import sys -from pathlib import Path - - -def find_markdown_files_with_code_blocks(docs_dir: Path) -> list[Path]: - """Find all markdown files containing markdown-code-runner markers.""" - files_with_code = [] - for md_file in docs_dir.rglob("*.md"): - content = md_file.read_text() - if "" in content: - files_with_code.append(md_file) - return sorted(files_with_code) - - -def run_markdown_code_runner(files: list[Path], repo_root: Path) -> bool: - """Run markdown-code-runner on all files. Returns True if all succeeded.""" - if not files: - print("No files with CODE:START markers found.") - return True - - print(f"Found {len(files)} file(s) with auto-generated content:") - for f in files: - print(f" - {f.relative_to(repo_root)}") - print() - - all_success = True - for file in files: - rel_path = file.relative_to(repo_root) - print(f"Updating {rel_path}...", end=" ", flush=True) - result = subprocess.run( - ["markdown-code-runner", str(file)], - check=False, - capture_output=True, - text=True, - ) - if result.returncode == 0: - print("✓") - else: - print("✗") - print(f" Error: {result.stderr}") - all_success = False - - return all_success - - -def main() -> int: - """Main entry point.""" - repo_root = Path(__file__).parent.parent - - # Process docs/ files and README.md - files = find_markdown_files_with_code_blocks(repo_root / "docs") - readme = repo_root / "README.md" - if readme.exists() and "" in readme.read_text(): - files.append(readme) - - success = run_markdown_code_runner(files, repo_root) - return 0 if success else 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/docs/see-also.md b/docs/see-also.md deleted file mode 100644 index 5b06fcae..00000000 --- a/docs/see-also.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -icon: lucide/external-link ---- - -# See Also - -Resources, tutorials, and related projects for Adaptive Lighting. - -## Tutorials & Articles - - - - - - - -- [*Sleep better with Adaptive Lighting in Home Assistant*](https://wartner.io/sleep-better-with-adaptive-lightning-in-home-assistant/) by Florian Wartner on 2023-02-23 (blog post 📜) -- [*Automatic smart light brightness and color based on the sun*](https://www.youtube.com/watch?v=Rg3zI1Oyk3c) by Home Automation Guy on 2022-08-31 (YouTube video 📺) -- [*Adaptive Lighting Blew My Mind in Home Assistant - How to set it up*](https://www.youtube.com/watch?v=c1cnccmgl3k) by Smart Home Junkie on 2022-06-26 (YouTube video 📺) - - - -## Interactive Tools - -### Adaptive Lighting Simulator - -Visualize how different settings affect your lighting throughout the day: - - - -The simulator lets you: - -- Adjust all configuration parameters in real-time -- See how brightness and color temperature change throughout the day -- Visualize the effects of different `brightness_mode` settings -- Test sleep mode transitions - -## Related Projects - -### Circadian Lighting - -Adaptive Lighting was initially inspired by [hass-circadian_lighting](https://github.com/claytonjn/hass-circadian_lighting) by @claytonjn, but has since been entirely rewritten and expanded with many new features. - -## Official Documentation - -- [Home Assistant Documentation (PR Preview)](https://deploy-preview-14877--home-assistant-docs.netlify.app/integrations/adaptive_lighting/) -- [Home Assistant Community Store (HACS)](https://hacs.xyz/) - -## Community - -- [Home Assistant Community Forums](https://community.home-assistant.io/) -- [Home Assistant Discord](https://discord.gg/home-assistant) -- [Reddit r/homeassistant](https://www.reddit.com/r/homeassistant/) - -## Contributing - -Interested in contributing to Adaptive Lighting? - -- [GitHub Repository](https://github.com/basnijholt/adaptive-lighting) -- [Issue Tracker](https://github.com/basnijholt/adaptive-lighting/issues) -- [Translation via Weblate](https://hosted.weblate.org/engage/adaptive-lighting/) - -### Translation - -Help translate Adaptive Lighting into your language on [Hosted Weblate](https://hosted.weblate.org/engage/adaptive-lighting/). No programming knowledge required! - - -Translation status - diff --git a/docs/services.md b/docs/services.md deleted file mode 100644 index 306aa7c3..00000000 --- a/docs/services.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -icon: lucide/zap ---- - -# Services - -Adaptive Lighting provides three services for programmatic control, allowing you to integrate with automations and scripts. - -## adaptive_lighting.apply - -Applies the current Adaptive Lighting settings to lights on demand. Useful for forcing an immediate update or applying settings to lights that aren't in the regular adaptation cycle. - -### Parameters - - - - - - - -| Service data attribute | Description | Required | Type | -|:-------------------------|:--------------------------------------------------------------------------------------|:-----------|:---------------------| -| `entity_id` | The `entity_id` of the switch with the settings to apply. 📝 | ✅ | list of `entity_id`s | -| `lights` | A light (or list of lights) to apply the settings to. 💡 | ❌ | list of `entity_id`s | -| `transition` | Duration of transition when lights change, in seconds. 🕑 | ❌ | `float` 0-6553 | -| `adapt_brightness` | Whether to adapt the brightness of the light. 🌞 | ❌ | bool | -| `adapt_color` | Whether to adapt the color on supporting lights. 🌈 | ❌ | bool | -| `prefer_rgb_color` | Whether to prefer RGB color adjustment over light color temperature when possible. 🌈 | ❌ | bool | -| `turn_on_lights` | Whether to turn on lights that are currently off. 🔆 | ❌ | bool | - - - -### Example Usage - -```yaml -# Apply current settings to specific lights -service: adaptive_lighting.apply -data: - entity_id: switch.adaptive_lighting_living_room - lights: - - light.floor_lamp - - light.desk_lamp - turn_on_lights: false -``` - -```yaml -# Force apply with custom transition -service: adaptive_lighting.apply -data: - entity_id: switch.adaptive_lighting_bedroom - transition: 5 - adapt_brightness: true - adapt_color: true -``` - ---- - -## adaptive_lighting.set_manual_control - -Marks or unmarks a light as "manually controlled". When a light is marked as manually controlled, Adaptive Lighting will not adjust it until the manual control flag is cleared. - -### Parameters - - - - - - - -| Service data attribute | Description | Required | Type | -|:-------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------|:-----------------------------------------| -| `entity_id` | The `entity_id` of the switch in which to (un)mark the light as being `manually controlled`. 📝 | ✅ | list of `entity_id`s | -| `lights` | entity_id(s) of lights, if not specified, all lights in the switch are selected. 💡 | ❌ | list of `entity_id`s | -| `manual_control` | Whether to add ("true") or remove ("false") all adapted attributes of the light from the "manual_control" list, or the name of an attribute for selective addition. 🔒 | ❌ | bool or one of `['brightness', 'color']` | - - - -### Example Usage - -```yaml -# Remove manual control from a light (resume adaptation) -service: adaptive_lighting.set_manual_control -data: - entity_id: switch.adaptive_lighting_living_room - lights: - - light.floor_lamp - manual_control: false -``` - -```yaml -# Mark a light as manually controlled (pause adaptation) -service: adaptive_lighting.set_manual_control -data: - entity_id: switch.adaptive_lighting_living_room - lights: - - light.floor_lamp - manual_control: true -``` - -```yaml -# Only pause brightness adaptation, continue color adaptation -service: adaptive_lighting.set_manual_control -data: - entity_id: switch.adaptive_lighting_living_room - lights: - - light.floor_lamp - manual_control: brightness -``` - ---- - -## adaptive_lighting.change_switch_settings - - - - - - - -`adaptive_lighting.change_switch_settings` (new in 1.7.0) Change any of the above configuration options of Adaptive Lighting (such as `sunrise_time` or `prefer_rgb_color`) with a service call directly from your script/automation. - -> [!WARNING] -> These settings will **not** be written to your config and will be reset on restart of Home Assistant! You can see the current settings in the `switch.adaptive_lighting_XXX` attributes if `include_config_in_attributes` is enabled. - -| Service data attribute | Required | Description | -| --------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `use_defaults` | ❌ | (default: `current` for current settings) Choose from `factory`, `configuration`, or `current` to reset variables not being set with this service call. `current` leaves them as they are, `configuration` resets to initial startup values, `factory` resets to default values listed in the documentation. | -| **all other keys** (except the ones in the table below ⚠️) | ❌ | See the table below for disallowed keys. | - -The following keys are disallowed: - -| **DISALLOWED** service data | Description | -| --------------------------- | ----------------------------------------------------------------------------------------------- | -| `entity_id` | You cannot change the switch's `entity_id`, as it has already been registered. | -| `lights` | You may call `adaptive_lighting.apply` with your lights or create a new config instead. | -| `name` | You can rename your switch's display name in Home Assistant's UI. | -| `interval` | The interval is used only once when the config loads. A config change and restart are required. | - - - -### Example Usage - -```yaml -# Temporarily change color temperature range -service: adaptive_lighting.change_switch_settings -data: - entity_id: switch.adaptive_lighting_living_room - min_color_temp: 2500 - max_color_temp: 4000 -``` - -```yaml -# Override sunrise time for the day -service: adaptive_lighting.change_switch_settings -data: - entity_id: switch.adaptive_lighting_bedroom - sunrise_time: "07:00:00" - use_defaults: current -``` - -```yaml -# Reset to configuration defaults -service: adaptive_lighting.change_switch_settings -data: - entity_id: switch.adaptive_lighting_living_room - use_defaults: configuration -``` - ---- - -## Events - -Adaptive Lighting also fires events that you can use in automations. - -### adaptive_lighting.manual_control - -Fired when a light is marked as "manually controlled" due to a detected manual change. - -**Event Data:** - -| Attribute | Description | -|-----------|-------------| -| `entity_id` | The light that was marked as manually controlled | -| `switch` | The Adaptive Lighting switch entity | - -### Example Automation - -```yaml -automation: - - alias: "Log manual control events" - trigger: - platform: event - event_type: adaptive_lighting.manual_control - action: - - service: notify.mobile_app - data: - title: "Adaptive Lighting" - message: "{{ trigger.event.data.entity_id }} was manually controlled" -``` - -See [Automation Examples](automation-examples.md) for more use cases. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md deleted file mode 100644 index 22374949..00000000 --- a/docs/troubleshooting.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -icon: lucide/life-buoy ---- - -# Troubleshooting - -This guide covers common issues and their solutions when using Adaptive Lighting. - -## Enable Debug Logging - - - - - - - -Encountering issues? Enable debug logging in your `configuration.yaml`: - -```yaml -logger: - default: warning - logs: - custom_components.adaptive_lighting: debug -``` - -After the issue occurs, create a new issue report with the log (`/config/home-assistant.log`). - - - -## Common Problems & Solutions - - - - - - - -#### :bulb: Lights Not Responding or Turning On by Themselves - -Adaptive Lighting sends more commands to lights than a typical human user would. If your light control network is unhealthy, you may experience: - -- Laggy manual commands (e.g., turning lights on or off). -- Unresponsive lights. -- Home Assistant reporting incorrect light states, causing Adaptive Lighting to inadvertently turn lights back on. - -Most issues that appear to be caused by Adaptive Lighting are actually due to unrelated problems. -Addressing these issues will significantly improve your Home Assistant experience. - -In case lights are suddenly turning on by themselves, this is most likely due to the light incorrectly reporting an "on" state to Home Assistant, leading to an undesired Adaptive Lighting action. -To prevent adapting in cases *where the state of the light is suddenly "on" and only adapt if there is an associated `light.turn_on` service call*, set `detect_non_ha_changes: false`. - -#### :signal_strength: WiFi Networks - -Ensure your light bulbs have a strong WiFi connection. If the signal strength is less than -70dBm, the connection may be weak and prone to dropping messages. - -#### :spider_web: Zigbee, Z-Wave, and Other Mesh Networks - -Mesh networks typically require powered devices to act as routers, relaying messages back to the central coordinator (the radio connected to Home Assistant). -Most modern lights function as routers, very early models may not. -If devices become unresponsive or fail to respond to commands, Adaptive Lighting can exacerbate the issue. -Use network maps (available in ZHA, zigbee2mqtt, deCONZ, and ZWaveJS UI) to evaluate your network health. -Smart plugs can be an affordable way to add more routers to your network. - -For most Zigbee networks, **using groups is essential for optimal performance**. -For example, if you want to use Adaptive Lighting in a hallway with six bulbs, adding each bulb individually to the Adaptive Lighting configuration could overwhelm the network with commands. -Instead, create a group in your Zigbee software (not a regular Home Assistant group) and add that single group to the Adaptive Lighting configuration. -This sends a single broadcast command to adjust all bulbs, improving response times and keeping the bulbs in sync. - -As a rule of thumb, if you always control lights together (e.g., bulbs in a ceiling fixture), they should be in a Zigbee group. -Expose only the group (not individual bulbs) in Home Assistant Dashboards and external systems like Google Home or Apple HomeKit. - -> :warning: **If you control lights individually, `manual_control` cannot behave correctly! If you need to control lights individually as well, use a [Home Assistant Light Group](https://www.home-assistant.io/integrations/group/).** - -#### :rainbow: Light Colors Not Matching - -Bulbs from different manufacturers or models may have varying color temperature specifications. For instance, if you have two Adaptive Lighting configurations—one with only Philips Hue White Ambiance bulbs and another with a mix of Philips Hue White Ambiance and Sengled bulbs—the Philips Hue bulbs may appear to have different color temperatures despite having identical settings. - -To resolve this: - -1. Include only bulbs of the same make and model in a single Adaptive Lighting configuration. -2. Rearrange bulbs so that different color temperatures are not visible simultaneously. - -#### :bulb: Bulb-Specific Issues - -These lights are known to exhibit disadvantageous behaviour due to firmware bugs, insufficient functionality, or hardware limitations: - -- [Sengled Z01-A19NAE26](https://www.zigbee2mqtt.io/devices/Z01-A19NAE26.html#sengled-z01-a19nae26) - - Unexpected turn-ons: If Adaptive Lighting sends a long transition time (like the default 45 seconds), and the bulb is turned off during that time, it may turn back on after approximately 10 seconds to continue the transition command. Since the bulb is turning itself on, there will be no obvious trigger in Home Assistant or other logs indicating the cause of the light turning on. To fix this, set a much shorter `transition` time, such as 1 second. - - Heat sensitivity: Additionally, these bulbs may perform poorly in enclosed "dome" style ceiling lights, particularly when hot. While most LEDs (even non-smart ones) state in the fine print that they do not support working in enclosed fixtures, in practice, more expensive bulbs like Philips Hue generally perform better. To resolve this issue, move the problematic bulbs to open-air fixtures. -- Ikea Tradfri bulbs/drivers (and related Ikea smart light products) - - Unsupported simultaneous transition of brightness and color: When receiving such a command, they switch the brightness instantly and only transition the color. To get smooth transitions of both brightness and color, enable `separate_turn_on_commands`. - - Unresponsiveness during color transitions: No other commands are processed during an ongoing color transition, e.g., turn-off commands are ignored and lights stay on despite being reported as off to Home Assistant. The default config with long transitions thus results in long periods of unresponsiveness. To work around this, disable transitions by setting `transition` to `0`, and increase the adaptation frequency by setting `interval` to a short time, e.g., `15` seconds, to retain the impression of smooth continuous adaptations. Keeping the `initial_transition` is recommended for a smooth fade-in (lights are usually not turned off momentarily after being turned on, in which case a short period of unresponsiveness is tolerable). - - - -## Getting Help - -If you're still having issues: - -1. Enable debug logging and capture the relevant logs -2. Open an issue on [GitHub](https://github.com/basnijholt/adaptive-lighting/issues) -3. Include: - - Your configuration (redact sensitive info) - - Debug logs - - Home Assistant version - - Description of expected vs actual behavior diff --git a/pyproject.toml b/pyproject.toml index 84eb9fae..115395b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,18 +11,6 @@ license = "Apache-2.0" requires-python = ">=3.12" [dependency-groups] -docs = [ - "astral", - "homeassistant", - "markdown-code-runner>=2.7.0", - "markdown-gfm-admonition", - "pandas", - "shinylive", - "tabulate", - "ulid-transform", - "voluptuous", - "zensical", -] dev = [ "mypy", "pre-commit", diff --git a/scripts/update-generated-content b/scripts/update-generated-content deleted file mode 100755 index 1fed3178..00000000 --- a/scripts/update-generated-content +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -cd "$(dirname "$0")/.." - -uv sync --group docs -uv pip install -e . -uv run python docs/run_markdown_code_runner.py -uv run python .github/update-services.py -uv run python .github/update-strings.py diff --git a/tests/test_sensor_platform.py b/tests/test_sensor_platform.py index 6e11a470..d27aeb50 100644 --- a/tests/test_sensor_platform.py +++ b/tests/test_sensor_platform.py @@ -49,7 +49,9 @@ def _unique_id(entry, key: str) -> str: def _resolve_entity_id(hass, entry, key: str) -> str | None: return er.async_get(hass).async_get_entity_id( - "sensor", DOMAIN, _unique_id(entry, key), + "sensor", + DOMAIN, + _unique_id(entry, key), ) @@ -63,7 +65,9 @@ async def test_three_sensor_entities_registered(hass) -> None: registry = er.async_get(hass) for key in SENSOR_KEYS: eid = registry.async_get_entity_id( - "sensor", DOMAIN, _unique_id(entry, key), + "sensor", + DOMAIN, + _unique_id(entry, key), ) assert eid is not None, f"Missing sensor entity for {key}" assert eid.startswith("sensor.") @@ -331,9 +335,9 @@ async def test_friendly_names_compose_correctly(hass) -> None: if fname in names.values(): continue # Any other entity's friendly name must not match a sensor's. - assert fname not in all_friendly, ( - f"Collision: {fname} appears on both sensor and another entity" - ) + assert ( + fname not in all_friendly + ), f"Collision: {fname} appears on both sensor and another entity" # --------------------------------------------------------------------------- @@ -370,11 +374,13 @@ async def test_unload_detaches_dispatcher(hass) -> None: # dispatcher subscription leaked. ent_reg = er.async_get(hass) eid = ent_reg.async_get_entity_id( - "sensor", DOMAIN, _unique_id(entry, "output_brightness"), + "sensor", + DOMAIN, + _unique_id(entry, "output_brightness"), ) if eid is not None: state = hass.states.get(eid) if state is not None: - assert state.state != "99", ( - "Dispatcher subscription leaked: sensor updated after unload" - ) + assert ( + state.state != "99" + ), "Dispatcher subscription leaked: sensor updated after unload" diff --git a/uv.lock b/uv.lock index 9acac2a4..d37ddf28 100644 --- a/uv.lock +++ b/uv.lock @@ -82,23 +82,6 @@ dev = [ { name = "pre-commit" }, { name = "ruff" }, ] -docs = [ - { name = "astral" }, - { name = "homeassistant", version = "2024.12.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "homeassistant", version = "2025.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and python_full_version < '3.13.2'" }, - { name = "homeassistant", version = "2026.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13.2'" }, - { name = "markdown-code-runner" }, - { name = "markdown-gfm-admonition" }, - { name = "pandas" }, - { name = "shinylive" }, - { name = "tabulate" }, - { name = "ulid-transform", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "ulid-transform", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and python_full_version < '3.13.2'" }, - { name = "ulid-transform", version = "1.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13.2'" }, - { name = "voluptuous", version = "0.15.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13.2'" }, - { name = "voluptuous", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13.2'" }, - { name = "zensical" }, -] test = [ { name = "pytest", version = "8.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and python_full_version < '3.13.2'" }, @@ -119,18 +102,6 @@ dev = [ { name = "pre-commit" }, { name = "ruff" }, ] -docs = [ - { name = "astral" }, - { name = "homeassistant" }, - { name = "markdown-code-runner", specifier = ">=2.7.0" }, - { name = "markdown-gfm-admonition" }, - { name = "pandas" }, - { name = "shinylive" }, - { name = "tabulate" }, - { name = "ulid-transform" }, - { name = "voluptuous" }, - { name = "zensical" }, -] test = [ { name = "pytest", specifier = ">=8" }, { name = "pytest-asyncio", specifier = ">=0.23" }, @@ -684,24 +655,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] -[[package]] -name = "appdirs" -version = "1.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, -] - -[[package]] -name = "asgiref" -version = "3.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, -] - [[package]] name = "astral" version = "2.2" @@ -1271,15 +1224,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] -[[package]] -name = "chevron" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/1f/ca74b65b19798895d63a6e92874162f44233467c9e7c1ed8afd19016ebe9/chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf", size = 11440, upload-time = "2021-01-02T22:47:59.233Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/93/342cc62a70ab727e093ed98e02a725d85b746345f05d2b5e5034649f4ec8/chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443", size = 11595, upload-time = "2021-01-02T22:47:57.847Z" }, -] - [[package]] name = "ciso8601" version = "2.3.1" @@ -1359,7 +1303,7 @@ name = "click" version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "python_full_version >= '3.13.2' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ @@ -1708,15 +1652,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/94/b7ff6279e642b014cd4aef4d914b9fca3917c2c9c35df49db062023cbdfc/dbus_fast-3.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1d7cc1315586e4c50875c9a2d56b9ad2e056ec75e2f27c43cd80392f72d0f6e3", size = 1623709, upload-time = "2025-11-17T03:49:59.571Z" }, ] -[[package]] -name = "deepmerge" -version = "2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, -] - [[package]] name = "distlib" version = "0.4.0" @@ -2078,15 +2013,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, ] -[[package]] -name = "future" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, -] - [[package]] name = "greenlet" version = "3.3.0" @@ -2609,19 +2535,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/3f/57e3c7e3daf7cc8509eb139f4faca9198474f8c4f50871d8fe9479adf3da/homeassistant-2026.1.0-py3-none-any.whl", hash = "sha256:29148b15f12fdf398ed8290ad1c23e5241011b225d2f96febcc7390855f18061", size = 50115149, upload-time = "2026-01-07T18:15:19.236Z" }, ] -[[package]] -name = "htmltools" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/1d/c568d17e9fb5ad5aa0ca3531c58d36fe69deb8eee4e53ff6425c1f99f210/htmltools-0.6.0.tar.gz", hash = "sha256:e8a3fb023d748935035db7ff17f620612ffc814a6a80b6ae388f7b7ab182adf7", size = 97152, upload-time = "2024-10-29T20:21:43.378Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/ba/aa99706246f1938ca905eb6eeb7db832ac2e157aa4b805acb5cd4cd1791a/htmltools-0.6.0-py3-none-any.whl", hash = "sha256:072a274ff5e2851e0acce13fc5bb2bbdbbad8268dc8b123f881c05012ce7dce0", size = 84954, upload-time = "2024-10-29T20:21:42.067Z" }, -] - [[package]] name = "httpcore" version = "1.0.9" @@ -3016,18 +2929,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/ba/f6f6573bb21e51b838f1e7b0e8ef831d50db6d0530a5afaba700a34d9e12/license_expression-30.4.3-py3-none-any.whl", hash = "sha256:fd3db53418133e0eef917606623bc125fbad3d1225ba8d23950999ee87c99280", size = 117085, upload-time = "2025-06-25T13:02:24.503Z" }, ] -[[package]] -name = "linkify-it-py" -version = "2.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "uc-micro-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946, upload-time = "2024-02-04T14:48:04.179Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820, upload-time = "2024-02-04T14:48:02.496Z" }, -] - [[package]] name = "litellm" version = "1.80.15" @@ -3073,57 +2974,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/18/06d9710cb0a0d3634f8501e4bdcc07abe64a32e404d82895a6a36fab97f6/lru_dict-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf9da32ef2582434842ab6ba6e67290debfae72771255a8e8ab16f3e006de0aa", size = 13811, upload-time = "2023-11-06T01:39:21.599Z" }, ] -[[package]] -name = "lzstring" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "future" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ab/0c/28347673b45e5f0975cdf1f6d69ede6ad049be873194c4e164d79aecd34c/lzstring-1.0.4.tar.gz", hash = "sha256:1afa61e598193fbcc211e0899f09a9679e33f9102bccc37fbfda0b7fef4d9ea2", size = 4256, upload-time = "2018-06-01T02:32:12.639Z" } - -[[package]] -name = "markdown" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, -] - -[[package]] -name = "markdown-code-runner" -version = "2.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/28/d1/2a7753e05dcc711552721048a34341b63a6a2b09e70ac738c7a4c81ca414/markdown_code_runner-2.7.0.tar.gz", hash = "sha256:b36f9314839c0db3ee5e5d644e1b4471454af8f624eb15bd5a5d68dbc6430afe", size = 99832, upload-time = "2026-01-24T21:20:04.772Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/92/9d7fa0567cf3d38b551bb1a30c177285e0e42ae2b10c16946af7e8a38e73/markdown_code_runner-2.7.0-py3-none-any.whl", hash = "sha256:fad5d5fbad9c49687141ab9c39ed7e054f857b97aa79d11f031de157afd21c20", size = 14692, upload-time = "2026-01-24T21:20:03.382Z" }, -] - -[[package]] -name = "markdown-gfm-admonition" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/5b/1e7114e8fac6aadf2a25dab2e24a1eba54acd504d6a5747928a3eb09d7fd/markdown_gfm_admonition-0.3.0.tar.gz", hash = "sha256:20a37febc79222badb6dfbfcbf0a74b94c0a6259b0f27e9f783601fea59094b6", size = 5817, upload-time = "2025-11-28T09:01:38.849Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/86/ce4288122111c11bb2483a962357baa9793817d48ac1e99ce5ff47384efe/markdown_gfm_admonition-0.3.0-py3-none-any.whl", hash = "sha256:8e49bfea892f4c220b7f7b9479462360b69c68ae7cae7739e288b5382d0ae9b5", size = 6416, upload-time = "2025-11-28T09:01:37.543Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, -] - [[package]] name = "markupsafe" version = "3.0.3" @@ -3199,27 +3049,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/2d/edc147aa1dce9e0e377687d453e62fdfcbccc08cd35d0b7413e3485c4b92/mashumaro-3.17-py3-none-any.whl", hash = "sha256:3964e2c804f62de9e4c58fb985de71dcd716f9507cc18374b1bd5c4f1a1b879b", size = 94198, upload-time = "2025-10-03T21:09:25.436Z" }, ] -[[package]] -name = "mdit-py-plugins" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - [[package]] name = "mock-open" version = "1.4.0" @@ -3376,15 +3205,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] -[[package]] -name = "narwhals" -version = "2.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/6d/b57c64e5038a8cf071bce391bb11551657a74558877ac961e7fa905ece27/narwhals-2.15.0.tar.gz", hash = "sha256:a9585975b99d95084268445a1fdd881311fa26ef1caa18020d959d5b2ff9a965", size = 603479, upload-time = "2026-01-06T08:10:13.27Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl", hash = "sha256:cbfe21ca19d260d9fd67f995ec75c44592d1f106933b03ddd375df7ac841f9d6", size = 432856, upload-time = "2026-01-06T08:10:11.511Z" }, -] - [[package]] name = "nodeenv" version = "1.10.0" @@ -3722,55 +3542,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219, upload-time = "2024-04-29T19:52:48.345Z" }, ] -[[package]] -name = "pandas" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "numpy", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and python_full_version < '3.13.2'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13.2'" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, - { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, - { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, - { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, - { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, - { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, -] - [[package]] name = "pathspec" version = "1.0.3" @@ -4030,18 +3801,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] -[[package]] -name = "prompt-toolkit" -version = "3.0.52" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, -] - [[package]] name = "propcache" version = "0.2.1" @@ -4588,20 +4347,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/0e/bf3473d86648a17e6dd6ee9e6abce526b077169031177f4f2031368f864a/pylint_per_file_ignores-1.4.0-py3-none-any.whl", hash = "sha256:0cd82d22551738b4e63a0aa1dab2a1fc4016e8f27f1429159616483711e122fd", size = 4888, upload-time = "2025-01-17T21:35:00.371Z" }, ] -[[package]] -name = "pymdown-extensions" -version = "10.20" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown" }, - { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13.2'" }, - { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13.2'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/35/e3814a5b7df295df69d035cfb8aab78b2967cdf11fcfae7faed726b66664/pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52", size = 852774, upload-time = "2025-12-31T19:59:42.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f", size = 268733, upload-time = "2025-12-31T19:59:40.652Z" }, -] - [[package]] name = "pymicro-vad" version = "1.0.1" @@ -5313,15 +5058,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, ] -[[package]] -name = "python-multipart" -version = "0.0.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" }, -] - [[package]] name = "python-slugify" version = "8.0.4" @@ -5433,18 +5169,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] -[[package]] -name = "questionary" -version = "2.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "prompt-toolkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" }, -] - [[package]] name = "referencing" version = "0.37.0" @@ -5804,70 +5528,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] -[[package]] -name = "shiny" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asgiref" }, - { name = "click", marker = "sys_platform != 'emscripten'" }, - { name = "htmltools" }, - { name = "linkify-it-py" }, - { name = "markdown-it-py" }, - { name = "mdit-py-plugins" }, - { name = "narwhals" }, - { name = "orjson", version = "3.10.12", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "orjson", version = "3.10.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' and python_full_version < '3.13.2'" }, - { name = "orjson", version = "3.11.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13.2'" }, - { name = "packaging" }, - { name = "platformdirs" }, - { name = "prompt-toolkit", marker = "sys_platform != 'emscripten'" }, - { name = "python-multipart", marker = "sys_platform != 'emscripten'" }, - { name = "questionary", marker = "sys_platform != 'emscripten'" }, - { name = "setuptools" }, - { name = "shinychat" }, - { name = "starlette" }, - { name = "typing-extensions" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, - { name = "watchfiles", marker = "sys_platform != 'emscripten'" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/8c/570fb0c8aa3b3f2b30e2f401bbe4048e7c39fb5ffb70994520fdff32a8da/shiny-1.5.1.tar.gz", hash = "sha256:482fa54635a6dc7e914cdbaa9099fe445790af3053d849b4e9a81e0f61677d37", size = 4941243, upload-time = "2025-12-08T18:18:23.763Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/09/2d/c955cbc6e66aff92645a7af35fa017001dffecb849b764f609fdc68da088/shiny-1.5.1-py3-none-any.whl", hash = "sha256:5ed60168e94fce0b7fc65bc04b92398e2611a2410acbcb850f8ae3001b99de40", size = 3943136, upload-time = "2025-12-08T18:18:21.654Z" }, -] - -[[package]] -name = "shinychat" -version = "0.2.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "htmltools" }, - { name = "shiny" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/c6/6887a3618842e65794e5dcfb345825ccd43d58c56fefa9d31286d8e14d16/shinychat-0.2.8.tar.gz", hash = "sha256:d27f28ddf1d512a05ef2cc2f0cffbb75b5b6e3157d5e3690ce4e63d18bfa7492", size = 547318, upload-time = "2025-09-11T20:13:20.367Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/01/8658eaffa920f4c621f05acc42ea98248147706079287b3a7ab4e47e1ece/shinychat-0.2.8-py3-none-any.whl", hash = "sha256:6742a2354257280458269a8b5675f1254e5583be34b6eb1a626de44c0323286a", size = 561057, upload-time = "2025-09-11T20:13:18.683Z" }, -] - -[[package]] -name = "shinylive" -version = "0.8.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "appdirs" }, - { name = "chevron" }, - { name = "click" }, - { name = "lzstring" }, - { name = "setuptools" }, - { name = "shiny" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/b4/a741e01b756d76bf5839d47c54562563353fd5c5849e0cbb8b5b8ea23d8b/shinylive-0.8.5.tar.gz", hash = "sha256:d485efa14a4053be0e6ec19a21a49e5d0b6bd8b99cd2f5cb81b0b04f57a1c249", size = 29519, upload-time = "2025-12-08T21:08:27.076Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/bb/8dc375e7fb90737fe64a3aa150441a667225672d5d4b735880e4a41f1686/shinylive-0.8.5-py3-none-any.whl", hash = "sha256:85ac83b5b86adf181f79937a50c997315d30cd944a565a31d1a99abc8d715dcd", size = 29432, upload-time = "2025-12-08T21:08:25.868Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -6069,19 +5729,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/85/a1808451ac0b36c61dffe8aea21e45c64ba7da28f6cb0d269171298c6281/standard_telnetlib-3.13.0-py3-none-any.whl", hash = "sha256:b268060a3220c80c7887f2ad9df91cd81e865f0c5052332b81d80ffda8677691", size = 9995, upload-time = "2024-10-30T16:01:29.289Z" }, ] -[[package]] -name = "starlette" -version = "0.51.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/65/5a1fadcc40c5fdc7df421a7506b79633af8f5d5e3a95c3e72acacec644b9/starlette-0.51.0.tar.gz", hash = "sha256:4c4fda9b1bc67f84037d3d14a5112e523509c369d9d47b111b2f984b0cc5ba6c", size = 2647658, upload-time = "2026-01-10T20:23:15.043Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/c4/09985a03dba389d4fe16a9014147a7b02fa76ef3519bf5846462a485876d/starlette-0.51.0-py3-none-any.whl", hash = "sha256:fb460a3d6fd3c958d729fdd96aee297f89a51b0181f16401fe8fd4cb6129165d", size = 74133, upload-time = "2026-01-10T20:23:13.445Z" }, -] - [[package]] name = "syrupy" version = "4.7.2" @@ -6129,15 +5776,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/9a/6c68aad2ccfce6e2eeebbf5bb709d0240592eb51ff142ec4c8fbf3c2460a/syrupy-5.0.0-py3-none-any.whl", hash = "sha256:c848e1a980ca52a28715cd2d2b4d434db424699c05653bd1158fb31cf56e9546", size = 49087, upload-time = "2025-09-28T21:15:11.639Z" }, ] -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, -] - [[package]] name = "termcolor" version = "3.3.0" @@ -6314,15 +5952,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/64/edf33c2d7fba7d6bf057c9dc4235bfc699517ea4c996240a1a9c2bf51c29/uart_devices-0.1.1-py3-none-any.whl", hash = "sha256:55bc8cce66465e90b298f0910e5c496bc7be021341c5455954cf61c6253dc123", size = 4827, upload-time = "2025-02-22T16:47:04.286Z" }, ] -[[package]] -name = "uc-micro-py" -version = "1.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043, upload-time = "2024-02-09T16:52:01.654Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" }, -] - [[package]] name = "ulid-transform" version = "1.0.2" @@ -6547,19 +6176,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/37/ef/813cfedda3c8e49d8b59a41c14fcc652174facfd7a1caf9fee162b40ccbd/uv-0.9.17-py3-none-win_arm64.whl", hash = "sha256:6761076b27a763d0ede2f5e72455d2a46968ff334badf8312bb35988c5254831", size = 20435751, upload-time = "2025-12-09T23:01:19.732Z" }, ] -[[package]] -name = "uvicorn" -version = "0.40.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" }, -] - [[package]] name = "virtualenv" version = "20.36.1" @@ -6681,85 +6297,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/41/d536d9cf39821c35cc13aff403728e60e32b2fd711c240b6b9980af1c03f/voluptuous_serialize-2.7.0-py3-none-any.whl", hash = "sha256:ee3ebecace6136f38d0bf8c20ee97155db2486c6b2d0795563fafd04a519e76f", size = 7850, upload-time = "2025-08-17T10:43:03.498Z" }, ] -[[package]] -name = "watchfiles" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, - { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, - { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, - { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, - { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, - { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, - { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, - { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, - { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, - { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, - { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, - { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, - { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, - { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, - { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, - { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, - { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, - { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, - { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, - { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, - { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, - { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, - { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, - { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, - { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, - { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, - { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, - { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, - { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, - { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, - { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, - { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, - { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, - { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, - { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, - { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, - { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, - { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, -] - -[[package]] -name = "wcwidth" -version = "0.2.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, -] - [[package]] name = "webrtc-models" version = "0.3.0" @@ -6775,51 +6312,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f2/e7/62f29980c9e8d75af93b642a0c37aa8e201fd5268ba3a7179c172549bac3/webrtc_models-0.3.0-py3-none-any.whl", hash = "sha256:8fddded3ffd7ca837de878033501927580799a2c1b7829f7ae8a0f43b49004ea", size = 7476, upload-time = "2024-11-18T17:43:44.165Z" }, ] -[[package]] -name = "websockets" -version = "16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, - { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, - { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, - { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, - { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, - { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, - { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, - { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, - { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, - { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, - { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, - { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, - { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, - { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, - { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, - { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, - { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, - { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, -] - [[package]] name = "winrt-runtime" version = "3.2.1" @@ -7150,35 +6642,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] -[[package]] -name = "zensical" -version = "0.0.15" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "deepmerge" }, - { name = "markdown" }, - { name = "pygments" }, - { name = "pymdown-extensions" }, - { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13.2'" }, - { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13.2'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/ad/87b7b591551c74de67b08dcf172532fbb6df6f0e626dce9f220aae293052/zensical-0.0.15.tar.gz", hash = "sha256:b3200c91b30370671c50b8b4aa41c20e55ff2814b9003ee23c9b6f923a0c19be", size = 3816831, upload-time = "2025-12-24T11:15:49.058Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/07/ede00d39ff6cff7ab4971d15caa04a6710b126cbf0c8342add0337f4db89/zensical-0.0.15-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:13f205d24baeb4a77d096c3d385a8496850567b45c397cd54dde7c22dcbf0da8", size = 11934661, upload-time = "2025-12-24T11:15:09.019Z" }, - { url = "https://files.pythonhosted.org/packages/d6/32/24449c59f90a6a17dd0d9740ee44f245887d0c177c9c1dc452b9eb812024/zensical-0.0.15-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:99702504d38d8f7da4bf9a69513d2f65a0371dcb8f0e9f180c861cb285adb61f", size = 11820384, upload-time = "2025-12-24T11:15:12.265Z" }, - { url = "https://files.pythonhosted.org/packages/ac/db/64fed914788f2f5a8880dcc9a08ce25bcf1a7f2586a09e5d7d61003fd652/zensical-0.0.15-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:051ea368d268ffebf7ed7b6422f211a57680b2810fcde7f251816eb5c8d2f488", size = 12128961, upload-time = "2025-12-24T11:15:16.178Z" }, - { url = "https://files.pythonhosted.org/packages/59/ca/04fd676880acad9571736e470e1f7bd8e6a2391f09952e69800f1b77fc75/zensical-0.0.15-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31a333afaf54d042bb51ac7dc623bd1b3bbe173c0ce4c7b72764cdef143ff4e2", size = 12098586, upload-time = "2025-12-24T11:15:19.452Z" }, - { url = "https://files.pythonhosted.org/packages/8b/02/6a6ecad3b4cb07e11c8d160882cc937f8e3e96868f598c371892f1e594d7/zensical-0.0.15-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:072f80b7346ad4657a4c23008ae5cd3b3511e3bfac8d6bb277cbb6b3c11b6387", size = 12418552, upload-time = "2025-12-24T11:15:23.096Z" }, - { url = "https://files.pythonhosted.org/packages/bb/b7/2a7205dfbeeb8612fbb1e22ad9f770804a07e440b276dcf23cbce3592da4/zensical-0.0.15-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec1b146adb3ee4146a3f800e0a4e6c8e681092d87ac51c60e35e99b68a724b31", size = 12193618, upload-time = "2025-12-24T11:15:26.359Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f4/5f38022d09e668622e49e8b5606bbfb5177d42ab1b4e91706ef0d38e2422/zensical-0.0.15-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:07a24e11a0e00d14f37d209cc37df446f91908109ca86ab3fe41c0b981e32668", size = 12308805, upload-time = "2025-12-24T11:15:29.602Z" }, - { url = "https://files.pythonhosted.org/packages/48/f2/6dd7657d6e1c1cd4b01ad531cea6a95eb660a2f960b03415d2184720a283/zensical-0.0.15-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:cc2bc9b4f89863d8b3443b0b9446ccaf5181085c3fc7a7e1bbc4708afe864f7d", size = 12367060, upload-time = "2025-12-24T11:15:32.621Z" }, - { url = "https://files.pythonhosted.org/packages/d3/83/781bdfbf459ec84b085085b142f433541de70d642d665227442a4deb9360/zensical-0.0.15-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:a3809be6d5f25bbd69dbe43715a60bf9d0186c51a1ec821e02f106e660e632b4", size = 12493255, upload-time = "2025-12-24T11:15:36.126Z" }, - { url = "https://files.pythonhosted.org/packages/54/10/0440e848658b97467c15c356f9038906fa90ac52b7f969ed1cce5deaf017/zensical-0.0.15-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4fd9382db77eff79eb347d4d0b6194c1c285e4151ad104bc9c8f65a27cb71d7f", size = 12430315, upload-time = "2025-12-24T11:15:39.697Z" }, - { url = "https://files.pythonhosted.org/packages/19/d9/50f3e833075e678047cd6e302f731b4b992762d366949124c2eeed6bd96c/zensical-0.0.15-cp310-abi3-win32.whl", hash = "sha256:1d29712dd4659e26b351417534e2ad6364f506514e168ac4c0ed42093b3a9469", size = 11559368, upload-time = "2025-12-24T11:15:42.893Z" }, - { url = "https://files.pythonhosted.org/packages/18/07/cf06fc620dd94b2ffaa3e9df47b174603e0234096fd857b25dcda6c4a538/zensical-0.0.15-cp310-abi3-win_amd64.whl", hash = "sha256:0d0b303ec8a7aec2e733239f21d3602ce29e0eaabe4e4d60c9bcccb5c2d162dc", size = 11740088, upload-time = "2025-12-24T11:15:45.631Z" }, -] - [[package]] name = "zeroconf" version = "0.146.0" diff --git a/webapp/README.md b/webapp/README.md index e561b8f4..b20b9e91 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -1,3 +1,18 @@ +> ### ⚠️ Not used by the CDiT fork +> +> This Shiny webapp is the **upstream** Adaptive Lighting simulator. It +> models the upstream curve (with `brightness_mode`, `brightness_mode_time_*`, +> sleep mode, etc.) — features the CDiT fork has **removed**. +> +> The CDiT fork uses a fixed-tanh curve with a hardcoded 30-minute half-width, +> anchored at the user's configured `sunrise_entity` / `sunset_entity`. +> The simulator's outputs will not match what the integration computes. +> +> Kept here for upstream-merge hygiene. Not built or deployed by the fork's +> CI. See [`../README.md`](../README.md) for the fork's actual behavior. + +--- + To run this app locally, install the requirements and run `shiny run`. ``` diff --git a/zensical.toml b/zensical.toml deleted file mode 100644 index dfa1b309..00000000 --- a/zensical.toml +++ /dev/null @@ -1,120 +0,0 @@ -# Adaptive Lighting Documentation Site Configuration -# Built with Zensical - https://zensical.org - -[project] -site_name = "Adaptive Lighting" -site_description = "Automatically adjust brightness and color of lights based on the sun position" -site_author = "Bas Nijholt" -site_url = "https://adaptive-lighting.nijho.lt/" -copyright = "Copyright © 2020-2026 Bas Nijholt" - -repo_url = "https://github.com/basnijholt/adaptive-lighting" -repo_name = "GitHub" -edit_uri = "edit/main/docs" - -nav = [ - { "Home" = "index.md" }, - { "Getting Started" = "getting-started.md" }, - { "Configuration" = "configuration.md" }, - { "Services" = "services.md" }, - { "Automation Examples" = "automation-examples.md" }, - { "Troubleshooting" = "troubleshooting.md" }, - { "Advanced" = [ - { "Brightness Modes" = "advanced/brightness-modes.md" }, - { "Manual Control" = "advanced/manual-control.md" }, - { "Sleep Mode" = "advanced/sleep-mode.md" }, - ] }, - { "See Also" = "see-also.md" }, -] - -[project.theme] -custom_dir = "docs/overrides" -language = "en" -logo = "assets/logo.png" - -features = [ - "announce.dismiss", - "content.action.edit", - "content.action.view", - "content.code.annotate", - "content.code.copy", - "content.code.select", - "content.footnote.tooltips", - "content.tabs.link", - "content.tooltips", - "navigation.footer", - "navigation.indexes", - "navigation.instant", - "navigation.instant.prefetch", - "navigation.path", - "navigation.top", - "navigation.tracking", - "search.highlight", -] - -# Three-way toggle: system preference -> light -> dark -> system preference -[[project.theme.palette]] -media = "(prefers-color-scheme)" -toggle.icon = "lucide/sun-moon" -toggle.name = "Switch to light mode" - -[[project.theme.palette]] -media = "(prefers-color-scheme: light)" -scheme = "default" -primary = "amber" -accent = "orange" -toggle.icon = "lucide/sun" -toggle.name = "Switch to dark mode" - -[[project.theme.palette]] -media = "(prefers-color-scheme: dark)" -scheme = "slate" -primary = "amber" -accent = "orange" -toggle.icon = "lucide/moon-star" -toggle.name = "Switch to system preference" - -[project.theme.font] -text = "Inter" -code = "JetBrains Mono" - -[project.theme.icon] -repo = "lucide/github" - -[project.extra] -generator = false - -[project.extra.analytics] -provider = "custom" - -[[project.extra.social]] -icon = "fontawesome/brands/github" -link = "https://github.com/basnijholt/adaptive-lighting" - -[[project.extra.social]] -icon = "fontawesome/brands/discord" -link = "https://discord.gg/home-assistant" - -# Enable GitHub-style admonitions (> [!NOTE], > [!TIP], etc.) -[project.markdown_extensions.gfm_admonition] - -# Enable markdown inside HTML blocks (e.g.,
) -[project.markdown_extensions.md_in_html] - -# Enable attribute lists for adding CSS classes (e.g., {.md-button}) -[project.markdown_extensions.attr_list] - -# Enable syntax highlighting for fenced code blocks -[project.markdown_extensions.pymdownx.highlight] -anchor_linenums = true - -[project.markdown_extensions.pymdownx.superfences] - -# Enable tabs (=== "Tab Name" syntax) -[project.markdown_extensions.pymdownx.tabbed] -alternate_style = true - -# Enable emoji/icon support -[project.markdown_extensions.pymdownx.emoji] -emoji_index = "zensical.extensions.emoji.twemoji" -emoji_generator = "zensical.extensions.emoji.to_svg"
- - Install via HACS - - - Try the Simulator - -