From 01d662cdec870249db1a1ab65b6cd2f3b0cc7fec Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 12 Mar 2024 12:50:09 -0400 Subject: [PATCH] Update pre-commit settings and fix lint issues (#3034) * update ci config * settings for IDE plugins * fix lint issues --- .pre-commit-config.yaml | 45 +---- pyproject.toml | 210 +++++++++++--------- setup.py | 2 + tests/conftest.py | 4 +- tests/test_develco.py | 3 +- tests/test_ikea.py | 5 +- tests/test_konke.py | 3 +- tests/test_legrand.py | 2 + tests/test_linkind.py | 5 +- tests/test_orvibo.py | 3 +- tests/test_quirks.py | 37 ++-- tests/test_schneider.py | 3 +- tests/test_schneiderelectric.py | 12 +- tests/test_sinope.py | 3 +- tests/test_thirdreality.py | 3 +- tests/test_tuya.py | 5 +- tests/test_tuya_dimmer.py | 3 +- tests/test_tuya_mcu.py | 3 +- tests/test_tuya_rcbo.py | 3 +- tests/test_tuya_valve.py | 3 +- tests/test_xbee.py | 3 +- tests/test_xiaomi.py | 38 ++-- zhaquirks/adeo/color_controller.py | 5 +- zhaquirks/develco/open_close.py | 5 +- zhaquirks/ikea/__init__.py | 3 +- zhaquirks/ikea/opencloseremote.py | 5 +- zhaquirks/ikea/somrigsmartbtn.py | 5 +- zhaquirks/inovelli/VZM36.py | 2 +- zhaquirks/inovelli/__init__.py | 4 +- zhaquirks/konke/__init__.py | 7 +- zhaquirks/legrand/cable_outlet.py | 10 + zhaquirks/philips/__init__.py | 5 +- zhaquirks/philips/rdm001.py | 5 +- zhaquirks/samjin/__init__.py | 5 +- zhaquirks/schneiderelectric/__init__.py | 11 +- zhaquirks/schneiderelectric/dimmers.py | 10 +- zhaquirks/sengled/e1e_g7f.py | 5 +- zhaquirks/siglis/zigfred.py | 7 +- zhaquirks/sinope/light.py | 4 +- zhaquirks/sinope/thermostat.py | 2 +- zhaquirks/smartwings/wm25lz.py | 3 +- zhaquirks/sonoff/snzb01p.py | 5 +- zhaquirks/sonoff/snzb06p.py | 2 + zhaquirks/terncy/__init__.py | 5 +- zhaquirks/tuya/__init__.py | 47 ++--- zhaquirks/tuya/air/__init__.py | 6 +- zhaquirks/tuya/mcu/__init__.py | 26 +-- zhaquirks/tuya/ts0001_fingerbot.py | 14 +- zhaquirks/tuya/ts0210.py | 7 +- zhaquirks/tuya/ts0211.py | 4 +- zhaquirks/tuya/ts0601_garage.py | 3 +- zhaquirks/tuya/ts0601_illuminance.py | 3 +- zhaquirks/tuya/ts0601_motion.py | 8 +- zhaquirks/tuya/ts0601_rcbo.py | 17 +- zhaquirks/tuya/ts0601_sensor.py | 8 +- zhaquirks/tuya/ts0601_siren.py | 14 +- zhaquirks/tuya/ts0601_trv.py | 30 ++- zhaquirks/tuya/ts0601_valve.py | 9 +- zhaquirks/tuya/ts130f.py | 3 +- zhaquirks/waxman/leaksmart.py | 5 +- zhaquirks/xbee/__init__.py | 10 +- zhaquirks/xiaomi/__init__.py | 19 +- zhaquirks/xiaomi/aqara/feeder_acn001.py | 4 +- zhaquirks/xiaomi/aqara/illumination.py | 2 +- zhaquirks/xiaomi/aqara/light_acn.py | 2 + zhaquirks/xiaomi/aqara/motion_ac01.py | 7 +- zhaquirks/xiaomi/aqara/motion_ac02.py | 2 +- zhaquirks/xiaomi/aqara/motion_agl04.py | 2 +- zhaquirks/xiaomi/aqara/opple_remote.py | 2 +- zhaquirks/xiaomi/aqara/plug_eu.py | 2 +- zhaquirks/xiaomi/aqara/smoke.py | 2 +- zhaquirks/xiaomi/aqara/switch_h1_single.py | 3 +- zhaquirks/xiaomi/aqara/thermostat_agl001.py | 28 ++- zhaquirks/xiaomi/aqara/vibration_aq1.py | 2 +- zhaquirks/xiaomi/mija/sensor_switch.py | 11 +- zhaquirks/zbeacon/__init__.py | 1 + zhaquirks/zbeacon/doorsensor.py | 9 +- 77 files changed, 449 insertions(+), 376 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c6988d82d4..1176fdc07c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,49 +1,18 @@ repos: - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: [--py38-plus] - - - repo: https://github.com/PyCQA/autoflake - rev: v2.0.2 - hooks: - - id: autoflake - - - repo: https://github.com/psf/black - rev: 23.3.0 - hooks: - - id: black - args: - - --quiet - - - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - additional_dependencies: - - Flake8-pyproject==1.2.3 - - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.6 hooks: - id: codespell - additional_dependencies: - - tomli + additional_dependencies: [tomli] + args: ["--toml", "pyproject.toml"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.2.0 + rev: v1.9.0 hooks: - id: mypy - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.261 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.2 hooks: - id: ruff - args: - - --fix \ No newline at end of file + args: ["--fix", "--exit-non-zero-on-fix", "--config", "pyproject.toml"] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3e2787d9ae..5dc4d4a895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ authors = [ ] readme = "README.md" license = {text = "Apache License Version 2.0"} -requires-python = ">=3.8" +requires-python = ">=3.12" dependencies = [ "zigpy>=0.63.5", ] @@ -28,29 +28,12 @@ testing = [ [tool.setuptools-git-versioning] enabled = true -[tool.isort] -# https://github.com/timothycrosley/isort -# https://github.com/timothycrosley/isort/wiki/isort-Settings -# splits long import on multiple lines indented by 4 spaces -profile = "black" -# by default isort don't check module indexes -not_skip = ["__init__.py"] -# will group `import x` and `from x import` of the same module. -force_sort_within_sections = true -sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] -default_section = "THIRDPARTY" -known_first_party = ["zhaquirks", "tests"] -forced_separate = "tests" -combine_as_imports = true -use_parentheses = true -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -line_length = 88 -indent = " " +[tool.pylint] +max-line-length = 120 +disable = ["C0103", "W0212"] [tool.mypy] -python_version = "3.9" +python_version = "3.12" check_untyped_defs = true disallow_incomplete_defs = true disallow_untyped_calls = true @@ -94,108 +77,145 @@ asyncio_mode = "auto" testpaths = "tests" norecursedirs = ".git testing_config" -[tool.flake8] -exclude = [".venv", ".git", ".tox", "docs", "venv", "bin", "lib", "deps", "build"] -# To work with Black -max-line-length = 88 -ignore = [ - "E501", # E501: line too long - "W503", # W503: Line break occurred before a binary operator - "E203", # E203: Whitespace before ':' - "D202", # D202 No blank lines allowed after function docstring -] - -[tool.autoflake] -in-place = true -recursive = false -expand-star-imports = false -exclude = [".venv", ".git", ".tox", "docs", "venv", "bin", "lib", "deps", "build"] - -[tool.pydocstyle] -ignore = [ - "D202", - "D203", - "D213", -] - -[tool.pyupgrade] -py37plus = true - [tool.codespell] -exclude = "pyproject.toml" -ignore-words-list = "hass,dout" -skip = "./.*,test/*" +skip = "Contributors.md" +ignore-words-list = "hass, dout, potentiels" quiet-level = 2 [tool.ruff] -target-version = "py38" +target-version = "py312" +[tool.ruff.lint] select = [ + "B002", # Python does not support the unary prefix increment "B007", # Loop control variable {name} not used within loop body "B014", # Exception handler with duplicate exception - "C", # complexity - "D", # docstrings - "E", # pycodestyle - "F", # pyflakes/autoflake + "B023", # Function definition does not bind loop variable {name} + "B026", # Star-arg unpacking after a keyword argument is strongly discouraged + "B904", # Use raise from to specify exception cause + "C", # complexity + "COM818", # Trailing comma on bare tuple prohibited + "D", # docstrings + "DTZ003", # Use datetime.now(tz=) instead of datetime.utcnow() + "DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts) + "E", # pycodestyle + "F", # pyflakes/autoflake + "G", # flake8-logging-format + "I", # isort "ICN001", # import concentions; {name} should be imported as {asname} - "PGH004", # Use specific rule codes when using noqa + "N804", # First argument of a class method should be named cls + "N805", # First argument of a method should be named self + "N815", # Variable {name} in class scope should not be mixedCase + "PERF101", # Do not cast an iterable to list before iterating over it + "PERF102", # When using only the {subset} of a dict use the {subset}() method + "PERF203", # try-except within a loop incurs performance overhead + "PGH004", # Use specific rule codes when using noqa "PLC0414", # Useless import alias. Import alias does not rename original package. + "PLC", # pylint + "PLE", # pylint + "PLR", # pylint + "PLW", # pylint + "Q000", # Double quotes found but single quotes preferred + "RUF006", # Store a reference to the return value of asyncio.create_task + "S102", # Use of exec detected + "S103", # bad-file-permissions + "S108", # hardcoded-temp-file + "S306", # suspicious-mktemp-usage + "S307", # suspicious-eval-usage + "S313", # suspicious-xmlc-element-tree-usage + "S314", # suspicious-xml-element-tree-usage + "S315", # suspicious-xml-expat-reader-usage + "S316", # suspicious-xml-expat-builder-usage + "S317", # suspicious-xml-sax-usage + "S318", # suspicious-xml-mini-dom-usage + "S319", # suspicious-xml-pull-dom-usage + "S320", # suspicious-xmle-tree-usage + "S601", # paramiko-call + "S602", # subprocess-popen-with-shell-equals-true + "S604", # call-with-shell-equals-true + "S608", # hardcoded-sql-expression + "S609", # unix-command-wildcard-injection "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass + "SIM114", # Combine if branches using logical or operator "SIM117", # Merge with-statements that use the same scope "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys() "SIM201", # Use {left} != {right} instead of not {left} == {right} + "SIM208", # Use {expr} instead of not (not {expr}) "SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a} "SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'. "SIM401", # Use get from dict with default instead of an if block - "T20", # flake8-print + "T100", # Trace found: {name} used + "T20", # flake8-print + "TID251", # Banned imports "TRY004", # Prefer TypeError exception for invalid type - "RUF006", # Store a reference to the return value of asyncio.create_task - "UP", # pyupgrade - "W", # pycodestyle + "TRY302", # Remove exception handler; error is immediately re-raised + "UP", # pyupgrade + "W", # pycodestyle ] ignore = [ - "D100", # Missing docstring in public module - "D101", # Missing docstring in public class - "D102", # Missing docstring in public method - "D103", # Missing docstring in public function - "D104", # Missing docstring in public package - "D105", # Missing docstring in magic method - "D106", # Missing docstring in public nested class - "D107", # Missing docstring in `__init__` - "D202", # No blank lines allowed after function docstring - "D203", # 1 blank line required before class docstring - "D205", # 1 blank line required between summary line and description - "D213", # Multi-line docstring summary should start at the second line - "D400", # First line should end with a period - "D401", # First line of docstring should be in imperative mood: - "D406", # Section name should end with a newline - "D407", # Section name underlining - "D415", # First line should end with a period, question mark, or exclamation point - "E501", # line too long - # the rules below this line should be corrected - "PGH004", # Use specific rule codes when using `noqa` -] - -extend-exclude = [ - "tests" + "D202", # No blank lines allowed after function docstring + "D203", # 1 blank line required before class docstring + "D213", # Multi-line docstring summary should start at the second line + "D406", # Section name should end with a newline + "D407", # Section name underlining + "E501", # line too long + "E731", # do not assign a lambda expression, use a def + + # Ignore ignored, as the rule is now back in preview/nursery, which cannot + # be ignored anymore without warnings. + # https://github.com/astral-sh/ruff/issues/7491 + # "PLC1901", # Lots of false positives + + # False positives https://github.com/astral-sh/ruff/issues/5386 + "PLC0208", # Use a sequence type instead of a `set` when iterating over values + "PLR0911", # Too many return statements ({returns} > {max_returns}) + "PLR0912", # Too many branches ({branches} > {max_branches}) + "PLR0913", # Too many arguments to function call ({c_args} > {max_args}) + "PLR0915", # Too many statements ({statements} > {max_statements}) + "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable + "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target + "UP006", # keep type annotation style as is + "UP007", # keep type annotation style as is + # Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923 + "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` + + # May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", + "E111", + "E114", + "E117", + "D206", + "D300", + "Q000", + "Q001", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", + + # Disabled because ruff does not understand type of __all__ generated by a function + "PLE0605", ] -[tool.ruff.flake8-pytest-style] +[tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false -[tool.ruff.pyupgrade] -keep-runtime-typing = true - -[tool.ruff.isort] -# will group `import x` and `from x import` of the same module. +[tool.ruff.lint.isort] force-sort-within-sections = true known-first-party = [ "zhaquirks", "tests", ] -forced-separate = ["tests"] combine-as-imports = true +split-on-trailing-comma = false + +[tool.ruff.lint.per-file-ignores] + +# Allow for main entry & scripts to write to stdout +"script/*" = ["T20"] -[tool.ruff.mccabe] -max-complexity = 25 \ No newline at end of file +[tool.ruff.lint.mccabe] +max-complexity = 27 \ No newline at end of file diff --git a/setup.py b/setup.py index 1abbd068c1..413317b9a5 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +"""setup.""" + import setuptools if __name__ == "__main__": diff --git a/tests/conftest.py b/tests/conftest.py index 713c2cb659..e64beb2e8e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,8 +7,8 @@ import zigpy.device import zigpy.quirks import zigpy.types +from zigpy.zcl import foundation from zigpy.zcl.clusters.general import Basic -import zigpy.zcl.foundation as foundation from zhaquirks.const import ( DEVICE_TYPE, @@ -72,7 +72,7 @@ async def start_network(self, *args, **kwargs): """Mock start_network.""" async def permit_with_link_key(self, *args, **kwargs): - """Mock permit_with_link_key""" + """Mock permit_with_link_key.""" async def write_network_info(self, *args, **kwargs): """Mock write_network_info.""" diff --git a/tests/test_develco.py b/tests/test_develco.py index 97f5dc54b8..82049c4cca 100644 --- a/tests/test_develco.py +++ b/tests/test_develco.py @@ -2,11 +2,10 @@ import pytest from zigpy.zcl.clusters.general import DeviceTemperature +from tests.common import ClusterListener import zhaquirks.develco.motion import zhaquirks.develco.power_plug -from tests.common import ClusterListener - zhaquirks.setup() diff --git a/tests/test_ikea.py b/tests/test_ikea.py index 859654a561..fa4470090e 100644 --- a/tests/test_ikea.py +++ b/tests/test_ikea.py @@ -7,11 +7,10 @@ from zigpy.zcl.clusters.general import Basic, PowerConfiguration from zigpy.zcl.clusters.measurement import PM25 +from tests.common import ClusterListener import zhaquirks import zhaquirks.ikea.starkvind -from tests.common import ClusterListener - zhaquirks.setup() @@ -87,7 +86,7 @@ def test_ikea_starkvind_v2(assert_signature_matches_quirk): async def test_pm25_cluster_read(zigpy_device_from_quirk): - """Test reading from PM25 cluster""" + """Test reading from PM25 cluster.""" starkvind_device = zigpy_device_from_quirk(zhaquirks.ikea.starkvind.IkeaSTARKVIND) assert starkvind_device.model == "STARKVIND Air purifier" diff --git a/tests/test_konke.py b/tests/test_konke.py index 8ef1ddff58..00d90f09f9 100644 --- a/tests/test_konke.py +++ b/tests/test_konke.py @@ -5,6 +5,7 @@ import pytest +from tests.common import ZCL_IAS_MOTION_COMMAND, ClusterListener import zhaquirks from zhaquirks.const import ( COMMAND_DOUBLE, @@ -18,8 +19,6 @@ ) import zhaquirks.konke.motion -from tests.common import ZCL_IAS_MOTION_COMMAND, ClusterListener - zhaquirks.setup() diff --git a/tests/test_legrand.py b/tests/test_legrand.py index ab98c25f91..312e9e66fe 100644 --- a/tests/test_legrand.py +++ b/tests/test_legrand.py @@ -1,4 +1,5 @@ """Tests for Legrand.""" + import pytest import zhaquirks @@ -29,6 +30,7 @@ async def test_legrand_battery(zigpy_device_from_quirk, voltage, bpr): def test_light_switch_with_neutral_signature(assert_signature_matches_quirk): + """Test signature.""" signature = { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=1, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4129, maximum_buffer_size=89, maximum_incoming_transfer_size=63, server_mask=10752, maximum_outgoing_transfer_size=63, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { diff --git a/tests/test_linkind.py b/tests/test_linkind.py index 1510905d55..3f77cb48ca 100644 --- a/tests/test_linkind.py +++ b/tests/test_linkind.py @@ -1,9 +1,10 @@ +"""Test linkind.""" + import pytest from zigpy.zcl.clusters.security import IasZone -import zhaquirks - from tests.common import ClusterListener +import zhaquirks zhaquirks.setup() diff --git a/tests/test_orvibo.py b/tests/test_orvibo.py index b18d5d7eeb..ad363502a9 100644 --- a/tests/test_orvibo.py +++ b/tests/test_orvibo.py @@ -5,12 +5,11 @@ import pytest +from tests.common import ZCL_IAS_MOTION_COMMAND, ClusterListener import zhaquirks from zhaquirks.const import OFF, ON, ZONE_STATUS_CHANGE_COMMAND import zhaquirks.orvibo.motion -from tests.common import ZCL_IAS_MOTION_COMMAND, ClusterListener - zhaquirks.setup() diff --git a/tests/test_quirks.py b/tests/test_quirks.py index 61441d5bd0..f66e47089c 100644 --- a/tests/test_quirks.py +++ b/tests/test_quirks.py @@ -1,4 +1,5 @@ """General quirk tests.""" + from __future__ import annotations import collections @@ -8,19 +9,19 @@ from unittest import mock import pytest +from zigpy import zcl import zigpy.device import zigpy.endpoint import zigpy.profiles import zigpy.quirks as zq from zigpy.quirks import CustomDevice import zigpy.types -import zigpy.zcl as zcl import zigpy.zdo.types import zhaquirks +from zhaquirks import const import zhaquirks.bosch.motion import zhaquirks.centralite.cl_3310S -import zhaquirks.const as const from zhaquirks.const import ( ARGS, COMMAND, @@ -278,10 +279,8 @@ class QuirkDevice(zhaquirks.QuickInitDevice): assert ep.status == zigpy.endpoint.Status.ZDO_INIT assert ep.profile_id == ep_data[PROFILE_ID] assert ep.device_type == ep_data[DEVICE_TYPE] - assert [cluster_id for cluster_id in ep.in_clusters] == ep_data[INPUT_CLUSTERS] - assert [cluster_id for cluster_id in ep.out_clusters] == ep_data[ - OUTPUT_CLUSTERS - ] + assert list(ep.in_clusters) == ep_data[INPUT_CLUSTERS] + assert list(ep.out_clusters) == ep_data[OUTPUT_CLUSTERS] @pytest.mark.parametrize( @@ -309,7 +308,7 @@ def test_signature(quirk: CustomDevice) -> None: """Make sure signature look sane for all custom devices.""" def _check_range(cluster: zcl.Cluster) -> bool: - for range in zcl.Cluster._registry_range.keys(): + for range in zcl.Cluster._registry_range: if range[0] <= cluster <= range[1]: return True return False @@ -429,7 +428,7 @@ def test_quirk_importable(quirk: CustomDevice) -> None: path = f"{quirk.__module__}.{quirk.__name__}" assert all( - [m and m.isidentifier() for m in path.split(".")] + m and m.isidentifier() for m in path.split(".") ), f"{path} is not importable" @@ -592,7 +591,7 @@ def test_migrated_lighting_automation_triggers(quirk: CustomDevice) -> None: if not hasattr(quirk, "device_automation_triggers"): return - for trigger, event in quirk.device_automation_triggers.items(): + for event in quirk.device_automation_triggers.values(): if COMMAND not in event: continue @@ -696,7 +695,7 @@ def test_quirk_device_automation_triggers_unique(quirk): triggers_text = "\n".join( [f" * {event} <- {trigger}" for trigger, event in triggers_and_events] ) - fail_func(f"Triggers are not unique for {quirk}:\n{triggers_text}") # noqa + fail_func(f"Triggers are not unique for {quirk}:\n{triggers_text}") @pytest.mark.parametrize( @@ -717,7 +716,7 @@ def test_attributes_updated_not_replaced(quirk: CustomDevice) -> None: base_cluster_attrs_name = {} base_cluster_attrs_id = {} - for name, cluster in zcl.clusters.CLUSTERS_BY_NAME.items(): + for cluster in zcl.clusters.CLUSTERS_BY_NAME.values(): assert cluster.ep_attribute not in base_cluster_attrs_name base_cluster_attrs_name[cluster.ep_attribute] = set( cluster.attributes_by_name.keys() @@ -726,13 +725,15 @@ def test_attributes_updated_not_replaced(quirk: CustomDevice) -> None: cluster.attributes_by_name.keys() ) - for ep_id, ep_data in quirk.replacement[ENDPOINTS].items(): + for ep_data in quirk.replacement[ENDPOINTS].values(): for cluster in ep_data.get(INPUT_CLUSTERS, []) + ep_data.get( OUTPUT_CLUSTERS, [] ): - if isinstance(cluster, int) or not issubclass(cluster, zcl.Cluster): - continue - elif cluster in ALL_ZIGPY_CLUSTERS: + if ( + isinstance(cluster, int) + or not issubclass(cluster, zcl.Cluster) + or cluster in ALL_ZIGPY_CLUSTERS + ): continue assert issubclass(cluster, zigpy.quirks.CustomCluster) @@ -771,7 +772,7 @@ def test_attributes_updated_not_replaced(quirk: CustomDevice) -> None: zhaquirks.xiaomi.aqara.vibration_aq1.VibrationAQ1.MultistateInputCluster, ): pytest.fail( - f"Cluster {cluster} with endpoint ID 0x{cluster.cluster_id:04X}" # noqa + f"Cluster {cluster} with endpoint ID 0x{cluster.cluster_id:04X}" f" does not contain all named attributes: {missing_attrs}" ) @@ -821,11 +822,11 @@ def check_for_duplicate_cluster_ids(clusters) -> None: if cluster_id in used_cluster_ids: pytest.fail( - f"Cluster ID 0x{cluster_id:04X} is used more than once in the" # noqa + f"Cluster ID 0x{cluster_id:04X} is used more than once in the" f" replacement for endpoint {ep_id} in {quirk}" ) used_cluster_ids.add(cluster_id) - for ep_id, ep_data in quirk.replacement[ENDPOINTS].items(): + for ep_id, ep_data in quirk.replacement[ENDPOINTS].items(): # noqa: B007 check_for_duplicate_cluster_ids(ep_data.get(INPUT_CLUSTERS, [])) check_for_duplicate_cluster_ids(ep_data.get(OUTPUT_CLUSTERS, [])) diff --git a/tests/test_schneider.py b/tests/test_schneider.py index 186c43f311..10b1d7a09e 100644 --- a/tests/test_schneider.py +++ b/tests/test_schneider.py @@ -3,9 +3,8 @@ import pytest from zigpy.zcl.clusters.smartenergy import Metering -import zhaquirks.schneider.outlet - from tests.common import ClusterListener +import zhaquirks.schneider.outlet zhaquirks.setup() diff --git a/tests/test_schneiderelectric.py b/tests/test_schneiderelectric.py index e1b7743209..6695114ea8 100644 --- a/tests/test_schneiderelectric.py +++ b/tests/test_schneiderelectric.py @@ -1,18 +1,19 @@ """Tests for Schneider Electric devices.""" + from unittest import mock from zigpy.zcl import foundation from zigpy.zcl.clusters.closures import WindowCovering +from tests.common import ClusterListener import zhaquirks.schneiderelectric.dimmers import zhaquirks.schneiderelectric.shutters -from tests.common import ClusterListener - zhaquirks.setup() def test_1gang_shutter_1_signature(assert_signature_matches_quirk): + """Test signature.""" signature = { "node_descriptor": ( "NodeDescriptor(logical_type=, " @@ -113,8 +114,10 @@ async def test_1gang_shutter_1_unpatched_cmd(zigpy_device_from_quirk): async def test_1gang_shutter_1_lift_percentage_updates(zigpy_device_from_quirk): - """Asserts that updates to the ``current_position_lift_percentage`` attribute - (e.g., by the device) invert the reported percentage value.""" + """Asserts that updates to the ``current_position_lift_percentage`` attribute. + + (e.g., by the device) invert the reported percentage value. + """ device = zigpy_device_from_quirk( zhaquirks.schneiderelectric.shutters.OneGangShutter1 @@ -136,6 +139,7 @@ async def test_1gang_shutter_1_lift_percentage_updates(zigpy_device_from_quirk): def test_nh_rotary_dimmer_1_signature(assert_signature_matches_quirk): + """Test signature.""" signature = { "node_descriptor": ( "NodeDescriptor(logical_type=, complex_descriptor_available=0, " diff --git a/tests/test_sinope.py b/tests/test_sinope.py index cfaa7d33e9..1cba91274f 100644 --- a/tests/test_sinope.py +++ b/tests/test_sinope.py @@ -4,9 +4,8 @@ from zigpy.zcl.clusters.general import DeviceTemperature from zigpy.zcl.clusters.measurement import FlowMeasurement -import zhaquirks.sinope.switch - from tests.common import ClusterListener +import zhaquirks.sinope.switch zhaquirks.setup() diff --git a/tests/test_thirdreality.py b/tests/test_thirdreality.py index 0d2af260d1..8c5bed7428 100644 --- a/tests/test_thirdreality.py +++ b/tests/test_thirdreality.py @@ -4,11 +4,10 @@ import pytest from zigpy.zcl.clusters.security import IasZone +from tests.common import ClusterListener import zhaquirks import zhaquirks.thirdreality.night_light -from tests.common import ClusterListener - zhaquirks.setup() diff --git a/tests/test_tuya.py b/tests/test_tuya.py index ec8b73af47..fe0c5af8ef 100644 --- a/tests/test_tuya.py +++ b/tests/test_tuya.py @@ -11,6 +11,7 @@ from zigpy.zcl import foundation from zigpy.zcl.clusters.general import PowerConfiguration +from tests.common import ClusterListener, MockDatetime, wait_for_zigpy_tasks import zhaquirks from zhaquirks.const import ( DEVICE_TYPE, @@ -25,10 +26,10 @@ ) from zhaquirks.tuya import Data, TuyaManufClusterAttributes, TuyaNewManufCluster import zhaquirks.tuya.sm0202_motion -import zhaquirks.tuya.ts011f_plug import zhaquirks.tuya.ts0041 import zhaquirks.tuya.ts0042 import zhaquirks.tuya.ts0043 +import zhaquirks.tuya.ts011f_plug import zhaquirks.tuya.ts0501_fan_switch import zhaquirks.tuya.ts0601_electric_heating import zhaquirks.tuya.ts0601_motion @@ -36,8 +37,6 @@ import zhaquirks.tuya.ts0601_trv import zhaquirks.tuya.ts0601_valve -from tests.common import ClusterListener, MockDatetime, wait_for_zigpy_tasks - zhaquirks.setup() ZCL_TUYA_SET_TIME_REQUEST = b"\tp\x24\x00\00" diff --git a/tests/test_tuya_dimmer.py b/tests/test_tuya_dimmer.py index 9ecff6e218..5e241cfbcc 100644 --- a/tests/test_tuya_dimmer.py +++ b/tests/test_tuya_dimmer.py @@ -5,9 +5,8 @@ import pytest from zigpy.zcl import foundation -import zhaquirks - from tests.common import ClusterListener, wait_for_zigpy_tasks +import zhaquirks zhaquirks.setup() diff --git a/tests/test_tuya_mcu.py b/tests/test_tuya_mcu.py index cd2ee4b1d0..a7572a990e 100644 --- a/tests/test_tuya_mcu.py +++ b/tests/test_tuya_mcu.py @@ -6,6 +6,7 @@ import pytest from zigpy.zcl import foundation +from tests.common import ClusterListener, MockDatetime import zhaquirks from zhaquirks.tuya import TUYA_MCU_VERSION_RSP, TUYA_SET_TIME, TuyaDPType from zhaquirks.tuya.mcu import ( @@ -16,8 +17,6 @@ TuyaMCUCluster, ) -from tests.common import ClusterListener, MockDatetime - zhaquirks.setup() ZCL_TUYA_VERSION_RSP = b"\x09\x06\x11\x01\x6D\x82" diff --git a/tests/test_tuya_rcbo.py b/tests/test_tuya_rcbo.py index 88ac1b4003..369947bc82 100644 --- a/tests/test_tuya_rcbo.py +++ b/tests/test_tuya_rcbo.py @@ -5,9 +5,8 @@ import pytest from zigpy.zcl import foundation -import zhaquirks - from tests.common import ClusterListener, wait_for_zigpy_tasks +import zhaquirks zhaquirks.setup() diff --git a/tests/test_tuya_valve.py b/tests/test_tuya_valve.py index ae5ea3f8cc..36262bc65a 100644 --- a/tests/test_tuya_valve.py +++ b/tests/test_tuya_valve.py @@ -5,9 +5,8 @@ import pytest from zigpy.zcl import foundation -import zhaquirks - from tests.common import ClusterListener, wait_for_zigpy_tasks +import zhaquirks zhaquirks.setup() diff --git a/tests/test_xbee.py b/tests/test_xbee.py index 91b986e60b..9128d5f087 100644 --- a/tests/test_xbee.py +++ b/tests/test_xbee.py @@ -6,6 +6,7 @@ from zigpy.zcl import foundation from zigpy.zcl.clusters.general import AnalogOutput, Basic, LevelControl, OnOff +from tests.common import ClusterListener import zhaquirks from zhaquirks.xbee import ( XBEE_AT_ENDPOINT, @@ -19,8 +20,6 @@ from zhaquirks.xbee.xbee3_io import XBee3Sensor from zhaquirks.xbee.xbee_io import XBeeSensor -from tests.common import ClusterListener - zhaquirks.setup() diff --git a/tests/test_xiaomi.py b/tests/test_xiaomi.py index 08387b0ad2..3836c79c47 100644 --- a/tests/test_xiaomi.py +++ b/tests/test_xiaomi.py @@ -1,4 +1,5 @@ """Tests for xiaomi.""" + import asyncio import logging import math @@ -30,6 +31,7 @@ from zigpy.zcl.clusters.security import IasZone from zigpy.zcl.clusters.smartenergy import Metering +from tests.common import ZCL_OCC_ATTR_RPT_OCC, ClusterListener import zhaquirks from zhaquirks.const import ( BUTTON_1, @@ -88,13 +90,11 @@ import zhaquirks.xiaomi.aqara.weather import zhaquirks.xiaomi.mija.motion -from tests.common import ZCL_OCC_ATTR_RPT_OCC, ClusterListener - zhaquirks.setup() def create_aqara_attr_report(attributes): - """Creates a special Aqara attriubte report with t.Single as a type for all values.""" + """Create a special Aqara attribute report with t.Single as a type for all values.""" serialized_data = b"" for key, value in attributes.items(): tv = foundation.TypeValue(0x39, t.Single(value)) # mostly used @@ -218,7 +218,7 @@ def test_xiaomi_quick_init_wrong_ep(raw_device, ep_id, cluster, message): ), # wrong command ( 0, - b"\x18\x00\xFF\x05\x00B\x11lumi.sensor_sm0ke\x01\x00 \x01", + b"\x18\x00\xff\x05\x00B\x11lumi.sensor_sm0ke\x01\x00 \x01", ), # unknown command (0, b"\x18\x00\n\x04\x00B\x11lumi.sensor_sm0ke\x01\x00 \x01"), # wrong attr id (0, b"\x18\x00\n\x05\x00B\x11lumi.sensor_sm0ke\x01\x00 "), # data under run @@ -923,8 +923,11 @@ def mock_read(attributes, manufacturer=None): ) with ( - patch_opple_read - ), patch_thermostat_read, patch_opple_write, patch_thermostat_write: + patch_opple_read, + patch_thermostat_read, + patch_opple_write, + patch_thermostat_write, + ): # test reads: # read system_mode attribute from thermostat cluster @@ -936,9 +939,12 @@ def mock_read(attributes, manufacturer=None): assert opple_cluster._read_attributes.mock_calls[0][1][0] == [ 0x0271 ] # Opple system_mode attribute - assert thermostat_listener.attribute_updates[0] == ( - Thermostat.AttributeDefs.system_mode.id, - Thermostat.SystemMode.Heat, + assert ( + thermostat_listener.attribute_updates[0] + == ( + Thermostat.AttributeDefs.system_mode.id, + Thermostat.SystemMode.Heat, + ) ) # check that attributes are correctly mapped and updated on ZCL thermostat cluster thermostat_cluster._read_attributes.reset_mock() @@ -1035,7 +1041,7 @@ async def test_xiaomi_e1_thermostat_attribute_update(zigpy_device_from_quirk, qu async def test_xiaomi_e1_thermostat_schedule_settings_string_representation( schedule_settings, ): - """Test creation of ScheduleSettings from str and converting back to same str""" + """Test creation of ScheduleSettings from str and converting back to same str.""" s = ScheduleSettings(schedule_settings) assert str(s) == schedule_settings @@ -1068,7 +1074,7 @@ async def test_xiaomi_e1_thermostat_schedule_settings_string_representation( async def test_xiaomi_e1_thermostat_schedule_settings_data_validation( schedule_settings, ): - """Test data validation of ScheduleSettings class""" + """Test data validation of ScheduleSettings class.""" with pytest.raises(Exception): ScheduleSettings(schedule_settings) @@ -1082,7 +1088,7 @@ async def test_xiaomi_e1_thermostat_schedule_settings_data_validation( ], ) async def test_xiaomi_e1_thermostat_schedule_event_data_validation(schedule_event): - """Test data validation of ScheduleEvent class""" + """Test data validation of ScheduleEvent class.""" with pytest.raises(Exception): ScheduleEvent(schedule_event) @@ -1352,10 +1358,10 @@ async def test_xiaomi_motion_sensor_misc( @pytest.mark.parametrize("quirk", (zhaquirks.xiaomi.aqara.plug.Plug,)) async def test_xiaomi_power_cluster_not_used(zigpy_device_from_quirk, caplog, quirk): - """Test that a log is printed which warns when a device reports battery mV readout, - even though XiaomiPowerConfigurationCluster is not used. + """Test log is printed which warns when a device reports battery mV readout. - This explicitly uses the Plug quirk which will always report this message, as this shouldn't have a battery readout. + ... even though XiaomiPowerConfigurationCluster is not used. This explicitly uses the Plug quirk + which will always report this message, as this shouldn't have a battery readout. Other battery-powered devices might implement the XiaomiPowerConfigurationCluster in the future, so they would no longer report this message. """ @@ -1666,6 +1672,7 @@ async def test_aqara_t2_relay(zigpy_device_from_quirk, endpoint): def test_aqara_acn003_signature_match(assert_signature_matches_quirk): + """Test signature.""" signature = { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4447, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { @@ -1696,6 +1703,7 @@ def test_aqara_acn003_signature_match(assert_signature_matches_quirk): def test_aqara_acn014_signature_match(assert_signature_matches_quirk): + """Test signature.""" signature = { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4447, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { diff --git a/zhaquirks/adeo/color_controller.py b/zhaquirks/adeo/color_controller.py index 5d32024ea4..782359b2bf 100644 --- a/zhaquirks/adeo/color_controller.py +++ b/zhaquirks/adeo/color_controller.py @@ -1,5 +1,6 @@ """Device handler for ADEO Lexman LXEK-5 (HR-C99C-Z-C045) & ZBEK-26 (HR-C99C-Z-C045-B) color controllers.""" -from typing import Any, List, Optional, Union + +from typing import Any, Optional, Union from zigpy.profiles import zha from zigpy.quirks import CustomDevice @@ -79,7 +80,7 @@ class AdeoManufacturerCluster(EventableCluster): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: List[Any], + args: list[Any], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] diff --git a/zhaquirks/develco/open_close.py b/zhaquirks/develco/open_close.py index ababf86768..22dbc9bd31 100644 --- a/zhaquirks/develco/open_close.py +++ b/zhaquirks/develco/open_close.py @@ -1,4 +1,5 @@ """Door/Windows sensors.""" + from zigpy.profiles import zha from zigpy.quirks import CustomCluster, CustomDevice import zigpy.types as t @@ -55,7 +56,7 @@ class DevelcoIASZone(CustomCluster, IasZone): class WISZB120(CustomDevice): - """Custom device representing door/windows sensors, with built-in temperature measuring""" + """Custom device representing door/windows sensors, with built-in temperature measuring.""" signature = { # Coroutine: + """Override the command method to invert the percent lift value.""" command = self.server_commands[command_id] # Override default command to invert percent lift value. @@ -152,6 +159,8 @@ class SESpecific(CustomCluster): cluster_id = 0xFF17 class AttributeDefs(BaseAttributeDefs): + """Attribute definitions.""" + led_indicator_signals: Final = ZCLAttributeDef( id=0x0000, type=SELedIndicatorSignals, diff --git a/zhaquirks/schneiderelectric/dimmers.py b/zhaquirks/schneiderelectric/dimmers.py index 32e2303726..2197d9f76e 100644 --- a/zhaquirks/schneiderelectric/dimmers.py +++ b/zhaquirks/schneiderelectric/dimmers.py @@ -1,3 +1,5 @@ +"""Schneider Electric dimmers and switches quirks.""" + from typing import Final from zigpy.profiles import zgp, zha @@ -30,7 +32,7 @@ class ControlMode(t.enum8): - """Dimming mode for PUCK/DIMMER/* and NHROTARY/DIMMER/1""" + """Dimming mode for PUCK/DIMMER/* and NHROTARY/DIMMER/1.""" Auto = 0 RC = 1 @@ -39,9 +41,13 @@ class ControlMode(t.enum8): class SEBallast(CustomCluster, Ballast): + """Schneider Electric Ballast cluster.""" + manufacturer_id_override = SE_MANUF_ID class AttributeDefs(Ballast.AttributeDefs): + """Attribute definitions.""" + control_mode: Final = ZCLAttributeDef( id=0xE000, type=ControlMode, is_manufacturer_specific=True ) @@ -54,7 +60,7 @@ class AttributeDefs(Ballast.AttributeDefs): class NHRotaryDimmer1(CustomDevice): - """NHROTARY/DIMMER/1 by Schneider Electric""" + """NHROTARY/DIMMER/1 by Schneider Electric.""" signature = { MODELS_INFO: [ diff --git a/zhaquirks/sengled/e1e_g7f.py b/zhaquirks/sengled/e1e_g7f.py index 0c80a76df0..e152119bfa 100644 --- a/zhaquirks/sengled/e1e_g7f.py +++ b/zhaquirks/sengled/e1e_g7f.py @@ -1,5 +1,6 @@ """Sengled E1E-G7F device.""" -from typing import Any, List, Optional, Union + +from typing import Any, Optional, Union from zigpy.profiles import zha from zigpy.quirks import CustomCluster, CustomDevice @@ -85,7 +86,7 @@ class SengledE1EG7FManufacturerSpecificCluster(CustomCluster): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: List[Any], + args: list[Any], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] diff --git a/zhaquirks/siglis/zigfred.py b/zhaquirks/siglis/zigfred.py index a2756ef72b..3c4fb9132c 100644 --- a/zhaquirks/siglis/zigfred.py +++ b/zhaquirks/siglis/zigfred.py @@ -1,6 +1,7 @@ """zigfred device handler.""" + import logging -from typing import Any, List, Optional, Union +from typing import Any, Optional, Union from zigpy.profiles import zgp, zha from zigpy.quirks import CustomCluster, CustomDevice @@ -93,7 +94,7 @@ def _process_button_event(self, value: t.uint32_t): PRESS_TYPE: press_type, } - _LOGGER.info(f"Got button press on zigfred cluster: {action}") + _LOGGER.info("Got button press on zigfred cluster: %s", action) if button and press_type: self.listener_event(ZHA_SEND_EVENT, action, event_args) @@ -101,7 +102,7 @@ def _process_button_event(self, value: t.uint32_t): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: List[Any], + args: list[Any], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] diff --git a/zhaquirks/sinope/light.py b/zhaquirks/sinope/light.py index d4d672c55b..72496350c4 100644 --- a/zhaquirks/sinope/light.py +++ b/zhaquirks/sinope/light.py @@ -49,13 +49,13 @@ class KeypadLock(t.enum8): Partial_lock = 0x02 class PhaseControl(t.enum8): - """Phase control value, reverse / forward""" + """Phase control value, reverse / forward.""" Forward = 0x00 Reverse = 0x01 class DoubleFull(t.enum8): - """Double click up set full intensity""" + """Double click up set full intensity.""" Off = 0x00 On = 0x01 diff --git a/zhaquirks/sinope/thermostat.py b/zhaquirks/sinope/thermostat.py index 946eed6f73..be044974a0 100644 --- a/zhaquirks/sinope/thermostat.py +++ b/zhaquirks/sinope/thermostat.py @@ -103,7 +103,7 @@ class SystemMode(t.enum8): Heat = 0x04 class PumpDuration(t.enum8): - """Pump protection duration period values""" + """Pump protection duration period values.""" T5 = 0x05 T10 = 0x0A diff --git a/zhaquirks/smartwings/wm25lz.py b/zhaquirks/smartwings/wm25lz.py index b761a19f54..eaaf71ca42 100644 --- a/zhaquirks/smartwings/wm25lz.py +++ b/zhaquirks/smartwings/wm25lz.py @@ -1,7 +1,8 @@ """Device handler for Smartwings blinds.""" from __future__ import annotations -from typing import Any, Coroutine +from collections.abc import Coroutine +from typing import Any from zigpy.profiles import zha from zigpy.quirks import CustomCluster, CustomDevice diff --git a/zhaquirks/sonoff/snzb01p.py b/zhaquirks/sonoff/snzb01p.py index 5248f09d77..52669f4e2b 100644 --- a/zhaquirks/sonoff/snzb01p.py +++ b/zhaquirks/sonoff/snzb01p.py @@ -1,4 +1,5 @@ -"""Sonoff Smart Button SNZB-01P""" +"""Sonoff Smart Button SNZB-01P.""" + from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import ( @@ -33,7 +34,7 @@ class SonoffSmartButtonSNZB01P(CustomDevice): - """Sonoff smart button remote - model SNZB-01P""" + """Sonoff smart button remote - model SNZB-01P.""" signature = { # None: """Handle cluster request.""" - """Tuya Specific Cluster Commands""" + # Tuya Specific Cluster Commands if hdr.command_id in (TUYA_GET_DATA, TUYA_SET_DATA_RESPONSE): tuya_payload = args[0] _LOGGER.debug( @@ -1200,16 +1202,11 @@ def handle_cluster_request( tuya_payload.data, ) - if tuya_payload.command_id == TUYA_DP_TYPE_VALUE + TUYA_DP_ID_PERCENT_STATE: - self.endpoint.device.cover_bus.listener_event( - COVER_EVENT, - ATTR_COVER_POSITION, - tuya_payload.data[4], - ) - elif ( - tuya_payload.command_id - == TUYA_DP_TYPE_VALUE + TUYA_DP_ID_PERCENT_CONTROL - ): + ids = [ + TUYA_DP_TYPE_VALUE + TUYA_DP_ID_PERCENT_STATE, + TUYA_DP_TYPE_VALUE + TUYA_DP_ID_PERCENT_CONTROL, + ] + if tuya_payload.command_id in ids: self.endpoint.device.cover_bus.listener_event( COVER_EVENT, ATTR_COVER_POSITION, @@ -1248,7 +1245,7 @@ def handle_cluster_request( class TuyaWindowCoverControl(LocalDataCluster, WindowCovering): """Manufacturer Specific Cluster of Device cover.""" - """Add additional attributes for direction""" + # Add additional attributes for direction attributes = WindowCovering.attributes.copy() attributes.update({ATTR_COVER_DIRECTION: ("motor_direction", t.Bool)}) attributes.update({ATTR_COVER_INVERTED: ("cover_inverted", t.Bool)}) @@ -1384,7 +1381,7 @@ class TuyaManufacturerLevelControl(TuyaManufCluster): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: Tuple[TuyaManufCluster.Command], + args: tuple[TuyaManufCluster.Command], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] @@ -1558,12 +1555,12 @@ class TuyaNewManufCluster(CustomCluster): ), } - data_point_handlers: Dict[int, str] = {} + data_point_handlers: dict[int, str] = {} def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: Tuple, + args: tuple, *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] diff --git a/zhaquirks/tuya/air/__init__.py b/zhaquirks/tuya/air/__init__.py index 05d143791f..96b9c71cd4 100644 --- a/zhaquirks/tuya/air/__init__.py +++ b/zhaquirks/tuya/air/__init__.py @@ -1,6 +1,6 @@ """Tuya Air sensors.""" -from typing import Any, Dict +from typing import Any import zigpy.types as t from zigpy.zcl.clusters.measurement import ( @@ -69,7 +69,7 @@ class TuyaAirQualityHumidity(RelativeHumidity, TuyaLocalCluster): class TuyaAirQualityPM25(PM25, TuyaLocalCluster): - """Tuya PM25 concentration measurement""" + """Tuya PM25 concentration measurement.""" class TuyaAirQualityCO2(CarbonDioxideConcentration, TuyaLocalCluster): @@ -83,7 +83,7 @@ class TuyaAirQualityFormaldehyde(FormaldehydeConcentration, TuyaLocalCluster): class TuyaCO2ManufCluster(TuyaNewManufCluster): """Tuya with Air quality data points.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 2: DPToAttributeMapping( TuyaAirQualityCO2.ep_attribute, "measured_value", diff --git a/zhaquirks/tuya/mcu/__init__.py b/zhaquirks/tuya/mcu/__init__.py index bd775bba79..1e550c0c74 100644 --- a/zhaquirks/tuya/mcu/__init__.py +++ b/zhaquirks/tuya/mcu/__init__.py @@ -1,18 +1,23 @@ -"""Tuya MCU comunications.""" +"""Tuya MCU communications.""" + +from collections.abc import Callable import dataclasses import datetime -from typing import Any, Callable, Dict, Optional, Tuple, Union +from typing import Any, Optional, Union import zigpy.types as t from zigpy.zcl import foundation from zigpy.zcl.clusters.general import LevelControl, OnOff from zhaquirks import Bus, DoublingPowerConfigurationCluster + +# add EnchantedDevice import for custom quirks backwards compatibility from zhaquirks.tuya import ( TUYA_MCU_COMMAND, TUYA_MCU_VERSION_RSP, TUYA_SET_DATA, TUYA_SET_TIME, + EnchantedDevice, # noqa: F401 NoManufacturerCluster, PowerOnState, TuyaCommand, @@ -23,9 +28,6 @@ TuyaTimePayload, ) -# add EnchantedDevice import for custom quirks backwards compatibility -from zhaquirks.tuya import EnchantedDevice # noqa: F401 - # New manufacturer attributes ATTR_MCU_VERSION = 0xEF00 @@ -284,7 +286,7 @@ def tuya_mcu_command(self, cluster_data: TuyaClusterData): def get_dp_mapping( self, endpoint_id: int, attribute_name: str - ) -> Optional[Tuple[int, DPToAttributeMapping]]: + ) -> Optional[tuple[int, DPToAttributeMapping]]: """Search for the DP in dp_to_attribute.""" result = {} @@ -319,7 +321,7 @@ def handle_set_time_request(self, payload: t.uint16_t) -> foundation.Status: self.debug("handle_set_time_request payload: %s", payload) payload_rsp = TuyaTimePayload() - utc_now = datetime.datetime.utcnow() + utc_now = datetime.datetime.utcnow() # noqa: DTZ003 now = datetime.datetime.now() offset_time = datetime.datetime(self.set_time_offset, 1, 1) @@ -406,7 +408,7 @@ class TuyaOnOffNM(NoManufacturerCluster, TuyaOnOff): class TuyaOnOffManufCluster(TuyaMCUCluster): """Tuya with On/Off data points.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", @@ -519,9 +521,9 @@ class MoesSwitchManufCluster(TuyaOnOffManufCluster): } ) - dp_to_attribute: Dict[ - int, DPToAttributeMapping - ] = TuyaOnOffManufCluster.dp_to_attribute.copy() + dp_to_attribute: dict[int, DPToAttributeMapping] = ( + TuyaOnOffManufCluster.dp_to_attribute.copy() + ) dp_to_attribute.update( { 14: DPToAttributeMapping( @@ -637,7 +639,7 @@ class TuyaInWallLevelControl(TuyaAttributesCluster, TuyaLevelControl): class TuyaLevelControlManufCluster(TuyaMCUCluster): """Tuya with Level Control data points.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", diff --git a/zhaquirks/tuya/ts0001_fingerbot.py b/zhaquirks/tuya/ts0001_fingerbot.py index b00b373d83..0f85da45e5 100644 --- a/zhaquirks/tuya/ts0001_fingerbot.py +++ b/zhaquirks/tuya/ts0001_fingerbot.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Optional, Union +"""Tuya Fingerbot device.""" + +from typing import Any, Optional, Union from zigpy.profiles import zha import zigpy.types as t @@ -23,17 +25,23 @@ class FingerBotMode(t.enum8): + """FingerBot mode.""" + CLICK = 0x00 SWITCH = 0x01 PROGRAM = 0x02 class FingerBotReverse(t.enum8): + """FingerBot reverse.""" + UP_ON = 0x00 UP_OFF = 0x01 class TuyaFingerbotCluster(TuyaMCUCluster): + """Tuya Fingerbot cluster.""" + attributes = TuyaMCUCluster.attributes.copy() attributes.update( { @@ -67,7 +75,7 @@ async def command( **kwargs, ) - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { # Mode 101: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, @@ -116,6 +124,8 @@ class OnOffEnchantable(TuyaEnchantableCluster, OnOff): class TuyaFingerbot(EnchantedDevice): + """Tuya finger bot device.""" + signature = { MODELS_INFO: [("_TZ3210_dse8ogfy", "TS0001"), ("_TZ3210_j4pdtz9v", "TS0001")], ENDPOINTS: { diff --git a/zhaquirks/tuya/ts0210.py b/zhaquirks/tuya/ts0210.py index 48c6738a99..bedbebd319 100644 --- a/zhaquirks/tuya/ts0210.py +++ b/zhaquirks/tuya/ts0210.py @@ -1,6 +1,6 @@ """TS0210 vibration sensor.""" -from typing import Optional, Tuple, Union +from typing import Optional, Union from zigpy.profiles import zha from zigpy.quirks import CustomDevice @@ -34,7 +34,7 @@ class VibrationCluster(LocalDataCluster, MotionOnEvent, IasZone): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: Tuple[TuyaManufCluster.Command], + args: tuple[TuyaManufCluster.Command], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] @@ -53,7 +53,8 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) signature = { - # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=0, input_clusters=[0, 10, 1, 1280], output_clusters=[25]) + # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=0, + # input_clusters=[0, 10, 1, 1280], output_clusters=[25]) MODEL: "TS0210", ENDPOINTS: { 1: { diff --git a/zhaquirks/tuya/ts0211.py b/zhaquirks/tuya/ts0211.py index 33a14dfef1..1149074fac 100644 --- a/zhaquirks/tuya/ts0211.py +++ b/zhaquirks/tuya/ts0211.py @@ -1,6 +1,6 @@ """Tuya Doorbell.""" -from typing import Optional, Tuple, Union +from typing import Optional, Union from zigpy.profiles import zha from zigpy.quirks import CustomDevice @@ -29,7 +29,7 @@ class IasZoneDoorbellCluster(CustomCluster, IasZone): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: Tuple[IasZone.ZoneStatus], + args: tuple[IasZone.ZoneStatus], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] diff --git a/zhaquirks/tuya/ts0601_garage.py b/zhaquirks/tuya/ts0601_garage.py index 5ce8bdd987..f97861f476 100644 --- a/zhaquirks/tuya/ts0601_garage.py +++ b/zhaquirks/tuya/ts0601_garage.py @@ -1,5 +1,4 @@ """Tuya based cover and blinds.""" -from typing import Dict from zigpy.profiles import zgp, zha from zigpy.quirks import CustomDevice @@ -39,7 +38,7 @@ class TuyaGarageManufCluster(NoManufacturerCluster, TuyaMCUCluster): } ) - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { # garage door trigger ¿on movement, on open, on closed? 1: DPToAttributeMapping( TUYA_MANUFACTURER_GARAGE, diff --git a/zhaquirks/tuya/ts0601_illuminance.py b/zhaquirks/tuya/ts0601_illuminance.py index 845a334f32..32505d2670 100644 --- a/zhaquirks/tuya/ts0601_illuminance.py +++ b/zhaquirks/tuya/ts0601_illuminance.py @@ -1,7 +1,6 @@ """Tuya illuminance sensors.""" import math -from typing import Dict from zigpy.profiles import zgp, zha from zigpy.quirks import CustomDevice @@ -46,7 +45,7 @@ class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster): class TuyaIlluminanceCluster(TuyaMCUCluster): """Tuya Illuminance cluster.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { TUYA_BRIGHTNESS_LEVEL_DP: DPToAttributeMapping( TuyaIlluminanceMeasurement.ep_attribute, "manufacturer_brightness_level", diff --git a/zhaquirks/tuya/ts0601_motion.py b/zhaquirks/tuya/ts0601_motion.py index 45c34e8a23..09fac192b7 100644 --- a/zhaquirks/tuya/ts0601_motion.py +++ b/zhaquirks/tuya/ts0601_motion.py @@ -1,7 +1,7 @@ """BlitzWolf IS-3/Tuya motion rechargeable occupancy sensor.""" import math -from typing import Dict, Optional, Tuple, Union +from typing import Optional, Union from zigpy.profiles import zgp, zha from zigpy.quirks import CustomDevice @@ -86,7 +86,7 @@ class NeoMotionManufCluster(TuyaNewManufCluster): } ) - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 101: DPToAttributeMapping( TuyaOccupancySensing.ep_attribute, "occupancy", @@ -150,7 +150,7 @@ class MmwRadarManufCluster(TuyaMCUCluster): } ) - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaOccupancySensing.ep_attribute, "occupancy", @@ -242,7 +242,7 @@ class TuyaManufacturerClusterMotion(TuyaManufCluster): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: Tuple[TuyaManufCluster.Command], + args: tuple[TuyaManufCluster.Command], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] diff --git a/zhaquirks/tuya/ts0601_rcbo.py b/zhaquirks/tuya/ts0601_rcbo.py index ef03e54cea..3c65eaebd9 100644 --- a/zhaquirks/tuya/ts0601_rcbo.py +++ b/zhaquirks/tuya/ts0601_rcbo.py @@ -1,5 +1,6 @@ """Tuya Din RCBO Circuit Breaker.""" -from typing import Any, Dict, Optional, Union + +from typing import Any, Optional, Union from zigpy.profiles import zha from zigpy.quirks import CustomCluster, CustomDevice @@ -335,7 +336,7 @@ async def command( class TuyaRCBOManufCluster(TuyaMCUCluster): """Tuya with power measurement data points.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { TUYA_DP_STATE: DPToAttributeMapping( TuyaRCBOOnOff.ep_attribute, "on_off", @@ -422,7 +423,11 @@ class TuyaRCBOManufCluster(TuyaMCUCluster): x[5] | x[4] << 8, x[6], ), - lambda rms_extreme_over_voltage, over_voltage_trip, ac_alarms_mask, rms_extreme_under_voltage, under_voltage_trip: VoltageParameters( + lambda rms_extreme_over_voltage, + over_voltage_trip, + ac_alarms_mask, + rms_extreme_under_voltage, + under_voltage_trip: VoltageParameters( rms_extreme_over_voltage, over_voltage_trip, bool(ac_alarms_mask & 0x40), @@ -439,7 +444,9 @@ class TuyaRCBOManufCluster(TuyaMCUCluster): x[3], AttributeWithMask(x[4] << 1, 1 << 1), ), - lambda ac_current_overload, over_current_trip, ac_alarms_mask: CurrentParameters( + lambda ac_current_overload, + over_current_trip, + ac_alarms_mask: CurrentParameters( ac_current_overload, over_current_trip, bool(ac_alarms_mask & 0x02) ), ), @@ -478,7 +485,7 @@ class TuyaRCBOManufCluster(TuyaMCUCluster): ), } - data_point_handlers: Dict[int, str] = { + data_point_handlers: dict[int, str] = { TUYA_DP_STATE: "_dp_2_attr_update", TUYA_DP_COUNTDOWN_TIMER: "_dp_2_attr_update", TUYA_DP_FAULT_CODE: "_dp_2_attr_update", diff --git a/zhaquirks/tuya/ts0601_sensor.py b/zhaquirks/tuya/ts0601_sensor.py index 30338fab49..8c8bc9258e 100644 --- a/zhaquirks/tuya/ts0601_sensor.py +++ b/zhaquirks/tuya/ts0601_sensor.py @@ -1,6 +1,6 @@ """Tuya temp and humidity sensors.""" -from typing import Any, Dict +from typing import Any from zigpy.profiles import zha from zigpy.quirks import CustomDevice @@ -50,7 +50,7 @@ def update_attribute(self, attr_name: str, value: Any) -> None: class TemperatureHumidityManufCluster(TuyaMCUCluster): """Tuya Manufacturer Cluster with Temperature and Humidity data points.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaTemperatureMeasurement.ep_attribute, "measured_value", @@ -78,7 +78,7 @@ class TemperatureHumidityManufCluster(TuyaMCUCluster): class TemperatureHumidityBatteryStatesManufCluster(TuyaMCUCluster): """Tuya Manufacturer Cluster with Temperature and Humidity data points. Battery states 25, 50 and 100%.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: TemperatureHumidityManufCluster.dp_to_attribute[1], 2: TemperatureHumidityManufCluster.dp_to_attribute[2], 3: DPToAttributeMapping( @@ -291,7 +291,7 @@ class TuyaTempHumiditySensorVar04(CustomDevice): class SoilManufCluster(TuyaMCUCluster): """Tuya Manufacturer Cluster with Temperature and Humidity data points.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 5: DPToAttributeMapping( TuyaTemperatureMeasurement.ep_attribute, "measured_value", diff --git a/zhaquirks/tuya/ts0601_siren.py b/zhaquirks/tuya/ts0601_siren.py index 3d4f597e71..5bc321bd31 100644 --- a/zhaquirks/tuya/ts0601_siren.py +++ b/zhaquirks/tuya/ts0601_siren.py @@ -1,5 +1,6 @@ """Map from manufacturer to standard clusters for the NEO Siren device.""" -from typing import Dict, Optional, Union + +from typing import Optional, Union from zigpy.profiles import zgp, zha from zigpy.quirks import CustomDevice @@ -106,15 +107,18 @@ def _update_attribute(self, attrid, value): super()._update_attribute(attrid, value) if attrid == TUYA_TEMPERATURE_ATTR: self.endpoint.device.temperature_bus.listener_event( - "temperature_reported", value * 10 # decidegree to centidegree + "temperature_reported", + value * 10, # decidegree to centidegree ) elif attrid == TUYA_HUMIDITY_ATTR: self.endpoint.device.humidity_bus.listener_event( - "humidity_reported", value * 100 # whole percentage to 1/1000th + "humidity_reported", + value * 100, # whole percentage to 1/1000th ) elif attrid == TUYA_ALARM_ATTR: self.endpoint.device.switch_bus.listener_event( - "switch_event", value # boolean 1=on / 0=off + "switch_event", + value, # boolean 1=on / 0=off ) @@ -309,7 +313,7 @@ async def command( class NeoSirenManufCluster(TuyaMCUCluster): """Tuya with NEO Siren data points.""" - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 5: DPToAttributeMapping( TuyaMCUSiren.ep_attribute, "volume", diff --git a/zhaquirks/tuya/ts0601_trv.py b/zhaquirks/tuya/ts0601_trv.py index d480b9c1c6..9ce875f307 100644 --- a/zhaquirks/tuya/ts0601_trv.py +++ b/zhaquirks/tuya/ts0601_trv.py @@ -1,4 +1,5 @@ """Map from manufacturer to standard clusters for thermostatic valves.""" + import logging from typing import Optional, Union @@ -525,13 +526,12 @@ def map_attribute(self, attribute, value): ) / 100 ) + elif attr == attribute: + val = value else: - if attr == attribute: - val = value - else: - val = self._attr_cache.get( - self.attributes_by_name[attr].id, default - ) + val = self._attr_cache.get( + self.attributes_by_name[attr].id, default + ) data.append(val) return {MOES_SCHEDULE_WORKDAY_ATTR: data} @@ -548,13 +548,12 @@ def map_attribute(self, attribute, value): ) / 100 ) + elif attr == attribute: + val = value else: - if attr == attribute: - val = value - else: - val = self._attr_cache.get( - self.attributes_by_name[attr].id, default - ) + val = self._attr_cache.get( + self.attributes_by_name[attr].id, default + ) data.append(val) return {MOES_SCHEDULE_WEEKEND_ATTR: data} @@ -567,10 +566,7 @@ def mode_change(self, value): elif value == 1: prog_mode = self.ProgrammingOperationMode.Schedule_programming_mode occupancy = self.Occupancy.Occupied - elif value == 2: - prog_mode = self.ProgrammingOperationMode.Simple - occupancy = self.Occupancy.Occupied - elif value == 3: + elif value in (2, 3): prog_mode = self.ProgrammingOperationMode.Simple occupancy = self.Occupancy.Occupied elif value == 4: @@ -942,7 +938,7 @@ class ZONNSMARTManufCluster(TuyaManufClusterAttributes): def __init__(self, *args, **kwargs): """Init.""" super().__init__(*args, **kwargs) - global ZonnsmartManuClusterSelf + global ZonnsmartManuClusterSelf # noqa: PLW0603 ZonnsmartManuClusterSelf = self attributes = TuyaManufClusterAttributes.attributes.copy() diff --git a/zhaquirks/tuya/ts0601_valve.py b/zhaquirks/tuya/ts0601_valve.py index 919b160c50..7e6d0f1f58 100644 --- a/zhaquirks/tuya/ts0601_valve.py +++ b/zhaquirks/tuya/ts0601_valve.py @@ -1,5 +1,4 @@ """Collection of Tuya Valve devices e.g. water valves, gas valve etc.""" -from typing import Dict from zigpy.profiles import zha from zigpy.quirks import CustomDevice @@ -54,7 +53,7 @@ class TuyaValveManufCluster(TuyaMCUCluster): } ) - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", @@ -194,7 +193,7 @@ class ParksideTuyaValveManufCluster(TuyaMCUCluster): } ) - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", @@ -293,7 +292,7 @@ class ParksidePSBZS(EnchantedDevice): GIEX_START_TIME_ATTR = 0xEF65 # Last irrigation start time (GMT) GIEX_END_TIME_ATTR = 0xEF66 # Last irrigation end time (GMT) GIEX_NUM_TIMES_ATTR = 0xEF67 # Number of cycle irrigation times min=0 max=100 -GIEX_TARGET_ATTR = 0xEF68 # Irrigation target, duration in seconds or capacity in litres (depending on mode) min=0 max=3600 +GIEX_TARGET_ATTR = 0xEF68 # Irrigation target, duration in sec or capacity in litres (depending on mode) min=0 max=3600 GIEX_INTERVAL_ATTR = 0xEF69 # Cycle irrigation interval in seconds min=0 max=3600 GIEX_DURATION_ATTR = 0xEF72 # Last irrigation duration @@ -314,7 +313,7 @@ class GiexValveManufCluster(TuyaMCUCluster): } ) - dp_to_attribute: Dict[int, DPToAttributeMapping] = { + dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "irrigation_mode", diff --git a/zhaquirks/tuya/ts130f.py b/zhaquirks/tuya/ts130f.py index 784d10e7e9..ba97ce2469 100644 --- a/zhaquirks/tuya/ts130f.py +++ b/zhaquirks/tuya/ts130f.py @@ -1,4 +1,5 @@ """Device handler for loratap TS130F smart curtain switch.""" + from zigpy.profiles import zgp, zha from zigpy.quirks import CustomCluster, CustomDevice import zigpy.types as t @@ -273,7 +274,7 @@ class TuyaTS130FTO(CustomDevice): signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=0x0202, device_version=1, input_clusters=[0, 4, 5, 6, 10, 0x0102], output_clusters=[25])) - # This singnature is not correct is one copy of the first one and the cluster is not inline with the device. + # This signature is not correct is one copy of the first one and the cluster is not inline with the device. MODEL: "TS130F", ENDPOINTS: { 1: { diff --git a/zhaquirks/waxman/leaksmart.py b/zhaquirks/waxman/leaksmart.py index 2bcb0865f5..7efb7db50a 100644 --- a/zhaquirks/waxman/leaksmart.py +++ b/zhaquirks/waxman/leaksmart.py @@ -1,6 +1,7 @@ """Device handler for WAXMAN leakSMART.""" + # pylint: disable=W0102 -from typing import Any, List, Optional, Union +from typing import Any, Optional, Union from zigpy.profiles import zha from zigpy.quirks import CustomCluster, CustomDevice @@ -78,7 +79,7 @@ def __init__(self, *args, **kwargs): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: List[Any], + args: list[Any], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] diff --git a/zhaquirks/xbee/__init__.py b/zhaquirks/xbee/__init__.py index 470d2692f0..17f8317fd4 100644 --- a/zhaquirks/xbee/__init__.py +++ b/zhaquirks/xbee/__init__.py @@ -6,7 +6,7 @@ import asyncio import enum import logging -from typing import Any, List, Optional +from typing import Any, Optional from zigpy.quirks import CustomDevice import zigpy.types as t @@ -320,7 +320,7 @@ async def _remote_at_command(self, options, name, *args): await self._command(options, name.encode("ascii"), data, *args), timeout=REMOTE_AT_COMMAND_TIMEOUT, ) - except asyncio.TimeoutError: + except TimeoutError: _LOGGER.warning("No response to %s command", name) raise @@ -430,7 +430,7 @@ def save_at_request(self, frame_id, future): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: List[Any], + args: list[Any], *, dst_addressing: Optional[t.AddrMode] = None, ): @@ -484,7 +484,7 @@ class XBeeDigitalIOCluster(LocalDataCluster, BinaryInput): def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: List[Any], + args: list[Any], *, dst_addressing: Optional[t.AddrMode] = None, ): @@ -587,7 +587,7 @@ async def command( def handle_cluster_request( self, hdr: foundation.ZCLHeader, - args: List[Any], + args: list[Any], *, dst_addressing: Optional[t.AddrMode] = None, ): diff --git a/zhaquirks/xiaomi/__init__.py b/zhaquirks/xiaomi/__init__.py index a01ad46b4b..603876533b 100644 --- a/zhaquirks/xiaomi/__init__.py +++ b/zhaquirks/xiaomi/__init__.py @@ -1,14 +1,17 @@ """Xiaomi common components for custom device handlers.""" + from __future__ import annotations +from collections.abc import Iterable, Iterator import logging import math -from typing import Any, Iterable, Iterator +from typing import Any from zigpy import types as t import zigpy.device from zigpy.profiles import zha from zigpy.quirks import CustomCluster, CustomDevice +from zigpy.zcl import foundation from zigpy.zcl.clusters.general import ( AnalogInput, Basic, @@ -26,7 +29,6 @@ ) from zigpy.zcl.clusters.security import IasZone from zigpy.zcl.clusters.smartenergy import Metering -import zigpy.zcl.foundation as foundation import zigpy.zdo from zigpy.zdo.types import NodeDescriptor @@ -162,10 +164,13 @@ def _iter_parse_attr_report( attr_val = t.LVBytes(val) attr_type = 0x41 # The data type should be "Octet String" - yield foundation.Attribute( - attrid=attr_id, - value=foundation.TypeValue(type=attr_type, value=attr_val), - ), final_data + yield ( + foundation.Attribute( + attrid=attr_id, + value=foundation.TypeValue(type=attr_type, value=attr_val), + ), + final_data, + ) def _interpret_attr_reports( self, data: bytes @@ -511,7 +516,7 @@ def _update_battery_percentage(self, voltage_mv: int) -> None: class XiaomiPowerConfigurationPercent(XiaomiPowerConfiguration): - """Power cluster which ignores Xiaomi voltage reports for calculating battery percentage + """Power cluster which ignores Xiaomi voltage reports for calculating battery percentage. Devices that use this cluster (E1 curtain driver/roller) already send the battery percentage on their own as a separate attribute, but additionally also send the battery voltage. diff --git a/zhaquirks/xiaomi/aqara/feeder_acn001.py b/zhaquirks/xiaomi/aqara/feeder_acn001.py index 35e986516e..238e5869a0 100644 --- a/zhaquirks/xiaomi/aqara/feeder_acn001.py +++ b/zhaquirks/xiaomi/aqara/feeder_acn001.py @@ -4,8 +4,9 @@ import logging from typing import Any +from zigpy import types from zigpy.profiles import zgp, zha -import zigpy.types as types +from zigpy.zcl import foundation from zigpy.zcl.clusters.general import ( Basic, GreenPowerProxy, @@ -16,7 +17,6 @@ Scenes, Time, ) -import zigpy.zcl.foundation as foundation from zhaquirks.const import ( DEVICE_TYPE, diff --git a/zhaquirks/xiaomi/aqara/illumination.py b/zhaquirks/xiaomi/aqara/illumination.py index 99a360beb6..feb6967737 100644 --- a/zhaquirks/xiaomi/aqara/illumination.py +++ b/zhaquirks/xiaomi/aqara/illumination.py @@ -1,7 +1,7 @@ """Quirk for Aqara illumination sensor.""" +from zigpy import types from zigpy.profiles import zha -import zigpy.types as types from zigpy.zcl.clusters.general import Basic, Identify, PowerConfiguration from zigpy.zcl.clusters.measurement import IlluminanceMeasurement from zigpy.zdo.types import NodeDescriptor diff --git a/zhaquirks/xiaomi/aqara/light_acn.py b/zhaquirks/xiaomi/aqara/light_acn.py index 9ef4527027..e82f2059a9 100644 --- a/zhaquirks/xiaomi/aqara/light_acn.py +++ b/zhaquirks/xiaomi/aqara/light_acn.py @@ -1,3 +1,5 @@ +"""Aqara light.""" + from zigpy import types as t from zigpy.profiles import zgp, zha from zigpy.zcl.clusters.general import ( diff --git a/zhaquirks/xiaomi/aqara/motion_ac01.py b/zhaquirks/xiaomi/aqara/motion_ac01.py index de80633b96..2b6bd0cd98 100644 --- a/zhaquirks/xiaomi/aqara/motion_ac01.py +++ b/zhaquirks/xiaomi/aqara/motion_ac01.py @@ -1,11 +1,12 @@ """Quirk for aqara lumi.motion.ac01.""" + from __future__ import annotations from typing import Any +from zigpy import types from zigpy.profiles import zha from zigpy.quirks import CustomDevice -import zigpy.types as types from zigpy.zcl.clusters.general import Basic, DeviceTemperature, Identify, Ota from zigpy.zcl.clusters.measurement import OccupancySensing @@ -60,10 +61,10 @@ class OppleCluster(XiaomiAqaraE1Cluster): def _update_attribute(self, attrid: int, value: Any) -> None: super()._update_attribute(attrid, value) - if attrid == PRESENCE or attrid == PRESENCE2: + if attrid in (PRESENCE, PRESENCE2): if value != 0xFF: self.endpoint.occupancy.update_attribute(OCCUPANCY, value) - elif attrid == PRESENCE_EVENT or attrid == PRESENCE_EVENT2: + elif attrid in (PRESENCE_EVENT, PRESENCE_EVENT2): self.listener_event(ZHA_SEND_EVENT, AqaraPresenceEvents(value).name, {}) diff --git a/zhaquirks/xiaomi/aqara/motion_ac02.py b/zhaquirks/xiaomi/aqara/motion_ac02.py index a285f2e41d..efb52175b6 100644 --- a/zhaquirks/xiaomi/aqara/motion_ac02.py +++ b/zhaquirks/xiaomi/aqara/motion_ac02.py @@ -4,9 +4,9 @@ import logging from typing import Any +from zigpy import types from zigpy.profiles import zha from zigpy.quirks import CustomDevice -import zigpy.types as types from zigpy.zcl.clusters.general import Basic, Identify, Ota, PowerConfiguration from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing diff --git a/zhaquirks/xiaomi/aqara/motion_agl04.py b/zhaquirks/xiaomi/aqara/motion_agl04.py index 789462f0b0..74c4fe6d70 100644 --- a/zhaquirks/xiaomi/aqara/motion_agl04.py +++ b/zhaquirks/xiaomi/aqara/motion_agl04.py @@ -3,8 +3,8 @@ from typing import Any +from zigpy import types from zigpy.profiles import zha -import zigpy.types as types from zigpy.zcl.clusters.general import Basic, Identify, Ota, PowerConfiguration from zigpy.zcl.clusters.measurement import OccupancySensing diff --git a/zhaquirks/xiaomi/aqara/opple_remote.py b/zhaquirks/xiaomi/aqara/opple_remote.py index a3590e4f44..a848a0e5e9 100644 --- a/zhaquirks/xiaomi/aqara/opple_remote.py +++ b/zhaquirks/xiaomi/aqara/opple_remote.py @@ -1,7 +1,7 @@ """Xiaomi aqara opple remote devices.""" +from zigpy import types from zigpy.profiles import zha -import zigpy.types as types from zigpy.zcl.clusters.general import ( Basic, Identify, diff --git a/zhaquirks/xiaomi/aqara/plug_eu.py b/zhaquirks/xiaomi/aqara/plug_eu.py index 5b5fc4875c..fc7b95f44e 100644 --- a/zhaquirks/xiaomi/aqara/plug_eu.py +++ b/zhaquirks/xiaomi/aqara/plug_eu.py @@ -1,8 +1,8 @@ """Xiaomi Aqara EU plugs.""" import zigpy +from zigpy import types from zigpy.profiles import zgp, zha -import zigpy.types as types from zigpy.zcl.clusters.general import ( Alarms, AnalogInput, diff --git a/zhaquirks/xiaomi/aqara/smoke.py b/zhaquirks/xiaomi/aqara/smoke.py index ede4671444..a87c22a9f5 100644 --- a/zhaquirks/xiaomi/aqara/smoke.py +++ b/zhaquirks/xiaomi/aqara/smoke.py @@ -1,9 +1,9 @@ """Quirk for LUMI lumi.sensor_smoke.acn03 smoke sensor.""" from typing import Any +from zigpy import types from zigpy.profiles import zha from zigpy.quirks import CustomDevice -import zigpy.types as types from zigpy.zcl.clusters.general import Basic, Identify, Ota, PowerConfiguration from zigpy.zcl.clusters.security import IasZone from zigpy.zdo.types import NodeDescriptor diff --git a/zhaquirks/xiaomi/aqara/switch_h1_single.py b/zhaquirks/xiaomi/aqara/switch_h1_single.py index b9d2f915ac..ae951bb181 100644 --- a/zhaquirks/xiaomi/aqara/switch_h1_single.py +++ b/zhaquirks/xiaomi/aqara/switch_h1_single.py @@ -1,4 +1,5 @@ """Aqara H1 single rocker switch quirks. Also see opple_switch.py for similar double rocker switches.""" + from zigpy.profiles import zgp, zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import ( @@ -56,7 +57,7 @@ class AqaraH1SingleRockerBase(CustomDevice): - """Device automation triggers for the Aqara H1 Single Rocker Switches""" + """Device automation triggers for the Aqara H1 Single Rocker Switches.""" device_automation_triggers = { (SHORT_PRESS, BUTTON): { diff --git a/zhaquirks/xiaomi/aqara/thermostat_agl001.py b/zhaquirks/xiaomi/aqara/thermostat_agl001.py index 5aae8ad4f2..0fdb90bda1 100644 --- a/zhaquirks/xiaomi/aqara/thermostat_agl001.py +++ b/zhaquirks/xiaomi/aqara/thermostat_agl001.py @@ -140,11 +140,12 @@ async def write_attributes( class ScheduleEvent: - """Schedule event object""" + """Schedule event object.""" _is_next_day = False def __init__(self, value, is_next_day=False): + """Create ScheduleEvent object from bytes or string.""" if isinstance(value, bytes): self._verify_buffer_len(value) self._time = self._read_time_from_buf(value) @@ -177,8 +178,8 @@ def _read_time_from_buf(buf): return time @staticmethod - def _parse_time(str): - parts = str.split(":") + def _parse_time(string): + parts = string.split(":") if len(parts) != 2: raise ValueError("Time must contain ':' separator") @@ -192,8 +193,8 @@ def _read_temp_from_buf(buf): return struct.unpack_from(">H", buf, offset=4)[0] / 100 @staticmethod - def _parse_temp(str): - return float(str) + def _parse_temp(string): + return float(string) @staticmethod def _validate_time(time): @@ -221,18 +222,23 @@ def _write_temp_to_buf(self, buf): struct.pack_into(">H", buf, 4, int(self._temp * 100)) def is_next_day(self): + """Return if event is on the next day.""" return self._is_next_day def set_next_day(self, is_next_day): + """Set if event is on the next day.""" self._is_next_day = is_next_day def get_time(self): + """Return event time.""" return self._time def __str__(self): - return f"{math.floor(self._time / 60)}:{f'{self._time % 60:0>2}'},{f'{self._temp:.1f}'}" # noqa + """Return event as string.""" + return f"{math.floor(self._time / 60)}:{f'{self._time % 60:0>2}'},{f'{self._temp:.1f}'}" def serialize(self): + """Serialize event to bytes.""" result = bytearray(6) self._write_time_to_buf(result) self._write_temp_to_buf(result) @@ -240,9 +246,10 @@ def serialize(self): class ScheduleSettings(t.LVBytes): - """Schedule settings object""" + """Schedule settings object.""" def __new__(cls, value): + """Create ScheduleSettings object from bytes or string.""" day_selection = None events = [None] * 4 if isinstance(value, bytes): @@ -297,7 +304,7 @@ def _verify_day_selection_in_str(days): if len(days) != len(set(days)): raise ValueError("Duplicate day names present") for d in days: - if d not in DAYS_MAP.keys(): + if d not in DAYS_MAP: raise ValueError( f"String: {d} is not a valid day name, valid names: mon, tue, wed, thu, fri, sat, sun" ) @@ -309,8 +316,8 @@ def _read_day_selection(value): byte = struct.unpack_from("c", value, offset=1)[0][0] if byte & 0x01: raise ValueError("Incorrect day selected") - for i in DAYS_MAP: - if byte & DAYS_MAP[i]: + for i, v in DAYS_MAP.items(): + if byte & v: day_selection.append(i) ScheduleSettings._verify_day_selection_in_str(day_selection) elif isinstance(value, str): @@ -351,6 +358,7 @@ def _get_day_selection_byte(day_selection): return byte def __str__(self): + """Return ScheduleSettings as string.""" day_selection = ScheduleSettings._read_day_selection(self) events = [None] * 4 for i in range(4): diff --git a/zhaquirks/xiaomi/aqara/vibration_aq1.py b/zhaquirks/xiaomi/aqara/vibration_aq1.py index e5048f7d54..15bea3169c 100644 --- a/zhaquirks/xiaomi/aqara/vibration_aq1.py +++ b/zhaquirks/xiaomi/aqara/vibration_aq1.py @@ -1,9 +1,9 @@ """Xiaomi aqara smart motion sensor device.""" import math +from zigpy import types from zigpy.profiles import zha from zigpy.quirks import CustomCluster -import zigpy.types as types from zigpy.zcl.clusters.closures import DoorLock from zigpy.zcl.clusters.general import ( Basic, diff --git a/zhaquirks/xiaomi/mija/sensor_switch.py b/zhaquirks/xiaomi/mija/sensor_switch.py index 9adfea75f0..eba8f3b8fd 100644 --- a/zhaquirks/xiaomi/mija/sensor_switch.py +++ b/zhaquirks/xiaomi/mija/sensor_switch.py @@ -95,13 +95,12 @@ def _update_attribute(self, attrid, value): self._timer_handle = self._loop.call_later( self.hold_duration, self._hold_timeout ) + elif self._timer_handle: + self._timer_handle.cancel() + self._timer_handle = None + click_type = COMMAND_SINGLE else: - if self._timer_handle: - self._timer_handle.cancel() - self._timer_handle = None - click_type = COMMAND_SINGLE - else: - self.listener_event(ZHA_SEND_EVENT, COMMAND_RELEASE, []) + self.listener_event(ZHA_SEND_EVENT, COMMAND_RELEASE, []) # Handle Multi Clicks elif attrid == 32768: diff --git a/zhaquirks/zbeacon/__init__.py b/zhaquirks/zbeacon/__init__.py index e69de29bb2..d5fddb6f07 100644 --- a/zhaquirks/zbeacon/__init__.py +++ b/zhaquirks/zbeacon/__init__.py @@ -0,0 +1 @@ +"""zbeacon module.""" diff --git a/zhaquirks/zbeacon/doorsensor.py b/zhaquirks/zbeacon/doorsensor.py index b8fa562aaa..8d47c5144c 100644 --- a/zhaquirks/zbeacon/doorsensor.py +++ b/zhaquirks/zbeacon/doorsensor.py @@ -1,4 +1,4 @@ -"""Doorsensors""" +"""Doorsensors.""" from zigpy.profiles import zha from zigpy.quirks import CustomDevice @@ -22,7 +22,8 @@ class DS01DoorSensor(CustomDevice): - """One of the long rectangular Doorsensors working on 2xAAA + """One of the long rectangular Doorsensors working on 2xAAA. + It doesn't correctly implement the PollControl Cluster. The device will send "PollControl:checkin()" on PollControl cluster, but doesn't respond when checkin_response is sent after that from the coordinator @@ -35,7 +36,9 @@ class DS01DoorSensor(CustomDevice): signature = { MODELS_INFO: [("zbeacon", "DS01")], ENDPOINTS: { - # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=0, input_clusters=[0, 3, 1, 1280, 32], output_clusters=[25]) + # SizePrefixedSimpleDescriptor( + # endpoint=1, profile=260, device_type=1026, device_version=0, + # input_clusters=[0, 3, 1, 1280, 32], output_clusters=[25]) 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.IAS_ZONE,