Compare commits

...

70 Commits

Author SHA1 Message Date
J. Nick Koston b2257caeb7 touch ups 2026-05-22 09:11:01 -05:00
J. Nick Koston 0ec0ea30ac single pass 2026-05-22 09:06:15 -05:00
J. Nick Koston 584b32c8b3 address copilot, cleanups 2026-05-22 09:01:39 -05:00
J. Nick Koston 4033a8b83a Apply suggestions from code review
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2026-05-22 08:53:47 -05:00
J. Nick Koston add8a5f799 Merge branch 'dev' into cache-split-tests 2026-05-22 08:53:27 -05:00
DeerMaximum 40c0d79d1d Replaced duplicate constant with homeassistant.const in NINA (#171852) 2026-05-22 15:41:04 +02:00
J. Nick Koston 7c137b5c73 cleanup 2026-05-22 08:34:32 -05:00
Franck Nijhof bef8632d78 Fix OpenHome config flow crash when UDN is a list (#171841) 2026-05-22 15:23:42 +02:00
Duco Sebel f00decfaa3 Use uptime device class for HomeWizard uptime sensor (#171830) 2026-05-22 15:23:09 +02:00
Manu 42e7add026 Add selector options translations to System Bridge integration (#171771) 2026-05-22 15:22:22 +02:00
Franck Nijhof 263aa3f16e Fix Hue device trigger crash for devices removed from bridge (#171844) 2026-05-22 15:18:00 +02:00
mhuiskes 03b364dcf0 Refactor zeversolar tests: use fixtures, patch at use site, add unique_id (#171697) 2026-05-22 14:58:56 +02:00
Duco Sebel 3b1aaf39af Bumb python homewizard energy 10.1.0 (#171826) 2026-05-22 14:51:58 +02:00
J. Nick Koston 4a6c5b5a22 cleanups 2026-05-22 07:46:31 -05:00
Franck Nijhof b82ba43fa4 Add pylint checker for invalid MDI icon references (#171824)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 13:45:40 +02:00
starkillerOG d81ef5593c Bump reolink_aio to 0.20.0: Reolink battery camera support (#171836) 2026-05-22 12:59:33 +02:00
Manu 5c5e50f024 Fix platform unloading in System Bridge integration (#171822) 2026-05-22 12:56:03 +02:00
Lex Postma e796d9c467 Update strings.json to align with HomeWizard app (#171740)
Co-authored-by: Duco Sebel <74970928+DCSBL@users.noreply.github.com>
2026-05-22 12:54:58 +02:00
Karl Beecken 342f23526f Remove empty requirements_test_all.txt (#171530 follow-up) (#171834) 2026-05-22 12:38:58 +02:00
Erik Montnemery 814ec697cf Remove advanced mode from hue service actions (#171442) 2026-05-22 11:45:33 +02:00
Erik Montnemery 120f1446d4 Rename advanced section to additional options in telegram_bot service actions (#171460) 2026-05-22 11:44:05 +02:00
Franck Nijhof 170af75b7d Fix Lutron Caseta battery sensor crash on unsupported devices (#171829) 2026-05-22 11:37:05 +02:00
Ariel Ebersberger 5432d29489 Use is/is not for same-enum identity comparisons (tests) (#171689) 2026-05-22 11:32:27 +02:00
Franck Nijhof 8098f4f6bc Fix invalid MDI icon references (#171831)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 11:25:28 +02:00
Simone Chemelli 6a70077687 Fix exception translation placeholder mismatches in comelit (#171748) 2026-05-22 11:17:17 +02:00
Max Michels 5dbb0464ba Replace duplicate constants with homeassistant.const imports (#171815) 2026-05-22 11:10:21 +02:00
dependabot[bot] 1df165ea02 Bump j178/prek-action from 2.0.3 to 2.0.4 (#171812)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 10:39:43 +02:00
Manu 62542eb911 Replace duplicate constants with homeassistant.const imports in xiaomi_miio (#171823) 2026-05-22 10:39:09 +02:00
Max Michels a842cac34c Replace duplicate constants with homeassistant.const imports (#171817) 2026-05-22 10:38:06 +02:00
Simone Chemelli 2460f688e3 Add missing exception translation keys in alexa_devices (#171749) 2026-05-22 10:34:00 +02:00
Simone Chemelli a868ea443c Fix hardcoded exception strings in uptimerobot (#171744) 2026-05-22 10:33:07 +02:00
Franck Nijhof 1d8565483b Apply web search citation stripping for GPT-5.x models in OpenAI conversation (#170956) 2026-05-22 10:31:10 +02:00
dependabot[bot] 1ef3301253 Bump github/codeql-action from 4.35.4 to 4.35.5 (#171813)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 09:47:16 +02:00
Manu 525952f016 Add entity translations to System Bridge integration (#171807) 2026-05-22 09:00:54 +02:00
Shay Levy 3257275c5a Fix LG webOS TV hardcoded exception strings (#171777) 2026-05-22 08:28:19 +02:00
Max Michels cb54fd4921 Replace duplicate constants with homeassistant.const imports (#171809) 2026-05-22 07:57:08 +02:00
Max Michels b391fc61ea Replace duplicate constants with homeassistant.const imports (#171808) 2026-05-22 07:56:29 +02:00
J. Nick Koston 1009ce4180 Merge branch 'dev' into cache-split-tests 2026-05-21 23:09:44 -05:00
J. Nick Koston fcd4e4939c Bump habluetooth to 6.2.0 (#171800) 2026-05-21 23:08:17 -05:00
J. Nick Koston deb8b5da05 Parallelize pytest --collect-only in split_tests.py (#171772) 2026-05-21 22:58:01 -04:00
g4bri3lDev c7754a6ce9 Bump py-opendisplay to 7.2.3 (#171775) 2026-05-21 22:52:36 -04:00
J. Nick Koston 242724bd50 Bump aiodiscover to 3.2.3 (#171803) 2026-05-21 22:51:54 -04:00
Max Michels 42454563db Replace duplicate constants with homeassistant.const imports (#171790)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2026-05-21 22:51:34 -04:00
J. Nick Koston bf03d0c216 Bump dbus-fast to 5.0.3 (#171595) 2026-05-21 21:11:35 -05:00
Max Michels 568107e06b Replace duplicate constants with homeassistant.const imports (#171784) 2026-05-22 01:33:48 +03:00
Jens Timmerman 7da44428b6 Bump guntamatic to v1.9.0 (#171631) 2026-05-21 22:55:29 +01:00
J. Nick Koston 22fb68b7a1 Revert "DNM: test cache, touch cloud manifest only"
This reverts commit a8bc244a7a.
2026-05-21 16:53:19 -05:00
J. Nick Koston 81e06539e6 Revert "DNM: test cache bust, touch cloud conftest"
This reverts commit 7c18b67b2e.
2026-05-21 16:53:19 -05:00
Max Michels 0a27f31949 Replace duplicate constants with homeassistant.const imports (#171781) 2026-05-21 22:53:07 +01:00
Erwin Douna 905b868c82 Add recreate services to Portainer (#167225)
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
2026-05-21 22:52:07 +01:00
J. Nick Koston 7c18b67b2e DNM: test cache bust, touch cloud conftest 2026-05-21 16:48:05 -05:00
J. Nick Koston a8bc244a7a DNM: test cache, touch cloud manifest only 2026-05-21 16:44:43 -05:00
Max Michels 3187289913 Replace duplicate constants with homeassistant.const imports (#171776) 2026-05-22 00:18:54 +03:00
Max Michels 87cecd4a44 Replace duplicate constants with homeassistant.const imports (#171778) 2026-05-22 00:18:23 +03:00
Robert Svensson fed38b0e38 Replace duplicate ATTR_LOCKED constant with homeassistant.const import in deconz (#171779) 2026-05-22 00:17:22 +03:00
J. Nick Koston 5975f4b179 Skip cache walking when --cache is not passed
Address Copilot review feedback on the cache PR:

* Split collect_tests into _collect_tests_uncached (the original
  directory-based pre-cache flow) and _collect_tests_cached (walks
  the tree to build per-file hashes).  Without --cache we now skip
  the walk + per-file hash entirely.
* A single-file root has no ancestor conftests to walk, so the
  conftest_hash would always be empty and stale counts could survive
  a real conftest change; bypass the cache for the file-root case.
* Save the cache file with explicit utf-8 encoding and
  ensure_ascii=False.
2026-05-21 16:08:44 -05:00
J. Nick Koston 9ed16b63a3 Cache per-file test counts in split_tests
Persist the result of pytest --collect-only between CI runs as a JSON
file keyed by content hash, so unchanged test files are served from
cache and only edited or new files are re-collected.  The cache is
self-healing:

* Missing, corrupt, or wrong-version files fall back to a full collect.
* Any conftest.py change anywhere under the test root invalidates the
  whole cache, so fixture parametrization shifts cannot silently skew
  counts.
* Files pytest returns nothing for (helper modules named test_*.py with
  no test functions) are cached as zero so they don't get re-collected
  forever.

Walking is done once with os.walk (~2x faster than Path.rglob) and
collects test files plus conftests in a single pass.  When the cache
is fully cold we feed pytest top-level directories rather than
thousands of file paths so cold runs stay as fast as before the cache
landed.

Wire the new --cache flag through the prepare-pytest-full job and back
the cache file with actions/cache so PRs can pick up the latest dev
snapshot via restore-keys.  Local timings: cold 11s, warm with no diff
0.4s, warm with one file edited 2.3s.
2026-05-21 15:56:08 -05:00
Raphael Hehl 6a36d1260b Bump uiprotect to 10.5.0 (#171768)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-05-21 15:42:31 -05:00
J. Nick Koston 8dadaa2f9e Filter fan-out children and fail fast on empty batch list
Only pass directories and test_*.py files to pytest --collect-only so
helpers like tests/components/conftest.py and tests/components/common.py
are not treated as explicit collection targets, and bail out with a
clear error if no eligible paths are found instead of running pytest
with no arguments.
2026-05-21 15:17:42 -05:00
J. Nick Koston 4f98c71586 Run pytest --collect-only in parallel batches in split_tests
cProfile showed 99.6% of split_tests.py wall time was spent in the
single pytest --collect-only subprocess.  Fan out the collection across
``os.cpu_count()`` workers; round-robin chunking keeps each batch
roughly equal, and tests/components is expanded one level deeper so
the ~1000 integration subdirectories distribute evenly.  Local wall
time dropped from ~132s to ~11s on an 18-core box.  Bucket output is
unchanged because we still parse the same pytest -qq output, just
aggregated from multiple invocations.
2026-05-21 15:10:01 -05:00
Raphael Hehl 49fc1b413d Bump pydantic to 2.13.4 (#171763)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-05-21 14:42:06 -05:00
Abílio Costa bffb0417cc Instruct agents to run prek after doing changes (#171757) 2026-05-21 20:16:26 +01:00
G Johansson 8b8c687fc3 Remove not needed exception handling in dnsip (#171758) 2026-05-21 20:58:32 +02:00
Lukas e3dd6b5fc5 Fix hardcoded exception strings in pooldose integration (#171652)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-21 20:36:17 +02:00
Ariel Ebersberger 94d620438b Use is/is not for same-enum identity comparisons (source) (#171591) 2026-05-21 19:30:55 +02:00
Erik Montnemery 8867b792dc Remove use of advanced mode from the zha integration (#171753) 2026-05-21 19:26:24 +02:00
G Johansson 97967abfeb Fix missing string in smhi (#171756) 2026-05-21 19:25:03 +02:00
mhuiskes af8fea272d Declare Bronze quality scale for Zeversolar integration (#170410) 2026-05-21 19:12:54 +02:00
Simone Chemelli 2db0eed570 Fix hardcoded exception strings in samsungtv (#171745) 2026-05-21 18:59:37 +02:00
Erwin Douna ded1628c20 Downloader add missing data description (#171727) 2026-05-21 18:59:24 +02:00
396 changed files with 13455 additions and 1633 deletions
+1
View File
@@ -15,6 +15,7 @@ Dockerfile.dev linguist-language=Dockerfile
# Generated files
CODEOWNERS linguist-generated=true
homeassistant/generated/*.py linguist-generated=true
pylint/plugins/pylint_home_assistant/generated/*.py linguist-generated=true
machine/* linguist-generated=true
mypy.ini linguist-generated=true
requirements.txt linguist-generated=true
+1
View File
@@ -25,6 +25,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
- When entering a new environment or worktree, run `script/setup` to set up the virtual environment with all development dependencies (pylint, pre-commit hooks, etc.). This is required before committing.
- .vscode/tasks.json contains useful commands used for development.
- After finishing a code session, run `uv run prek run --all-files` to check for linting and formatting issues.
## Python Syntax Notes
+29 -3
View File
@@ -281,7 +281,7 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json"
echo "::add-matcher::.github/workflows/matchers/codespell.json"
- name: Run prek
uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2.0.3
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
env:
PREK_SKIP: no-commit-to-branch,mypy,pylint,gen_requirements_all,hassfest,hassfest-metadata,hassfest-mypy-config,zizmor
RUFF_OUTPUT_FORMAT: github
@@ -302,7 +302,7 @@ jobs:
with:
persist-credentials: false
- name: Run zizmor
uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2.0.3
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
with:
extra-args: --all-files zizmor
@@ -917,12 +917,38 @@ jobs:
key: >-
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Restore pytest test counts cache
id: cache-pytest-counts
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: pytest_test_counts.json
key: >-
pytest-counts-${{ runner.os }}-${{ runner.arch }}-${{
steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}-${{ github.sha }}
restore-keys: |
pytest-counts-${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }}-
- name: Run split_tests.py
env:
TEST_GROUP_COUNT: ${{ needs.info.outputs.test_group_count }}
run: |
. venv/bin/activate
python -m script.split_tests ${TEST_GROUP_COUNT} tests
python -m script.split_tests \
--cache pytest_test_counts.json \
${TEST_GROUP_COUNT} tests
- name: Save pytest test counts cache
# Only the canonical dev push writes the cache, otherwise every PR
# build would create a new entry and the actions/cache quota fills
# up with near-duplicate snapshots. PRs and feature branches still
# restore from dev's most recent cache via restore-keys.
if: |
github.event_name == 'push'
&& github.ref == 'refs/heads/dev'
&& steps.cache-pytest-counts.outputs.cache-hit != 'true'
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: pytest_test_counts.json
key: ${{ steps.cache-pytest-counts.outputs.cache-primary-key }}
- name: Upload pytest_buckets
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
+2 -2
View File
@@ -28,11 +28,11 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
with:
category: "/language:python"
+1
View File
@@ -15,6 +15,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
- When entering a new environment or worktree, run `script/setup` to set up the virtual environment with all development dependencies (pylint, pre-commit hooks, etc.). This is required before committing.
- .vscode/tasks.json contains useful commands used for development.
- After finishing a code session, run `uv run prek run --all-files` to check for linting and formatting issues.
## Python Syntax Notes
+1 -1
View File
@@ -134,7 +134,7 @@ class AuthManagerFlowManager(
"""
flow = cast(LoginFlow, flow)
if result["type"] != FlowResultType.CREATE_ENTRY:
if result["type"] is not FlowResultType.CREATE_ENTRY:
return result
# we got final result
@@ -11,7 +11,7 @@
"service": "mdi:dialpad"
},
"alarm_toggle_chime": {
"service": "mdi:abc"
"service": "mdi:bell-ring"
}
}
}
@@ -91,7 +91,6 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
translation_placeholders={"error": repr(err)},
) from err
except CannotAuthenticate as err:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
@@ -102,6 +102,9 @@
"entry_not_loaded": {
"message": "Entry not loaded: {entry}"
},
"invalid_auth": {
"message": "Invalid authentication credentials: {error}"
},
"invalid_device_id": {
"message": "Invalid device ID specified: {device_id}"
},
@@ -226,7 +226,7 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
) -> SubentryFlowResult:
"""Set initial options."""
# abort if entry is not loaded
if self._get_entry().state != ConfigEntryState.LOADED:
if self._get_entry().state is not ConfigEntryState.LOADED:
return self.async_abort(reason="entry_not_loaded")
hass_apis: list[SelectOptionDict] = [
@@ -89,7 +89,7 @@ class AOSmithWaterHeaterEntity(AOSmithStatusEntity, WaterHeaterEntity):
def supported_features(self) -> WaterHeaterEntityFeature:
"""Return the list of supported features."""
supports_vacation_mode = any(
supported_mode.mode == AOSmithOperationMode.VACATION
supported_mode.mode is AOSmithOperationMode.VACATION
for supported_mode in self.device.supported_modes
)
@@ -122,7 +122,7 @@ class AOSmithWaterHeaterEntity(AOSmithStatusEntity, WaterHeaterEntity):
@property
def is_away_mode_on(self) -> bool:
"""Return True if away mode is on."""
return self.device.status.current_mode == AOSmithOperationMode.VACATION
return self.device.status.current_mode is AOSmithOperationMode.VACATION
async def async_set_operation_mode(self, operation_mode: str) -> None:
"""Set new target operation mode."""
@@ -369,7 +369,7 @@ class AppleTVManager(DeviceListener):
attrs[ATTR_MODEL] = (
dev_info.raw_model
if dev_info.model == DeviceModel.Unknown and dev_info.raw_model
if dev_info.model is DeviceModel.Unknown and dev_info.raw_model
else model_str(dev_info.model)
)
attrs[ATTR_SW_VERSION] = dev_info.version
@@ -63,7 +63,7 @@ class AppleTVKeyboardFocused(AppleTVEntity, BinarySensorEntity, KeyboardListener
# Listen to keyboard updates
atv.keyboard.listener = self
# Set initial state based on current focus state
self._update_state(atv.keyboard.text_focus_state == KeyboardFocusState.Focused)
self._update_state(atv.keyboard.text_focus_state is KeyboardFocusState.Focused)
@callback
def async_device_disconnected(self) -> None:
@@ -78,7 +78,7 @@ class AppleTVKeyboardFocused(AppleTVEntity, BinarySensorEntity, KeyboardListener
This is a callback function from pyatv.interface.KeyboardListener.
"""
self._update_state(new_state == KeyboardFocusState.Focused)
self._update_state(new_state is KeyboardFocusState.Focused)
def _update_state(self, new_state: bool) -> None:
"""Update and report."""
@@ -354,7 +354,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
"name": self.atv.name,
"type": (
dev_info.raw_model
if dev_info.model == DeviceModel.Unknown and dev_info.raw_model
if dev_info.model is DeviceModel.Unknown and dev_info.raw_model
else model_str(dev_info.model)
),
}
@@ -441,12 +441,12 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
return await self.async_step_password()
# Figure out, depending on protocol, what kind of pairing is needed
if service.pairing == PairingRequirement.Unsupported:
if service.pairing is PairingRequirement.Unsupported:
_LOGGER.debug("%s does not support pairing", self.protocol)
return await self.async_pair_next_protocol()
if service.pairing == PairingRequirement.Disabled:
if service.pairing is PairingRequirement.Disabled:
return await self.async_step_protocol_disabled()
if service.pairing == PairingRequirement.NotNeeded:
if service.pairing is PairingRequirement.NotNeeded:
_LOGGER.debug("%s does not require pairing", self.protocol)
self.credentials[self.protocol.value] = None
return await self.async_pair_next_protocol()
@@ -457,7 +457,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
pair_args: dict[str, Any] = {}
if self.protocol in {Protocol.AirPlay, Protocol.Companion, Protocol.DMAP}:
pair_args["name"] = "Home Assistant"
if self.protocol == Protocol.DMAP:
if self.protocol is Protocol.DMAP:
pair_args["zeroconf"] = await zeroconf.async_get_instance(self.hass)
# Initiate the pairing process
@@ -139,7 +139,7 @@ class AppleTvMediaPlayer(
all_features = atv.features.all_features()
for feature_name, support_flag in SUPPORT_FEATURE_MAPPING.items():
feature_info = all_features.get(feature_name)
if feature_info and feature_info.state != FeatureState.Unsupported:
if feature_info and feature_info.state is not FeatureState.Unsupported:
self._attr_supported_features |= support_flag
# No need to schedule state update here as that will happen when the first
@@ -188,14 +188,14 @@ class AppleTvMediaPlayer(
return MediaPlayerState.OFF
if (
self._is_feature_available(FeatureName.PowerState)
and self.atv.power.power_state == PowerState.Off
and self.atv.power.power_state is PowerState.Off
):
return MediaPlayerState.OFF
if self._playing:
state = self._playing.device_state
if state in (DeviceState.Idle, DeviceState.Loading):
return MediaPlayerState.IDLE
if state == DeviceState.Playing:
if state is DeviceState.Playing:
return MediaPlayerState.PLAYING
if state in (DeviceState.Paused, DeviceState.Seeking, DeviceState.Stopped):
return MediaPlayerState.PAUSED
@@ -446,7 +446,7 @@ class AppleTvMediaPlayer(
def shuffle(self) -> bool | None:
"""Boolean if shuffle is enabled."""
if self._playing and self._is_feature_available(FeatureName.Shuffle):
return self._playing.shuffle != ShuffleState.Off
return self._playing.shuffle is not ShuffleState.Off
return None
def _is_feature_available(self, feature: FeatureName) -> bool:
@@ -506,7 +506,7 @@ class AppleTvMediaPlayer(
and (self._is_feature_available(FeatureName.TurnOff))
and (
not self._is_feature_available(FeatureName.PowerState)
or self.atv.power.power_state == PowerState.On
or self.atv.power.power_state is PowerState.On
)
):
await self.atv.power.turn_off()
@@ -59,7 +59,7 @@ def _check_keyboard_focus(atv: AppleTVInterface) -> None:
translation_domain=DOMAIN,
translation_key="keyboard_not_available",
) from err
if focus_state != KeyboardFocusState.Focused:
if focus_state is not KeyboardFocusState.Focused:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="keyboard_not_focused",
@@ -263,9 +263,9 @@ class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
def media_channel(self) -> str | None:
"""Channel currently playing."""
source = self._state.get_source()
if source == SourceCodes.DAB:
if source is SourceCodes.DAB:
value = self._state.get_dab_station()
elif source == SourceCodes.FM:
elif source is SourceCodes.FM:
value = self._state.get_rds_information()
else:
value = None
@@ -274,7 +274,7 @@ class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
@property
def media_artist(self) -> str | None:
"""Artist of current playing media, music track only."""
if self._state.get_source() == SourceCodes.DAB:
if self._state.get_source() is SourceCodes.DAB:
value = self._state.get_dls_pdt()
else:
value = None
@@ -1355,7 +1355,7 @@ class PipelineRun:
) -> bool:
"""Return true if all targeted entities were in the same area as the device."""
if (
intent_response.response_type != intent.IntentResponseType.ACTION_DONE
intent_response.response_type is not intent.IntentResponseType.ACTION_DONE
or not intent_response.matched_states
):
return False
+4 -4
View File
@@ -251,12 +251,12 @@ class AuthProvidersView(HomeAssistantView):
def _prepare_result_json(result: AuthFlowResult) -> dict[str, Any]:
"""Convert result to JSON serializable dict."""
if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
if result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY:
return {
key: val for key, val in result.items() if key not in ("result", "data")
}
if result["type"] != data_entry_flow.FlowResultType.FORM:
if result["type"] is not data_entry_flow.FlowResultType.FORM:
return result # type: ignore[return-value]
data = dict(result)
@@ -289,11 +289,11 @@ class LoginFlowBaseView(HomeAssistantView):
result: AuthFlowResult,
) -> web.Response:
"""Convert the flow result to a response."""
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
if result["type"] is not data_entry_flow.FlowResultType.CREATE_ENTRY:
# @log_invalid_auth does not work here since it returns HTTP 200.
# We need to manually log failed login attempts.
if (
result["type"] == data_entry_flow.FlowResultType.FORM
result["type"] is data_entry_flow.FlowResultType.FORM
and (errors := result.get("errors"))
and errors.get("base")
in (
@@ -142,9 +142,9 @@ def websocket_depose_mfa(
def _prepare_result_json(result: data_entry_flow.FlowResult) -> dict[str, Any]:
"""Convert result to JSON serializable dict."""
if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
if result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY:
return dict(result)
if result["type"] != data_entry_flow.FlowResultType.FORM:
if result["type"] is not data_entry_flow.FlowResultType.FORM:
return result # type: ignore[return-value]
data = dict(result)
@@ -20,7 +20,7 @@
"bluetooth-adapters==2.1.0",
"bluetooth-auto-recovery==1.5.3",
"bluetooth-data-tools==1.29.11",
"dbus-fast==5.0.0",
"habluetooth==6.1.0"
"dbus-fast==5.0.3",
"habluetooth==6.2.0"
]
}
@@ -273,7 +273,7 @@ async def ws_subscribe_scanner_details(
def _async_registration_changed(registration: HaScannerRegistration) -> None:
added_event = HaScannerRegistrationEvent.ADDED
event_type = "add" if registration.event == added_event else "remove"
event_type = "add" if registration.event is added_event else "remove"
_async_event_message({event_type: [registration.scanner.details]})
manager = _get_manager(hass)
+4 -1
View File
@@ -158,7 +158,10 @@ def process_service_info(
)
# If payload is encrypted and the bindkey is not verified then we need to reauth
if data.encryption_scheme != EncryptionScheme.NONE and not data.bindkey_verified:
if (
data.encryption_scheme is not EncryptionScheme.NONE
and not data.bindkey_verified
):
entry.async_start_reauth(hass, data={"device": data})
return update
@@ -59,7 +59,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN):
self._discovery_info = discovery_info
self._discovered_device = device
if device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
if device.encryption_scheme is EncryptionScheme.BTHOME_BINDKEY:
return await self.async_step_get_encryption_key()
return await self.async_step_bluetooth_confirm()
@@ -125,7 +125,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN):
self._discovery_info = discovery.discovery_info
self._discovered_device = discovery.device
if discovery.device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
if discovery.device.encryption_scheme is EncryptionScheme.BTHOME_BINDKEY:
return await self.async_step_get_encryption_key()
return self._async_get_or_create_entry()
@@ -164,7 +164,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN):
self._discovery_info = device.last_service_info
if device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
if device.encryption_scheme is EncryptionScheme.BTHOME_BINDKEY:
return await self.async_step_get_encryption_key()
# Otherwise there wasn't actually encryption so abort
@@ -110,7 +110,7 @@ class ComelitAlarmEntity(
@property
def available(self) -> bool:
"""Return True if alarm is available."""
if self._area.human_status == AlarmAreaState.UNKNOWN:
if self._area.human_status is AlarmAreaState.UNKNOWN:
return False
return super().available
@@ -124,7 +124,7 @@ class ComelitAlarmEntity(
self._area.human_status,
self._area.armed,
)
if self._area.human_status == AlarmAreaState.ARMED:
if self._area.human_status is AlarmAreaState.ARMED:
if self._area.armed == ALARM_AREA_ARMED_STATUS[AWAY]:
return AlarmControlPanelState.ARMED_AWAY
if self._area.armed == ALARM_AREA_ARMED_STATUS[NIGHT]:
@@ -43,7 +43,7 @@ BINARY_SENSOR_TYPES: Final[tuple[ComelitBinarySensorEntityDescription, ...]] = (
device_class=BinarySensorDeviceClass.PROBLEM,
is_on_fn=lambda obj: cast(ComelitVedoAreaObject, obj).anomaly,
available_fn=lambda obj: (
cast(ComelitVedoAreaObject, obj).human_status != AlarmAreaState.UNKNOWN
cast(ComelitVedoAreaObject, obj).human_status is not AlarmAreaState.UNKNOWN
),
),
ComelitBinarySensorEntityDescription(
@@ -67,7 +67,7 @@ BINARY_SENSOR_TYPES: Final[tuple[ComelitBinarySensorEntityDescription, ...]] = (
object_type=ALARM_ZONE,
device_class=BinarySensorDeviceClass.PROBLEM,
is_on_fn=lambda obj: (
cast(ComelitVedoZoneObject, obj).human_status == AlarmZoneState.FAULTY
cast(ComelitVedoZoneObject, obj).human_status is AlarmZoneState.FAULTY
),
available_fn=lambda obj: (
cast(ComelitVedoZoneObject, obj).human_status
@@ -65,11 +65,9 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
translation_placeholders={"error": repr(err)},
) from err
except aiocomelit_exceptions.CannotAuthenticate as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise InvalidAuth(
translation_domain=DOMAIN,
translation_key="cannot_authenticate",
translation_placeholders={"error": repr(err)},
) from err
finally:
await api.logout()
+2 -2
View File
@@ -166,12 +166,12 @@ class ComelitVedoSensorEntity(
@property
def available(self) -> bool:
"""Sensor availability."""
return self._zone_object.human_status != AlarmZoneState.UNAVAILABLE
return self._zone_object.human_status is not AlarmZoneState.UNAVAILABLE
@property
def native_value(self) -> StateType:
"""Sensor value."""
if (status := self._zone_object.human_status) == AlarmZoneState.UNKNOWN:
if (status := self._zone_object.human_status) is AlarmZoneState.UNKNOWN:
return None
return cast(str, status.value)
@@ -148,7 +148,7 @@ def _prepare_config_flow_result_json(
prepare_result_json: Callable[[data_entry_flow.FlowResult], dict[str, Any]],
) -> dict[str, Any]:
"""Convert result to JSON."""
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
if result["type"] is not data_entry_flow.FlowResultType.CREATE_ENTRY:
return prepare_result_json(result)
data = {key: val for key, val in result.items() if key not in ("data", "context")}
@@ -646,7 +646,7 @@ class DefaultAgent(ConversationEntity):
cache_value = self._intent_cache.get(cache_key)
if cache_value is not None:
if (cache_value.result is not None) and (
cache_value.stage == IntentMatchingStage.EXPOSED_ENTITIES_ONLY
cache_value.stage is IntentMatchingStage.EXPOSED_ENTITIES_ONLY
):
_LOGGER.debug("Got cached result for exposed entities")
return cache_value.result
@@ -686,7 +686,7 @@ class DefaultAgent(ConversationEntity):
skip_unexposed_entities_match = False
if cache_value is not None:
if (cache_value.result is not None) and (
cache_value.stage == IntentMatchingStage.UNEXPOSED_ENTITIES
cache_value.stage is IntentMatchingStage.UNEXPOSED_ENTITIES
):
_LOGGER.debug("Got cached result for all entities")
return cache_value.result
@@ -731,7 +731,7 @@ class DefaultAgent(ConversationEntity):
skip_unknown_names = False
if cache_value is not None:
if (cache_value.result is not None) and (
cache_value.stage == IntentMatchingStage.UNKNOWN_NAMES
cache_value.stage is IntentMatchingStage.UNKNOWN_NAMES
):
_LOGGER.debug("Got cached result for unknown names")
return cache_value.result
@@ -1546,7 +1546,7 @@ def _get_match_error_response(
# device_class only
return ErrorKey.NO_DEVICE_CLASS, {"device_class": device_class}
if (reason == intent.MatchFailedReason.DOMAIN) and constraints.domains:
if (reason is intent.MatchFailedReason.DOMAIN) and constraints.domains:
domain = next(iter(constraints.domains)) # first domain
if constraints.area_name:
# domain in area
@@ -1565,7 +1565,7 @@ def _get_match_error_response(
# domain only
return ErrorKey.NO_DOMAIN, {"domain": domain}
if reason == intent.MatchFailedReason.DUPLICATE_NAME:
if reason is intent.MatchFailedReason.DUPLICATE_NAME:
if constraints.floor_name:
# duplicate on floor
return ErrorKey.DUPLICATE_ENTITIES_IN_FLOOR, {
@@ -1582,26 +1582,26 @@ def _get_match_error_response(
return ErrorKey.DUPLICATE_ENTITIES, {"entity": result.no_match_name}
if reason == intent.MatchFailedReason.INVALID_AREA:
if reason is intent.MatchFailedReason.INVALID_AREA:
# Invalid area name
return ErrorKey.NO_AREA, {"area": result.no_match_name}
if reason == intent.MatchFailedReason.INVALID_FLOOR:
if reason is intent.MatchFailedReason.INVALID_FLOOR:
# Invalid floor name
return ErrorKey.NO_FLOOR, {"floor": result.no_match_name}
if reason == intent.MatchFailedReason.FEATURE:
if reason is intent.MatchFailedReason.FEATURE:
# Feature not supported by entity
return ErrorKey.FEATURE_NOT_SUPPORTED, {}
if reason == intent.MatchFailedReason.STATE:
if reason is intent.MatchFailedReason.STATE:
# Entity is not in correct state
assert constraints.states
state = next(iter(constraints.states))
return ErrorKey.ENTITY_WRONG_STATE, {"state": state}
if reason == intent.MatchFailedReason.ASSISTANT:
if reason is intent.MatchFailedReason.ASSISTANT:
# Not exposed
if constraints.name:
if constraints.area_name:
@@ -61,7 +61,7 @@ class CurrencylayerSensor(SensorEntity):
"""Implementing the Currencylayer sensor."""
_attr_attribution = "Data provided by currencylayer.com"
_attr_icon = "mdi:currency"
_attr_icon = "mdi:currency-usd"
def __init__(self, rest: CurrencylayerData, base: str, quote: str) -> None:
"""Initialize the sensor."""
+2 -2
View File
@@ -26,12 +26,12 @@ from homeassistant.components.climate import (
HVACAction,
HVACMode,
)
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.const import ATTR_LOCKED, ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DeconzConfigEntry
from .const import ATTR_LOCKED, ATTR_OFFSET, ATTR_VALVE
from .const import ATTR_OFFSET, ATTR_VALVE
from .entity import DeconzDevice
from .hub import DeconzHub
-2
View File
@@ -43,8 +43,6 @@ PLATFORMS = [
]
ATTR_DARK = "dark"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_LOCKED = "locked"
ATTR_OFFSET = "offset"
ATTR_ON = "on"
ATTR_VALVE = "valve"
@@ -80,7 +80,7 @@ async def async_validate_device_automation_config(
# the checks below which look for a config entry matching the device automation
# domain
if (
automation_type == DeviceAutomationType.ACTION
automation_type is DeviceAutomationType.ACTION
and validated_config[CONF_DOMAIN] in ENTITY_PLATFORMS
):
# Pass the unvalidated config to avoid mutating the raw config twice
+1 -1
View File
@@ -16,7 +16,7 @@
"quality_scale": "internal",
"requirements": [
"aiodhcpwatcher==1.2.1",
"aiodiscover==3.2.0",
"aiodiscover==3.2.3",
"cached-ipaddress==1.0.1"
]
}
@@ -261,7 +261,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
except KeyError, ValueError:
bootid = None
if change == ssdp.SsdpChange.UPDATE:
if change is ssdp.SsdpChange.UPDATE:
# This is an announcement that bootid is about to change
if self._bootid is not None and self._bootid == bootid:
# Store the new value (because our old value matches) so that we
@@ -281,7 +281,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
await self._device_disconnect()
self._bootid = bootid
if change == ssdp.SsdpChange.BYEBYE:
if change is ssdp.SsdpChange.BYEBYE:
# Device is going away
if self._device:
# Disconnect from gone device
@@ -290,7 +290,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
self._ssdp_connect_failed = False
if (
change == ssdp.SsdpChange.ALIVE
change is ssdp.SsdpChange.ALIVE
and not self._device
and not self._ssdp_connect_failed
):
@@ -718,7 +718,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
# If already playing, or don't want to autoplay, no need to call Play
autoplay = extra.get("autoplay", True)
if self._device.transport_state == TransportState.PLAYING or not autoplay:
if self._device.transport_state is TransportState.PLAYING or not autoplay:
return
# Play it
@@ -748,7 +748,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
if not (play_mode := self._device.play_mode):
return None
if play_mode == PlayMode.VENDOR_DEFINED:
if play_mode is PlayMode.VENDOR_DEFINED:
return None
return play_mode in (PlayMode.SHUFFLE, PlayMode.RANDOM)
@@ -782,10 +782,10 @@ class DlnaDmrEntity(MediaPlayerEntity):
if not (play_mode := self._device.play_mode):
return None
if play_mode == PlayMode.VENDOR_DEFINED:
if play_mode is PlayMode.VENDOR_DEFINED:
return None
if play_mode == PlayMode.REPEAT_ONE:
if play_mode is PlayMode.REPEAT_ONE:
return RepeatMode.ONE
if play_mode in (PlayMode.REPEAT_ALL, PlayMode.RANDOM):
+3 -3
View File
@@ -236,7 +236,7 @@ class DmsDeviceSource:
except KeyError, ValueError:
bootid = None
if change == ssdp.SsdpChange.UPDATE:
if change is ssdp.SsdpChange.UPDATE:
# This is an announcement that bootid is about to change
if self._bootid is not None and self._bootid == bootid:
# Store the new value (because our old value matches) so that we
@@ -258,7 +258,7 @@ class DmsDeviceSource:
await self.device_disconnect()
self._bootid = bootid
if change == ssdp.SsdpChange.BYEBYE:
if change is ssdp.SsdpChange.BYEBYE:
# Device is going away
if self._device:
# Disconnect from gone device
@@ -267,7 +267,7 @@ class DmsDeviceSource:
self._ssdp_connect_failed = False
if (
change == ssdp.SsdpChange.ALIVE
change is ssdp.SsdpChange.ALIVE
and not self._device
and not self._ssdp_connect_failed
):
+1 -6
View File
@@ -6,7 +6,6 @@ import logging
import aiodns
from aiodns.error import DNSError
from pycares import AresError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PORT
@@ -78,11 +77,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: DnsIPConfigEntry) -> boo
) from err
errors = [
result
for result in results
if isinstance(
result, (TimeoutError, DNSError, AresError, asyncio.CancelledError)
)
result for result in results if isinstance(result, (TimeoutError, DNSError))
]
if errors and len(errors) == len(results):
await _close_resolvers()
@@ -8,6 +8,12 @@
},
"step": {
"user": {
"data": {
"download_dir": "Download directory"
},
"data_description": {
"download_dir": "The directory where downloaded files will be stored. This can be an absolute path or a path relative to the Home Assistant configuration directory."
},
"description": "Select a location to get to store downloads. The setup will check if the directory exists."
}
}
+3 -3
View File
@@ -150,9 +150,9 @@ class ElevenLabsSTTEntity(SpeechToTextEntity):
raw_pcm_compatible = (
metadata.codec == AudioCodecs.PCM
and metadata.sample_rate == AudioSampleRates.SAMPLERATE_16000
and metadata.channel == AudioChannels.CHANNEL_MONO
and metadata.bit_rate == AudioBitRates.BITRATE_16
and metadata.sample_rate is AudioSampleRates.SAMPLERATE_16000
and metadata.channel is AudioChannels.CHANNEL_MONO
and metadata.bit_rate is AudioBitRates.BITRATE_16
)
if raw_pcm_compatible:
file_format = "pcm_s16le_16"
@@ -50,5 +50,5 @@ class ElkBinarySensor(ElkAttachedEntity, BinarySensorEntity):
def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
# Zone in NORMAL state is OFF; any other state is ON
self._attr_is_on = bool(
self._element.logical_status != ZoneLogicalStatus.NORMAL
self._element.logical_status is not ZoneLogicalStatus.NORMAL
)
+2 -2
View File
@@ -104,7 +104,7 @@ class ElkThermostat(ElkEntity, ClimateEntity):
ThermostatMode.EMERGENCY_HEAT,
):
return self._element.heat_setpoint
if self._element.mode == ThermostatMode.COOL:
if self._element.mode is ThermostatMode.COOL:
return self._element.cool_setpoint
return None
@@ -162,6 +162,6 @@ class ElkThermostat(ElkEntity, ClimateEntity):
self._attr_hvac_mode = ELK_TO_HASS_HVAC_MODES[self._element.mode]
if (
self._attr_hvac_mode == HVACMode.OFF
and self._element.fan == ThermostatFan.ON
and self._element.fan is ThermostatFan.ON
):
self._attr_hvac_mode = HVACMode.FAN_ONLY
+1 -1
View File
@@ -56,7 +56,7 @@ class ElkNumberSetting(ElkAttachedEntity, NumberEntity):
def __init__(self, element: Setting, elk: Any, elk_data: ELKM1Data) -> None:
"""Initialize the number setting."""
super().__init__(element, elk, elk_data)
if element.value_format == SettingFormat.TIMER:
if element.value_format is SettingFormat.TIMER:
self._attr_device_class = NumberDeviceClass.DURATION
self._attr_native_unit_of_measurement = UnitOfTime.SECONDS
+6 -6
View File
@@ -75,7 +75,7 @@ async def async_setup_entry(
for setting in elk.settings:
setting = cast(Setting, setting)
domain = (
"time" if setting.value_format == SettingFormat.TIME_OF_DAY else "number"
"time" if setting.value_format is SettingFormat.TIME_OF_DAY else "number"
)
orig_unique_id = generate_unique_id(elk_data.prefix, setting)
@@ -288,7 +288,7 @@ class ElkZone(ElkSensor):
@property
def temperature_unit(self) -> str | None:
"""Return the temperature unit."""
if self._element.definition == ZoneType.TEMPERATURE:
if self._element.definition is ZoneType.TEMPERATURE:
return self._temperature_unit
return None
@@ -305,18 +305,18 @@ class ElkZone(ElkSensor):
@property
def native_unit_of_measurement(self) -> str | None:
"""Return the unit of measurement."""
if self._element.definition == ZoneType.TEMPERATURE:
if self._element.definition is ZoneType.TEMPERATURE:
return self._temperature_unit
if self._element.definition == ZoneType.ANALOG_ZONE:
if self._element.definition is ZoneType.ANALOG_ZONE:
return UnitOfElectricPotential.VOLT
return None
def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
if self._element.definition == ZoneType.TEMPERATURE:
if self._element.definition is ZoneType.TEMPERATURE:
self._attr_native_value = temperature_to_state(
self._element.temperature, UNDEFINED_TEMPERATURE
)
elif self._element.definition == ZoneType.ANALOG_ZONE:
elif self._element.definition is ZoneType.ANALOG_ZONE:
self._attr_native_value = f"{self._element.voltage}"
else:
self._attr_native_value = pretty_const(self._element.logical_status.name)
+1 -1
View File
@@ -66,7 +66,7 @@ class ElkThermostatEMHeat(ElkEntity, SwitchEntity):
@property
def is_on(self) -> bool:
"""Get the current emergency heat status."""
return self._element.mode == ThermostatMode.EMERGENCY_HEAT
return self._element.mode is ThermostatMode.EMERGENCY_HEAT
def _elk_set(self, mode: ThermostatMode) -> None:
"""Set the thermostat mode."""
+1 -1
View File
@@ -30,7 +30,7 @@ async def async_setup_entry(
time_settings = [
setting
for setting in cast(list[Setting], elk.settings)
if setting.value_format == SettingFormat.TIME_OF_DAY
if setting.value_format is SettingFormat.TIME_OF_DAY
]
create_elk_entities(
@@ -96,7 +96,7 @@ def __get_coordinator(call: ServiceCall) -> EnergyZeroDataUpdateCoordinator:
"config_entry": entry_id,
},
)
if entry.state != ConfigEntryState.LOADED:
if entry.state is not ConfigEntryState.LOADED:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="unloaded_config_entry",
@@ -125,7 +125,7 @@ async def __get_prices(
data: Electricity | Gas
if price_type == PriceType.GAS:
if price_type is PriceType.GAS:
data = await coordinator.energyzero.get_gas_prices_legacy(
start_date=start,
end_date=end,
+1 -1
View File
@@ -23,7 +23,7 @@
"service": "mdi:refresh"
},
"set_dhw_override": {
"service": "mdi:water-heater"
"service": "mdi:water-boiler"
},
"set_system_mode": {
"service": "mdi:pencil"
@@ -24,7 +24,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: FastdotcomConfigEntry) -
async def _async_finish_startup(hass: HomeAssistant) -> None:
"""Run this only when HA has finished its startup."""
if entry.state == ConfigEntryState.LOADED:
if entry.state is ConfigEntryState.LOADED:
await coordinator.async_refresh()
else:
await coordinator.async_config_entry_first_refresh()
+1 -1
View File
@@ -16,7 +16,7 @@ class DeviceType(Enum):
GAME_CONSOLE = "mdi:nintendo-game-boy"
STREAMING_DONGLE = "mdi:cast"
LOUDSPEAKER = SOUND_SYSTEM = STB = SATELLITE = MUSIC = "mdi:speaker"
DISC_PLAYER = "mdi:disk-player"
DISC_PLAYER = "mdi:disc-player"
REMOTE_CONTROL = "mdi:remote-tv"
RADIO = "mdi:radio"
PHOTO_CAMERA = PHOTOS = "mdi:camera"
@@ -284,7 +284,7 @@ class FishAudioSubentryFlowHandler(ConfigSubentryFlow):
) -> SubentryFlowResult:
"""Manage initial options."""
entry = self._get_entry()
if entry.state != ConfigEntryState.LOADED:
if entry.state is not ConfigEntryState.LOADED:
return self.async_abort(reason="entry_not_loaded")
self.client = entry.runtime_data
+1 -1
View File
@@ -109,7 +109,7 @@ def setup_service(hass: HomeAssistant) -> None:
entry: FlumeConfigEntry | None = hass.config_entries.async_get_entry(entry_id)
if not entry:
raise ValueError(f"Invalid config entry: {entry_id}")
if not entry.state == ConfigEntryState.LOADED:
if entry.state is not ConfigEntryState.LOADED:
raise ValueError(f"Config entry not loaded: {entry_id}")
return {
"notifications": entry.runtime_data.notifications_coordinator.notifications # type: ignore[dict-item]
@@ -136,7 +136,7 @@ class FluxLedConfigFlow(ConfigFlow, domain=DOMAIN):
ConfigEntryState.SETUP_IN_PROGRESS,
ConfigEntryState.NOT_LOADED,
)
) or entry.state == ConfigEntryState.SETUP_RETRY:
) or entry.state is ConfigEntryState.SETUP_RETRY:
self.hass.config_entries.async_schedule_reload(entry.entry_id)
else:
async_dispatcher_send(
+1 -1
View File
@@ -51,7 +51,7 @@ async def async_setup_entry(
entry.data.get(CONF_NAME, entry.title)
base_unique_id = entry.unique_id or entry.entry_id
if device.device_type == DeviceType.Switch:
if device.device_type is DeviceType.Switch:
entities.append(FluxPowerStateSelect(coordinator.device, entry))
if device.operating_modes:
entities.append(
+1 -1
View File
@@ -32,7 +32,7 @@ async def async_setup_entry(
entities: list[FluxSwitch | FluxRemoteAccessSwitch | FluxMusicSwitch] = []
base_unique_id = entry.unique_id or entry.entry_id
if coordinator.device.device_type == DeviceType.Switch:
if coordinator.device.device_type is DeviceType.Switch:
entities.append(FluxSwitch(coordinator, base_unique_id, None))
if entry.data.get(CONF_REMOTE_ACCESS_HOST):
+3 -3
View File
@@ -212,7 +212,7 @@ class FroniusSolarNet:
inverter_info=_inverter_info,
config_entry=self.config_entry,
)
if self.config_entry.state == ConfigEntryState.LOADED:
if self.config_entry.state is ConfigEntryState.LOADED:
await _coordinator.async_refresh()
else:
await _coordinator.async_config_entry_first_refresh()
@@ -220,7 +220,7 @@ class FroniusSolarNet:
# Only for re-scans. Initial setup adds entities
# through sensor.async_setup_entry
if self.config_entry.state == ConfigEntryState.LOADED:
if self.config_entry.state is ConfigEntryState.LOADED:
async_dispatcher_send(self.hass, SOLAR_NET_DISCOVERY_NEW, _coordinator)
_LOGGER.debug(
@@ -235,7 +235,7 @@ class FroniusSolarNet:
try:
_inverter_info = await self.fronius.inverter_info()
except FroniusError as err:
if self.config_entry.state == ConfigEntryState.LOADED:
if self.config_entry.state is ConfigEntryState.LOADED:
# During a re-scan we will attempt again as per schedule.
_LOGGER.debug("Re-scan failed for %s", self.host)
return inverter_infos
@@ -42,7 +42,7 @@ async def _collect_coordinators(
raise HomeAssistantError(f"Device '{target}' not found in device registry")
coordinators = list[FullyKioskDataUpdateCoordinator]()
for config_entry in config_entries:
if config_entry.state != ConfigEntryState.LOADED:
if config_entry.state is not ConfigEntryState.LOADED:
raise HomeAssistantError(f"{config_entry.title} is not loaded")
coordinators.append(config_entry.runtime_data)
return coordinators
+1 -1
View File
@@ -2,7 +2,7 @@
"entity": {
"button": {
"sync_clock": {
"default": "mdi:clock-sync"
"default": "mdi:clock-check"
}
},
"number": {
@@ -75,7 +75,7 @@ async def async_setup_entry(
mfg_data = await async_get_manufacturer_data({address})
product_type = mfg_data[address].product_type
if product_type == ProductType.UNKNOWN:
if product_type is ProductType.UNKNOWN:
raise ConfigEntryNotReady("Unable to find product type")
client = Client(get_connection(hass, address), product_type)
+3 -3
View File
@@ -143,7 +143,7 @@ def _get_entity_descriptions(
local_sync = True
if (
search := data.get(CONF_SEARCH)
) or calendar_item.access_role == AccessRole.FREE_BUSY_READER:
) or calendar_item.access_role is AccessRole.FREE_BUSY_READER:
read_only = True
local_sync = False
entity_description = GoogleCalendarEntityDescription(
@@ -386,14 +386,14 @@ class GoogleCalendarEntity(
"""Return True if the event is visible and not declined."""
if any(
attendee.is_self and attendee.response_status == ResponseStatus.DECLINED
attendee.is_self and attendee.response_status is ResponseStatus.DECLINED
for attendee in event.attendees
):
return False
# Calendar enttiy may be limited to a specific event type
if (
self.entity_description.event_type is not None
and self.entity_description.event_type != event.event_type
and self.entity_description.event_type is not event.event_type
):
return False
# Default calendar entity omits the special types but includes all the others
@@ -247,7 +247,7 @@ class LocationSubentryFlowHandler(ConfigSubentryFlow):
user_input: dict[str, Any] | None = None,
) -> SubentryFlowResult:
"""Handle the location step."""
if self._get_entry().state != ConfigEntryState.LOADED:
if self._get_entry().state is not ConfigEntryState.LOADED:
return self.async_abort(reason="entry_not_loaded")
errors: dict[str, str] = {}
@@ -225,7 +225,7 @@ class LLMSubentryFlowHandler(ConfigSubentryFlow):
) -> SubentryFlowResult:
"""Set conversation options."""
# abort if entry is not loaded
if self._get_entry().state != ConfigEntryState.LOADED:
if self._get_entry().state is not ConfigEntryState.LOADED:
return self.async_abort(reason="entry_not_loaded")
errors: dict[str, str] = {}
@@ -754,7 +754,7 @@ async def async_prepare_files_for_prompt(
config={"http_options": {"timeout": TIMEOUT_MILLIS}},
)
if uploaded_file.state == FileState.FAILED:
if uploaded_file.state is FileState.FAILED:
raise HomeAssistantError(
f"File `{uploaded_file.name}` processing"
" failed, reason:"
@@ -766,7 +766,7 @@ async def async_prepare_files_for_prompt(
tasks = [
asyncio.create_task(wait_for_file_processing(part))
for part in prompt_parts
if part.state != FileState.ACTIVE
if part.state is not FileState.ACTIVE
]
async with asyncio.timeout(TIMEOUT_MILLIS / 1000):
await asyncio.gather(*tasks)
@@ -237,7 +237,7 @@ class LocationSubentryFlowHandler(ConfigSubentryFlow):
user_input: dict[str, Any] | None = None,
) -> SubentryFlowResult:
"""Handle the location step."""
if self._get_entry().state != ConfigEntryState.LOADED:
if self._get_entry().state is not ConfigEntryState.LOADED:
return self.async_abort(reason="entry_not_loaded")
errors: dict[str, str] = {}
@@ -26,7 +26,7 @@ def _get_coordinators(
coordinators: dict[str, GrowattCoordinator] = {}
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.state != ConfigEntryState.LOADED:
if entry.state is not ConfigEntryState.LOADED:
continue
for coord in entry.runtime_data.devices.values():
@@ -14,5 +14,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "silver",
"requirements": ["guntamatic==1.8.0"]
"requirements": ["guntamatic==1.9.0"]
}
+2 -2
View File
@@ -247,7 +247,7 @@ class SupervisorOSUpdateEntity(HassioOSEntity, UpdateEntity):
def release_url(self) -> str | None:
"""URL to the full release notes of the latest version available."""
version = AwesomeVersion(self.latest_version)
if version.dev or version.strategy == AwesomeVersionStrategy.UNKNOWN:
if version.dev or version.strategy is AwesomeVersionStrategy.UNKNOWN:
return "https://github.com/home-assistant/operating-system/commits/dev"
return (
f"https://github.com/home-assistant/operating-system/releases/tag/{version}"
@@ -304,7 +304,7 @@ class SupervisorSupervisorUpdateEntity(HassioSupervisorEntity, UpdateEntity):
def release_url(self) -> str | None:
"""URL to the full release notes of the latest version available."""
version = AwesomeVersion(self.latest_version)
if version.dev or version.strategy == AwesomeVersionStrategy.UNKNOWN:
if version.dev or version.strategy is AwesomeVersionStrategy.UNKNOWN:
return "https://github.com/home-assistant/supervisor/commits/main"
return f"https://github.com/home-assistant/supervisor/releases/tag/{version}"
+1 -1
View File
@@ -150,7 +150,7 @@ def _get_controller(hass: HomeAssistant) -> Heos:
hass.config_entries.async_entry_for_domain_unique_id(DOMAIN, DOMAIN)
)
if not entry or not entry.state == ConfigEntryState.LOADED:
if not entry or entry.state is not ConfigEntryState.LOADED:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="integration_not_loaded"
)
-2
View File
@@ -2,8 +2,6 @@
from homeassistant.const import Platform
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODE = "mode"
ATTR_TIME_PERIOD = "time_period"
ATTR_ONOFF = "on_off"
CONF_CODE = "2fa"
+1 -1
View File
@@ -12,12 +12,12 @@ from homeassistant.components.light import (
ColorMode,
LightEntity,
)
from homeassistant.const import ATTR_MODE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import color as color_util
from . import HiveConfigEntry, refresh_system
from .const import ATTR_MODE
from .entity import HiveEntity
PARALLEL_UPDATES = 0
+1 -2
View File
@@ -6,12 +6,11 @@ from typing import Any
from apyhiveapi import Hive
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.const import ATTR_MODE, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import HiveConfigEntry, refresh_system
from .const import ATTR_MODE
from .entity import HiveEntity
PARALLEL_UPDATES = 0
@@ -418,10 +418,10 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
otbr_manager = get_otbr_addon_manager(self.hass)
addon_info = await self._async_get_addon_info(otbr_manager)
if addon_info.state == AddonState.NOT_INSTALLED:
if addon_info.state is AddonState.NOT_INSTALLED:
return await self.async_step_install_otbr_addon()
if addon_info.state == AddonState.RUNNING:
if addon_info.state is AddonState.RUNNING:
await otbr_manager.async_stop_addon()
return await self.async_step_start_otbr_addon()
@@ -111,7 +111,7 @@ class WaitingAddonManager(AddonManager):
info = None
# Do not try to uninstall an addon if it is already uninstalled
if info is not None and info.state == AddonState.NOT_INSTALLED:
if info is not None and info.state is AddonState.NOT_INSTALLED:
return
await self.async_uninstall_addon()
@@ -383,7 +383,7 @@ class OptionsFlowHandler(OptionsFlow, ABC):
multipan_manager = await get_multiprotocol_addon_manager(self.hass)
addon_info = await self._async_get_addon_info(multipan_manager)
if addon_info.state == AddonState.NOT_INSTALLED:
if addon_info.state is AddonState.NOT_INSTALLED:
return await self.async_step_addon_not_installed()
return await self.async_step_addon_installed()
@@ -691,10 +691,10 @@ class OptionsFlowHandler(OptionsFlow, ABC):
flasher_manager = get_flasher_addon_manager(self.hass)
addon_info = await self._async_get_addon_info(flasher_manager)
if addon_info.state == AddonState.NOT_INSTALLED:
if addon_info.state is AddonState.NOT_INSTALLED:
return await self.async_step_install_flasher_addon()
if addon_info.state == AddonState.NOT_RUNNING:
if addon_info.state is AddonState.NOT_RUNNING:
return await self.async_step_configure_flasher_addon()
# If the addon is already installed and running, fail
@@ -907,7 +907,7 @@ async def check_multi_pan_addon(hass: HomeAssistant) -> None:
# Request the addon to start if it's not started
# `async_start_addon` returns as soon as the start request has been sent
# and does not wait for the addon to be started, so we raise below
if addon_info.state == AddonState.NOT_RUNNING:
if addon_info.state is AddonState.NOT_RUNNING:
await multipan_manager.async_start_addon()
if addon_info.state not in (AddonState.NOT_INSTALLED, AddonState.RUNNING):
@@ -927,7 +927,7 @@ async def multi_pan_addon_using_device(hass: HomeAssistant, device_path: str) ->
multipan_manager = await get_multiprotocol_addon_manager(hass)
addon_info: AddonInfo = await multipan_manager.async_get_addon_info()
if addon_info.state != AddonState.RUNNING:
if addon_info.state is not AddonState.RUNNING:
return False
if addon_info.options["device"] != device_path:
@@ -124,7 +124,7 @@ class OwningAddon:
except AddonError:
return False
else:
return addon_info.state == AddonState.RUNNING
return addon_info.state is AddonState.RUNNING
@asynccontextmanager
async def temporarily_stop(self, hass: HomeAssistant) -> AsyncGenerator[None]:
@@ -137,7 +137,7 @@ class OwningAddon:
yield
return
if addon_info.state != AddonState.RUNNING:
if addon_info.state is not AddonState.RUNNING:
yield
return
@@ -173,7 +173,7 @@ class OwningIntegration:
yield
return
if entry.state != ConfigEntryState.LOADED:
if entry.state is not ConfigEntryState.LOADED:
yield
return
@@ -213,7 +213,7 @@ async def get_otbr_addon_firmware_info(
except AddonError:
return None
if otbr_addon_info.state == AddonState.NOT_INSTALLED:
if otbr_addon_info.state is AddonState.NOT_INSTALLED:
return None
if (otbr_path := otbr_addon_info.options.get("device")) is None:
@@ -238,7 +238,7 @@ async def get_z2m_addon_firmware_info(
except AddonError:
return None
if z2m_addon_info.state == AddonState.NOT_INSTALLED:
if z2m_addon_info.state is AddonState.NOT_INSTALLED:
return None
serial = z2m_addon_info.options.get("serial")
@@ -286,7 +286,7 @@ async def guess_hardware_owners(
except AddonError:
pass
else:
if multipan_addon_info.state != AddonState.NOT_INSTALLED:
if multipan_addon_info.state is not AddonState.NOT_INSTALLED:
multipan_path = multipan_addon_info.options.get("device")
if multipan_path is not None:
@@ -122,7 +122,7 @@ class HomeeConfigFlow(ConfigFlow, domain=DOMAIN):
existing_entry = await self.async_set_unique_id(self._name)
if (
existing_entry
and existing_entry.state == ConfigEntryState.LOADED
and existing_entry.state is ConfigEntryState.LOADED
and existing_entry.runtime_data.connected
and existing_entry.data[CONF_HOST] != self._host
):
@@ -298,7 +298,7 @@ class HKDevice:
# yet.
attempts = None if self.hass.state is CoreState.running else 1
if (
transport == Transport.BLE
transport is Transport.BLE
and pairing.accessories
and pairing.accessories.has_aid(1)
):
@@ -328,7 +328,7 @@ class HKDevice:
)
entry.async_on_unload(self._async_cancel_subscription_timer)
if transport != Transport.BLE:
if transport is not Transport.BLE:
# Although async_populate_accessories_state fetched the accessory database,
# the /accessories endpoint may return cached values from the accessory's
# perspective. For example, Ecobee thermostats may report stale temperature
@@ -349,7 +349,7 @@ class HKDevice:
await self.async_process_entity_map()
if transport != Transport.BLE:
if transport is not Transport.BLE:
# Start regular polling after entity map is processed
self._async_start_polling()
@@ -359,7 +359,7 @@ class HKDevice:
self.async_set_available_state(self.pairing.is_available)
if transport == Transport.BLE:
if transport is Transport.BLE:
# If we are using BLE, we need to periodically check of the
# BLE device is available since we won't get callbacks
# when it goes away since we HomeKit supports disconnected
@@ -420,7 +420,7 @@ class HKDevice:
identifiers.add((IDENTIFIER_SERIAL_NUMBER, accessory.serial_number))
connections: set[tuple[str, str]] = set()
if self.pairing.transport == Transport.BLE and (
if self.pairing.transport is Transport.BLE and (
discovery := self.pairing.controller.discoveries.get(
normalize_hkid(self.unique_id)
)
@@ -622,7 +622,7 @@ class HKDevice:
current_unique_id.add((accessory.aid, service.iid, None))
for char in service.characteristics:
if self.pairing.transport != Transport.BLE:
if self.pairing.transport is not Transport.BLE:
if char.type == CharacteristicsTypes.THREAD_CONTROL_POINT:
continue
@@ -1057,7 +1057,7 @@ class HKDevice:
@property
def is_unprovisioned_thread_device(self) -> bool:
"""Is this a thread capable device not connected by CoAP."""
if self.pairing.controller.transport_type != TransportType.BLE:
if self.pairing.controller.transport_type is not TransportType.BLE:
return False
if not self.entity_map.aid(1).services.first(
@@ -1069,7 +1069,7 @@ class HKDevice:
async def async_thread_provision(self) -> None:
"""Migrate a HomeKit pairing to CoAP (Thread)."""
if self.pairing.controller.transport_type == TransportType.COAP:
if self.pairing.controller.transport_type is TransportType.COAP:
raise HomeAssistantError("Already connected to a thread network")
if not (dataset := await async_get_preferred_dataset(self.hass)):
@@ -700,7 +700,7 @@ async def async_setup_entry(
@callback
def async_add_accessory(accessory: Accessory) -> bool:
if conn.pairing.transport != Transport.BLE:
if conn.pairing.transport is not Transport.BLE:
return False
accessory_info = accessory.services.first(
@@ -13,6 +13,6 @@
"iot_class": "local_polling",
"loggers": ["homewizard_energy"],
"quality_scale": "platinum",
"requirements": ["python-homewizard-energy==10.0.1"],
"requirements": ["python-homewizard-energy==10.1.0"],
"zeroconf": ["_hwenergy._tcp.local.", "_homewizard._tcp.local."]
}
+2 -10
View File
@@ -35,7 +35,6 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util.dt import utcnow
from homeassistant.util.variance import ignore_variance
from .const import DOMAIN
from .coordinator import HomeWizardConfigEntry, HWEnergyDeviceUpdateCoordinator
@@ -66,13 +65,6 @@ def to_percentage(value: float | None) -> float | None:
return value * 100 if value is not None else None
def uptime_to_datetime(value: int) -> datetime:
"""Convert seconds to datetime timestamp."""
return utcnow().replace(microsecond=0) - timedelta(seconds=value)
uptime_to_stable_datetime = ignore_variance(uptime_to_datetime, timedelta(minutes=5))
SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
HomeWizardSensorEntityDescription(
key="smr_version",
@@ -643,7 +635,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
HomeWizardSensorEntityDescription(
key="uptime",
translation_key="uptime",
device_class=SensorDeviceClass.TIMESTAMP,
device_class=SensorDeviceClass.UPTIME,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
has_fn=(
@@ -651,7 +643,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
),
value_fn=(
lambda data: (
uptime_to_stable_datetime(data.system.uptime_s)
utcnow() - timedelta(seconds=data.system.uptime_s)
if data.system is not None and data.system.uptime_s is not None
else None
)
@@ -61,13 +61,14 @@
},
"select": {
"battery_group_mode": {
"name": "Battery group mode",
"name": "Battery group charging strategy",
"state": {
"predictive": "Smart charging",
"standby": "Standby",
"to_full": "Manual charge mode",
"zero": "Zero mode",
"zero_charge_only": "Zero mode (charge only)",
"zero_discharge_only": "Zero mode (discharge only)"
"to_full": "One-time full charge",
"zero": "Net zero",
"zero_charge_only": "Net zero (charge only)",
"zero_discharge_only": "Net zero (discharge only)"
}
}
},
+13 -12
View File
@@ -31,15 +31,16 @@ activate_scene:
dynamic:
selector:
boolean:
speed:
advanced: true
selector:
number:
min: 0
max: 100
brightness:
advanced: true
selector:
number:
min: 1
max: 255
scene_customization:
collapsed: true
fields:
speed:
selector:
number:
min: 0
max: 100
brightness:
selector:
number:
min: 1
max: 255
+6 -1
View File
@@ -184,7 +184,12 @@
"name": "Transition"
}
},
"name": "Activate Hue scene"
"name": "Activate Hue scene",
"sections": {
"scene_customization": {
"name": "Scene customization"
}
}
},
"hue_activate_scene": {
"description": "Activates a Hue scene stored in the Hue hub.",
@@ -87,6 +87,8 @@ def async_get_triggers(
# Get Hue device id from device identifier
hue_dev_id = get_hue_device_id(device_entry)
if hue_dev_id is None or hue_dev_id not in api.devices:
return []
# extract triggers from all button resources of this Hue device
triggers: list[dict[str, Any]] = []
model_id = api.devices[hue_dev_id].product_data.product_name
@@ -69,7 +69,7 @@ class HusqvarnaAutomowerBleConfigFlow(ConfigFlow, domain=DOMAIN):
await async_get_manufacturer_data({discovery_info.address})
)[discovery_info.address]
if manufacturer_data.product_type != ProductType.MOWER:
if manufacturer_data.product_type is not ProductType.MOWER:
LOGGER.debug(
"Unsupported device: %s (%s)", manufacturer_data, discovery_info
)
@@ -567,7 +567,7 @@ class IntegrationSensor(RestoreSensor):
assert old_timestamp is not None
elapsed_seconds = Decimal(
(new_timestamp - old_timestamp).total_seconds()
if self._last_integration_trigger == _IntegrationTrigger.StateEvent
if self._last_integration_trigger is _IntegrationTrigger.StateEvent
else (new_timestamp - self._last_integration_time).total_seconds()
)
@@ -192,11 +192,11 @@ async def async_update_options(
current_control_mode = fireplace.control_mode
# Only update modes that actually changed
if new_read_mode != current_read_mode:
if new_read_mode is not current_read_mode:
LOGGER.debug("Updating read mode: %s -> %s", current_read_mode, new_read_mode)
await fireplace.set_read_mode(new_read_mode)
if new_control_mode != current_control_mode:
if new_control_mode is not current_control_mode:
LOGGER.debug(
"Updating control mode: %s -> %s", current_control_mode, new_control_mode
)
+3 -3
View File
@@ -126,19 +126,19 @@ class IottyShutter(IottyEntity, CoverEntity):
self._iotty_device.percentage,
)
return (
self._iotty_device.status == ShutterState.STATIONARY
self._iotty_device.status is ShutterState.STATIONARY
and self._iotty_device.percentage == 0
)
@property
def is_opening(self) -> bool:
"""Return true if the Shutter is opening."""
return self._iotty_device.status == ShutterState.OPENING
return self._iotty_device.status is ShutterState.OPENING
@property
def is_closing(self) -> bool:
"""Return true if the Shutter is closing."""
return self._iotty_device.status == ShutterState.CLOSING
return self._iotty_device.status is ShutterState.CLOSING
@property
def supported_features(self) -> CoverEntityFeature:
@@ -1,7 +1,7 @@
{
"entity": {
"binary_sensor": {
"erev_shabbat_hag": { "default": "mdi:candle-light" },
"erev_shabbat_hag": { "default": "mdi:candle" },
"issur_melacha_in_effect": { "default": "mdi:power-plug-off" },
"motzei_shabbat_hag": { "default": "mdi:fire" }
},
+1 -1
View File
@@ -7,7 +7,7 @@
"service": "mdi:lock-open"
},
"disable": {
"service": "mdi:fash-off"
"service": "mdi:flash-off"
},
"enable": {
"service": "mdi:flash"
+3 -3
View File
@@ -380,7 +380,7 @@ class _KnxClimate(ClimateEntity, _KnxEntityBase):
self._attr_fan_modes = [fan_zero_mode, FAN_LOW, FAN_HIGH]
elif fan_max_step == 1:
self._attr_fan_modes = [fan_zero_mode, FAN_ON]
elif device.fan_speed_mode == FanSpeedMode.STEP:
elif device.fan_speed_mode is FanSpeedMode.STEP:
self._attr_fan_modes = [fan_zero_mode] + [
str(i) for i in range(1, fan_max_step + 1)
]
@@ -550,7 +550,7 @@ class _KnxClimate(ClimateEntity, _KnxEntityBase):
if not fan_speed or self._attr_fan_modes is None:
return self.fan_zero_mode
if self._device.fan_speed_mode == FanSpeedMode.STEP:
if self._device.fan_speed_mode is FanSpeedMode.STEP:
return self._attr_fan_modes[fan_speed]
# Find the closest fan mode percentage
@@ -570,7 +570,7 @@ class _KnxClimate(ClimateEntity, _KnxEntityBase):
fan_mode_index = self._attr_fan_modes.index(fan_mode)
if self._device.fan_speed_mode == FanSpeedMode.STEP:
if self._device.fan_speed_mode is FanSpeedMode.STEP:
await self._device.set_fan_speed(fan_mode_index)
return
+1 -1
View File
@@ -258,7 +258,7 @@ class KNXModule:
def connection_state_changed_cb(self, state: XknxConnectionState) -> None:
"""Call invoked after a KNX connection state change was received."""
self.connected = state == XknxConnectionState.CONNECTED
self.connected = state is XknxConnectionState.CONNECTED
for device in self.xknx.devices:
device.after_update()
+3 -3
View File
@@ -246,7 +246,7 @@ def async_host_event_received(
):
_LOGGER.info('The connection to host "%s" has been lost', config_entry.title)
hass.async_create_task(reload_config_entry())
elif event == LcnEvent.BUS_DISCONNECTED:
elif event is LcnEvent.BUS_DISCONNECTED:
_LOGGER.info(
'The connection to the LCN bus via host "%s" has been disconnected',
config_entry.title,
@@ -296,7 +296,7 @@ def _async_fire_access_control_event(
if device is not None:
event_data.update({CONF_DEVICE_ID: device.id})
if inp.periphery == pypck.lcn_defs.AccessControlPeriphery.TRANSMITTER:
if inp.periphery is pypck.lcn_defs.AccessControlPeriphery.TRANSMITTER:
event_data.update(
{
"level": inp.level,
@@ -317,7 +317,7 @@ def _async_fire_send_keys_event(
) -> None:
"""Fire send_keys event."""
for table, action in enumerate(inp.actions):
if action == pypck.lcn_defs.SendKeyCommand.DONTSEND:
if action is pypck.lcn_defs.SendKeyCommand.DONTSEND:
continue
for key, selected in enumerate(inp.keys):
+3 -3
View File
@@ -116,7 +116,7 @@ class LcnClimate(LcnEntity, ClimateEntity):
# Config schema only allows for:
# UnitOfTemperature.CELSIUS and
# UnitOfTemperature.FAHRENHEIT
if self.unit == pypck.lcn_defs.VarUnit.FAHRENHEIT:
if self.unit is pypck.lcn_defs.VarUnit.FAHRENHEIT:
return UnitOfTemperature.FAHRENHEIT
return UnitOfTemperature.CELSIUS
@@ -188,11 +188,11 @@ class LcnClimate(LcnEntity, ClimateEntity):
if not isinstance(input_obj, pypck.inputs.ModStatusVar):
return
self._attr_available = True
if input_obj.get_var() == self.variable:
if input_obj.get_var() is self.variable:
self._attr_current_temperature = float(
input_obj.get_value().to_var_unit(self.unit)
)
elif input_obj.get_var() == self.setpoint:
elif input_obj.get_var() is self.setpoint:
self._is_on = not input_obj.get_value().is_locked_regulator()
if self._is_on:
self._attr_target_temperature = float(
+3 -3
View File
@@ -191,7 +191,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
)
)
if self.positioning_mode != pypck.lcn_defs.MotorPositioningMode.NONE:
if self.positioning_mode is not pypck.lcn_defs.MotorPositioningMode.NONE:
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
self.motor = pypck.lcn_defs.MotorPort[config[CONF_DOMAIN_DATA][CONF_MOTOR]]
@@ -267,7 +267,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
| None,
]
] = [self.device_connection.request_status_relays(SCAN_INTERVAL.seconds)]
if self.positioning_mode == pypck.lcn_defs.MotorPositioningMode.BS4:
if self.positioning_mode is pypck.lcn_defs.MotorPositioningMode.BS4:
coros.append(
self.device_connection.request_status_motor_position(
self.motor, self.positioning_mode, SCAN_INTERVAL.seconds
@@ -282,7 +282,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
self._attr_is_opening = input_obj.is_opening(self.motor.value)
self._attr_is_closing = input_obj.is_closing(self.motor.value)
if self.positioning_mode == pypck.lcn_defs.MotorPositioningMode.NONE:
if self.positioning_mode is pypck.lcn_defs.MotorPositioningMode.NONE:
self._attr_is_closed = input_obj.is_assumed_closed(self.motor.value)
self.async_write_ha_state()
elif (
+1 -1
View File
@@ -144,7 +144,7 @@ class LcnVariableSensor(LcnEntity, SensorEntity):
"""Set sensor value when LCN input object (command) is received."""
if (
not isinstance(input_obj, pypck.inputs.ModStatusVar)
or input_obj.get_var() != self.variable
or input_obj.get_var() is not self.variable
):
return
self._attr_available = True
+1 -1
View File
@@ -330,7 +330,7 @@ class SendKeys(LcnServiceCall):
if (delay_time := service.data[CONF_TIME]) != 0:
hit = pypck.lcn_defs.SendKeyCommand.HIT
if pypck.lcn_defs.SendKeyCommand[service.data[CONF_STATE]] != hit:
if pypck.lcn_defs.SendKeyCommand[service.data[CONF_STATE]] is not hit:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_send_keys_action",
+1 -1
View File
@@ -200,7 +200,7 @@ class LcnRegulatorLockSwitch(LcnEntity, SwitchEntity):
"""Set switch state when LCN input object (command) is received."""
if (
not isinstance(input_obj, pypck.inputs.ModStatusVar)
or input_obj.get_var() != self.setpoint_variable
or input_obj.get_var() is not self.setpoint_variable
):
return
self._attr_available = True
+4 -4
View File
@@ -28,25 +28,25 @@
"ice_maker": {
"default": "mdi:cube-outline",
"state": {
"off": "mdi:cube-outline-off"
"off": "mdi:cube-off-outline"
}
},
"ice_maker_bottom_zone": {
"default": "mdi:cube-outline",
"state": {
"off": "mdi:cube-outline-off"
"off": "mdi:cube-off-outline"
}
},
"ice_maker_middle_zone": {
"default": "mdi:cube-outline",
"state": {
"off": "mdi:cube-outline-off"
"off": "mdi:cube-off-outline"
}
},
"ice_maker_top_zone": {
"default": "mdi:cube-outline",
"state": {
"off": "mdi:cube-outline-off"
"off": "mdi:cube-off-outline"
}
}
},
@@ -54,4 +54,4 @@ class LutronOccupancySensor(LutronDevice, BinarySensorEntity):
def _update_attrs(self) -> None:
"""Update the state attributes."""
self._attr_is_on = self._lutron_device.state == OccupancyGroup.State.OCCUPIED
self._attr_is_on = self._lutron_device.state is OccupancyGroup.State.OCCUPIED
@@ -3,7 +3,7 @@
from datetime import timedelta
from typing import Any
from pylutron_caseta import OCCUPANCY_GROUP_OCCUPIED
from pylutron_caseta import OCCUPANCY_GROUP_OCCUPIED, BridgeResponseError
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
@@ -131,7 +131,11 @@ class LutronCasetaBatterySensor(LutronCasetaEntity, BinarySensorEntity):
async def async_update(self) -> None:
"""Fetch the latest battery status from the bridge."""
status = await self._smartbridge.get_battery_status(self.device_id)
try:
status = await self._smartbridge.get_battery_status(self.device_id)
except BridgeResponseError:
self._attr_is_on = None
return
normalized_status = status.strip().casefold() if status else None
if normalized_status == BATTERY_STATUS_LOW:
self._attr_is_on = True

Some files were not shown because too many files have changed in this diff Show More