From 7b1ec409f8bb221de5e03ad41297258821d266a9 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 17:14:51 +0000 Subject: [PATCH 01/34] bump version and set minimum required ha version --- custom_components/victron/manifest.json | 2 +- hacs.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/victron/manifest.json b/custom_components/victron/manifest.json index e0217bb..53687a2 100644 --- a/custom_components/victron/manifest.json +++ b/custom_components/victron/manifest.json @@ -15,6 +15,6 @@ "pymodbus>=3.7.4" ], "ssdp": [], - "version": "v0.4.0", + "version": "v0.5.0", "zeroconf": [] } diff --git a/hacs.json b/hacs.json index e8053fd..1caea0c 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,6 @@ { "name": "Victron GX modbus TCP", "render_readme": true, - "homeassistant": "2023.9.1", + "homeassistant": "2024.12", "hacs": "1.28.4" } \ No newline at end of file From 2335988a4a5cb4c3c562a69e86bb8d9170506e8f Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 17:23:58 +0000 Subject: [PATCH 02/34] backport more changes --- .gitignore | 1 + hacs.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1be9e74..6ded410 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ custom_components/victron/__pycache__ .DS_Store +.idea \ No newline at end of file diff --git a/hacs.json b/hacs.json index 1caea0c..11fdbdf 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,6 @@ { "name": "Victron GX modbus TCP", "render_readme": true, - "homeassistant": "2024.12", + "homeassistant": "2025.1", "hacs": "1.28.4" } \ No newline at end of file From e588bdf69abe57aa6ac83dd0a24e7149d7f729f2 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 18:29:42 +0000 Subject: [PATCH 03/34] add slightly modified PR template md file (ha core file as base) --- .github/PULL_REQUEST_TEMPLATE.md | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..3b83d88 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,60 @@ + +## Breaking change + + + +## Proposed change + + + +## Type of change + + +- [ ] Dependency upgrade +- [ ] Bugfix (non-breaking change which fixes an issue) +- [ ] New integration (thank you!) +- [ ] New feature (which adds functionality to an existing integration) +- [ ] Deprecation (breaking change to happen in the future) +- [ ] Breaking change (fix/feature causing existing functionality to break) +- [ ] Code quality improvements to existing code or addition of tests + +## Additional information + + +- This PR fixes or closes issue: fixes # +- This PR is related to issue: +- Link to documentation pull request: + +## Checklist + + +- [ ] The code change is tested and works locally. +- [ ] There is no commented out code in this PR. +- [ ] The code has been formatted using Ruff (`ruff format custom_components/victron`) From 1157b41220094507de7d0cd756b153661cdb277f Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 18:46:19 +0000 Subject: [PATCH 04/34] add slightly modified ha core issue template md file --- .github/ISSUE_TEMPLATE.md | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..54ce1fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,45 @@ + +## The problem + + + +## Environment + + +- Home Assistant Core release with the issue: +- Last working Home Assistant Core release (if known): +- Integration version causing this issue: + +## Problem-relevant `configuration dialog` + + +```yaml + +``` + + +## Traceback/Error logs + + +```txt + +``` + +## Additional information + From 418122f318cc1b71d8dc6703b2405d29f01d7e66 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 18:57:26 +0000 Subject: [PATCH 05/34] trying out an slimmed version of the homeassistant ruff config --- ruff.toml | 613 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 613 insertions(+) create mode 100644 ruff.toml diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..f77e935 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,613 @@ +[build-system] +requires = ["setuptools==75.1.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "hass-victron" +version = "0.5.0" +license = {text = "Apache-2.0"} +description = "Open-source integration for the victron modbus endpoint" +readme = "README.md" +keywords = ["home", "automation","homeassistant","victron"] +requires-python = ">=3.13.0" + +[project.urls] +"Source Code" = "https://github.com/sfstar/hass-victron" +"Bug Reports" = "https://github.com/home-assistant/sfstar/hass-victron/issues" + +[tool.pylint.MAIN] +py-version = "3.13" +# Use a conservative default here; 2 should speed up most setups and not hurt +# any too bad. Override on command line as appropriate. +jobs = 2 +init-hook = """\ + from pathlib import Path; \ + import sys; \ + + from pylint.config import find_default_config_files; \ + + sys.path.append( \ + str(Path(next(find_default_config_files())).parent.joinpath('pylint/plugins')) + ) \ + """ +load-plugins = [ + "pylint.extensions.code_style", + "pylint.extensions.typing", + "pylint_per_file_ignores", +] +persistent = false +extension-pkg-allow-list = [ + "av.audio.stream", + "av.logging", + "av.stream", + "ciso8601", + "orjson", + "cv2", +] +fail-on = [ + "I", +] + +[tool.pylint.BASIC] +class-const-naming-style = "any" + +[tool.pylint."MESSAGES CONTROL"] +# Reasons disabled: +# format - handled by ruff +# locally-disabled - it spams too much +# duplicate-code - unavoidable +# cyclic-import - doesn't test if both import on load +# abstract-class-little-used - prevents from setting right foundation +# unused-argument - generic callbacks and setup methods create a lot of warnings +# too-many-* - are not enforced for the sake of readability +# too-few-* - same as too-many-* +# abstract-method - with intro of async there are always methods missing +# inconsistent-return-statements - doesn't handle raise +# too-many-ancestors - it's too strict. +# wrong-import-order - isort guards this +# possibly-used-before-assignment - too many errors / not necessarily issues +# --- +# Pylint CodeStyle plugin +# consider-using-namedtuple-or-dataclass - too opinionated +# consider-using-assignment-expr - decision to use := better left to devs +disable = [ + "format", + "abstract-method", + "cyclic-import", + "duplicate-code", + "inconsistent-return-statements", + "locally-disabled", + "not-context-manager", + "too-few-public-methods", + "too-many-ancestors", + "too-many-arguments", + "too-many-instance-attributes", + "too-many-lines", + "too-many-locals", + "too-many-public-methods", + "too-many-boolean-expressions", + "too-many-positional-arguments", + "wrong-import-order", + "consider-using-namedtuple-or-dataclass", + "consider-using-assignment-expr", + "possibly-used-before-assignment", + + # Handled by ruff + # Ref: + "await-outside-async", # PLE1142 + "bad-str-strip-call", # PLE1310 + "bad-string-format-type", # PLE1307 + "bidirectional-unicode", # PLE2502 + "continue-in-finally", # PLE0116 + "duplicate-bases", # PLE0241 + "misplaced-bare-raise", # PLE0704 + "format-needs-mapping", # F502 + "function-redefined", # F811 + # Needed because ruff does not understand type of __all__ generated by a function + # "invalid-all-format", # PLE0605 + "invalid-all-object", # PLE0604 + "invalid-character-backspace", # PLE2510 + "invalid-character-esc", # PLE2513 + "invalid-character-nul", # PLE2514 + "invalid-character-sub", # PLE2512 + "invalid-character-zero-width-space", # PLE2515 + "logging-too-few-args", # PLE1206 + "logging-too-many-args", # PLE1205 + "missing-format-string-key", # F524 + "mixed-format-string", # F506 + "no-method-argument", # N805 + "no-self-argument", # N805 + "nonexistent-operator", # B002 + "nonlocal-without-binding", # PLE0117 + "not-in-loop", # F701, F702 + "notimplemented-raised", # F901 + "return-in-init", # PLE0101 + "return-outside-function", # F706 + "syntax-error", # E999 + "too-few-format-args", # F524 + "too-many-format-args", # F522 + "too-many-star-expressions", # F622 + "truncated-format-string", # F501 + "undefined-all-variable", # F822 + "undefined-variable", # F821 + "used-prior-global-declaration", # PLE0118 + "yield-inside-async-function", # PLE1700 + "yield-outside-function", # F704 + "anomalous-backslash-in-string", # W605 + "assert-on-string-literal", # PLW0129 + "assert-on-tuple", # F631 + "bad-format-string", # W1302, F + "bad-format-string-key", # W1300, F + "bare-except", # E722 + "binary-op-exception", # PLW0711 + "cell-var-from-loop", # B023 + # "dangerous-default-value", # B006, ruff catches new occurrences, needs more work + "duplicate-except", # B014 + "duplicate-key", # F601 + "duplicate-string-formatting-argument", # F + "duplicate-value", # F + "eval-used", # S307 + "exec-used", # S102 + "expression-not-assigned", # B018 + "f-string-without-interpolation", # F541 + "forgotten-debug-statement", # T100 + "format-string-without-interpolation", # F + # "global-statement", # PLW0603, ruff catches new occurrences, needs more work + "global-variable-not-assigned", # PLW0602 + "implicit-str-concat", # ISC001 + "import-self", # PLW0406 + "inconsistent-quotes", # Q000 + "invalid-envvar-default", # PLW1508 + "keyword-arg-before-vararg", # B026 + "logging-format-interpolation", # G + "logging-fstring-interpolation", # G + "logging-not-lazy", # G + "misplaced-future", # F404 + "named-expr-without-context", # PLW0131 + "nested-min-max", # PLW3301 + "pointless-statement", # B018 + "raise-missing-from", # B904 + "redefined-builtin", # A001 + "try-except-raise", # TRY302 + "unused-argument", # ARG001, we don't use it + "unused-format-string-argument", #F507 + "unused-format-string-key", # F504 + "unused-import", # F401 + "unused-variable", # F841 + "useless-else-on-loop", # PLW0120 + "wildcard-import", # F403 + "bad-classmethod-argument", # N804 + "consider-iterating-dictionary", # SIM118 + "empty-docstring", # D419 + "invalid-name", # N815 + "line-too-long", # E501, disabled globally + "missing-class-docstring", # D101 + "missing-final-newline", # W292 + "missing-function-docstring", # D103 + "missing-module-docstring", # D100 + "multiple-imports", #E401 + "singleton-comparison", # E711, E712 + "subprocess-run-check", # PLW1510 + "superfluous-parens", # UP034 + "ungrouped-imports", # I001 + "unidiomatic-typecheck", # E721 + "unnecessary-direct-lambda-call", # PLC3002 + "unnecessary-lambda-assignment", # PLC3001 + "unnecessary-pass", # PIE790 + "unneeded-not", # SIM208 + "useless-import-alias", # PLC0414 + "wrong-import-order", # I001 + "wrong-import-position", # E402 + "comparison-of-constants", # PLR0133 + "comparison-with-itself", # PLR0124 + "consider-alternative-union-syntax", # UP007 + "consider-merging-isinstance", # PLR1701 + "consider-using-alias", # UP006 + "consider-using-dict-comprehension", # C402 + "consider-using-generator", # C417 + "consider-using-get", # SIM401 + "consider-using-set-comprehension", # C401 + "consider-using-sys-exit", # PLR1722 + "consider-using-ternary", # SIM108 + "literal-comparison", # F632 + "property-with-parameters", # PLR0206 + "super-with-arguments", # UP008 + "too-many-branches", # PLR0912 + "too-many-return-statements", # PLR0911 + "too-many-statements", # PLR0915 + "trailing-comma-tuple", # COM818 + "unnecessary-comprehension", # C416 + "use-a-generator", # C417 + "use-dict-literal", # C406 + "use-list-literal", # C405 + "useless-object-inheritance", # UP004 + "useless-return", # PLR1711 + "no-else-break", # RET508 + "no-else-continue", # RET507 + "no-else-raise", # RET506 + "no-else-return", # RET505 + "broad-except", # BLE001 + "protected-access", # SLF001 + "broad-exception-raised", # TRY002 + "consider-using-f-string", # PLC0209 + # "no-self-use", # PLR6301 # Optional plugin, not enabled + + # Handled by mypy + # Ref: + "abstract-class-instantiated", + "arguments-differ", + "assigning-non-slot", + "assignment-from-no-return", + "assignment-from-none", + "bad-exception-cause", + "bad-format-character", + "bad-reversed-sequence", + "bad-super-call", + "bad-thread-instantiation", + "catching-non-exception", + "comparison-with-callable", + "deprecated-class", + "dict-iter-missing-items", + "format-combined-specification", + "global-variable-undefined", + "import-error", + "inconsistent-mro", + "inherit-non-class", + "init-is-generator", + "invalid-class-object", + "invalid-enum-extension", + "invalid-envvar-value", + "invalid-format-returned", + "invalid-hash-returned", + "invalid-metaclass", + "invalid-overridden-method", + "invalid-repr-returned", + "invalid-sequence-index", + "invalid-slice-index", + "invalid-slots-object", + "invalid-slots", + "invalid-star-assignment-target", + "invalid-str-returned", + "invalid-unary-operand-type", + "invalid-unicode-codec", + "isinstance-second-argument-not-valid-type", + "method-hidden", + "misplaced-format-function", + "missing-format-argument-key", + "missing-format-attribute", + "missing-kwoa", + "no-member", + "no-value-for-parameter", + "non-iterator-returned", + "non-str-assignment-to-dunder-name", + "nonlocal-and-global", + "not-a-mapping", + "not-an-iterable", + "not-async-context-manager", + "not-callable", + "not-context-manager", + "overridden-final-method", + "raising-bad-type", + "raising-non-exception", + "redundant-keyword-arg", + "relative-beyond-top-level", + "self-cls-assignment", + "signature-differs", + "star-needs-assignment-target", + "subclassed-final-class", + "super-without-brackets", + "too-many-function-args", + "typevar-double-variance", + "typevar-name-mismatch", + "unbalanced-dict-unpacking", + "unbalanced-tuple-unpacking", + "unexpected-keyword-arg", + "unhashable-member", + "unpacking-non-sequence", + "unsubscriptable-object", + "unsupported-assignment-operation", + "unsupported-binary-operation", + "unsupported-delete-operation", + "unsupported-membership-test", + "used-before-assignment", + "using-final-decorator-in-unsupported-version", + "wrong-exception-operation", +] +enable = [ + #"useless-suppression", # temporarily every now and then to clean them up + "use-symbolic-message-instead", +] +per-file-ignores = [ + # redefined-outer-name: Tests reference fixtures in the test function + # use-implicit-booleaness-not-comparison: Tests need to validate that a list + # or a dict is returned + "/tests/:redefined-outer-name,use-implicit-booleaness-not-comparison", +] + +[tool.pylint.REPORTS] +score = false + +[tool.pylint.TYPECHECK] +ignored-classes = [ + "_CountingAttr", # for attrs +] +mixin-class-rgx = ".*[Mm]ix[Ii]n" + +[tool.pylint.FORMAT] +expected-line-ending-format = "LF" + +[tool.pylint.EXCEPTIONS] +overgeneral-exceptions = [ + "builtins.BaseException", + "builtins.Exception", + # "homeassistant.exceptions.HomeAssistantError", # too many issues +] + +[tool.pylint.TYPING] +runtime-typing = false + +[tool.pylint.CODE_STYLE] +max-line-length-suggestions = 72 + +[tool.pytest.ini_options] +testpaths = [ + "tests", +] +norecursedirs = [ + ".git", + "testing_config", +] +log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s" +log_date_format = "%Y-%m-%d %H:%M:%S" +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" + +[tool.ruff] +required-version = ">=0.9.1" + +[tool.ruff.lint] +select = [ + "A001", # Variable {name} is shadowing a Python builtin + "ASYNC210", # Async functions should not call blocking HTTP methods + "ASYNC220", # Async functions should not create subprocesses with blocking methods + "ASYNC221", # Async functions should not run processes with blocking methods + "ASYNC222", # Async functions should not wait on processes with blocking methods + "ASYNC230", # Async functions should not open files with blocking methods like open + "ASYNC251", # Async functions should not call time.sleep + "B002", # Python does not support the unary prefix increment + "B005", # Using .strip() with multi-character strings is misleading + "B007", # Loop control variable {name} not used within loop body + "B014", # Exception handler with duplicate exception + "B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. + "B017", # pytest.raises(BaseException) should be considered evil + "B018", # Found useless attribute access. Either assign it to a variable or remove it. + "B023", # Function definition does not bind loop variable {name} + "B024", # `{name}` is an abstract base class, but it has no abstract methods or properties + "B026", # Star-arg unpacking after a keyword argument is strongly discouraged + "B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)? + "B035", # Dictionary comprehension uses static key + "B904", # Use raise from to specify exception cause + "B905", # zip() without an explicit strict= parameter + "BLE", + "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 + "F541", # f-string without any placeholders + "FLY", # flynt + "FURB", # refurb + "G", # flake8-logging-format + "I", # isort + "INP", # flake8-no-pep420 + "ISC", # flake8-implicit-str-concat + "ICN001", # import concentions; {name} should be imported as {asname} + "LOG", # flake8-logging + "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 + "PERF", # Perflint + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-pathlib + "PYI", # flake8-pyi + "RET", # flake8-return + "RSE", # flake8-raise + "RUF005", # Consider iterable unpacking instead of concatenation + "RUF006", # Store a reference to the return value of asyncio.create_task + "RUF007", # Prefer itertools.pairwise() over zip() when iterating over successive pairs + "RUF008", # Do not use mutable default values for dataclass attributes + "RUF010", # Use explicit conversion flag + "RUF013", # PEP 484 prohibits implicit Optional + "RUF016", # Slice in indexed access to type {value_type} uses type {index_type} instead of an integer + "RUF017", # Avoid quadratic list summation + "RUF018", # Avoid assignment expressions in assert statements + "RUF019", # Unnecessary key check before dictionary access + "RUF020", # {never_like} | T is equivalent to T + "RUF021", # Parenthesize a and b expressions when chaining and and or together, to make the precedence clear + "RUF022", # Sort __all__ + "RUF023", # Sort __slots__ + "RUF024", # Do not pass mutable objects as values to dict.fromkeys + "RUF026", # default_factory is a positional-only argument to defaultdict + "RUF030", # print() call in assert statement is likely unintentional + "RUF032", # Decimal() called with float literal argument + "RUF033", # __post_init__ method with argument defaults + "RUF034", # Useless if-else condition + "RUF100", # Unused `noqa` directive + "RUF101", # noqa directives that use redirected rule codes + "RUF200", # Failed to parse pyproject.toml: {message} + "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 + "SIM", # flake8-simplify + "SLF", # flake8-self + "SLOT", # flake8-slots + "T100", # Trace found: {name} used + "T20", # flake8-print + "TC", # flake8-type-checking + "TID", # Tidy imports + "TRY", # tryceratops + "UP", # pyupgrade + "UP031", # Use format specifiers instead of percent format + "UP032", # Use f-string instead of `format` call + "W", # pycodestyle +] + +ignore = [ + "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 + + "PLC1901", # {existing} can be simplified to {replacement} as an empty string is falsey; too many false positives + "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 + "PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception + "PT018", # Assertion should be broken down into multiple parts + "RUF001", # String contains ambiguous unicode character. + "RUF002", # Docstring contains ambiguous unicode character. + "RUF003", # Comment contains ambiguous unicode character. + "RUF015", # Prefer next(...) over single element slice + "SIM102", # Use a single if statement instead of nested if statements + "SIM103", # Return the condition {condition} directly + "SIM108", # Use ternary operator {contents} instead of if-else-block + "SIM115", # Use context handler for opening files + + # Moving imports into type-checking blocks can mess with pytest.patch() + "TC001", # Move application import {} into a type-checking block + "TC002", # Move third-party import {} into a type-checking block + "TC003", # Move standard library import {} into a type-checking block + + "TRY003", # Avoid specifying long messages outside the exception class + "TRY400", # Use `logging.exception` instead of `logging.error` + # 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", + "Q", + "COM812", + "COM819", + + # Disabled because ruff does not understand type of __all__ generated by a function + "PLE0605" +] + +[tool.ruff.lint.flake8-import-conventions.extend-aliases] +voluptuous = "vol" +"homeassistant.components.air_quality.PLATFORM_SCHEMA" = "AIR_QUALITY_PLATFORM_SCHEMA" +"homeassistant.components.alarm_control_panel.PLATFORM_SCHEMA" = "ALARM_CONTROL_PANEL_PLATFORM_SCHEMA" +"homeassistant.components.binary_sensor.PLATFORM_SCHEMA" = "BINARY_SENSOR_PLATFORM_SCHEMA" +"homeassistant.components.button.PLATFORM_SCHEMA" = "BUTTON_PLATFORM_SCHEMA" +"homeassistant.components.calendar.PLATFORM_SCHEMA" = "CALENDAR_PLATFORM_SCHEMA" +"homeassistant.components.camera.PLATFORM_SCHEMA" = "CAMERA_PLATFORM_SCHEMA" +"homeassistant.components.climate.PLATFORM_SCHEMA" = "CLIMATE_PLATFORM_SCHEMA" +"homeassistant.components.conversation.PLATFORM_SCHEMA" = "CONVERSATION_PLATFORM_SCHEMA" +"homeassistant.components.cover.PLATFORM_SCHEMA" = "COVER_PLATFORM_SCHEMA" +"homeassistant.components.date.PLATFORM_SCHEMA" = "DATE_PLATFORM_SCHEMA" +"homeassistant.components.datetime.PLATFORM_SCHEMA" = "DATETIME_PLATFORM_SCHEMA" +"homeassistant.components.device_tracker.PLATFORM_SCHEMA" = "DEVICE_TRACKER_PLATFORM_SCHEMA" +"homeassistant.components.event.PLATFORM_SCHEMA" = "EVENT_PLATFORM_SCHEMA" +"homeassistant.components.fan.PLATFORM_SCHEMA" = "FAN_PLATFORM_SCHEMA" +"homeassistant.components.geo_location.PLATFORM_SCHEMA" = "GEO_LOCATION_PLATFORM_SCHEMA" +"homeassistant.components.humidifier.PLATFORM_SCHEMA" = "HUMIDIFIER_PLATFORM_SCHEMA" +"homeassistant.components.image.PLATFORM_SCHEMA" = "IMAGE_PLATFORM_SCHEMA" +"homeassistant.components.image_processing.PLATFORM_SCHEMA" = "IMAGE_PROCESSING_PLATFORM_SCHEMA" +"homeassistant.components.lawn_mower.PLATFORM_SCHEMA" = "LAWN_MOWER_PLATFORM_SCHEMA" +"homeassistant.components.light.PLATFORM_SCHEMA" = "LIGHT_PLATFORM_SCHEMA" +"homeassistant.components.lock.PLATFORM_SCHEMA" = "LOCK_PLATFORM_SCHEMA" +"homeassistant.components.media_player.PLATFORM_SCHEMA" = "MEDIA_PLAYER_PLATFORM_SCHEMA" +"homeassistant.components.notify.PLATFORM_SCHEMA" = "NOTIFY_PLATFORM_SCHEMA" +"homeassistant.components.number.PLATFORM_SCHEMA" = "NUMBER_PLATFORM_SCHEMA" +"homeassistant.components.remote.PLATFORM_SCHEMA" = "REMOTE_PLATFORM_SCHEMA" +"homeassistant.components.scene.PLATFORM_SCHEMA" = "SCENE_PLATFORM_SCHEMA" +"homeassistant.components.select.PLATFORM_SCHEMA" = "SELECT_PLATFORM_SCHEMA" +"homeassistant.components.sensor.PLATFORM_SCHEMA" = "SENSOR_PLATFORM_SCHEMA" +"homeassistant.components.siren.PLATFORM_SCHEMA" = "SIREN_PLATFORM_SCHEMA" +"homeassistant.components.stt.PLATFORM_SCHEMA" = "STT_PLATFORM_SCHEMA" +"homeassistant.components.switch.PLATFORM_SCHEMA" = "SWITCH_PLATFORM_SCHEMA" +"homeassistant.components.text.PLATFORM_SCHEMA" = "TEXT_PLATFORM_SCHEMA" +"homeassistant.components.time.PLATFORM_SCHEMA" = "TIME_PLATFORM_SCHEMA" +"homeassistant.components.todo.PLATFORM_SCHEMA" = "TODO_PLATFORM_SCHEMA" +"homeassistant.components.tts.PLATFORM_SCHEMA" = "TTS_PLATFORM_SCHEMA" +"homeassistant.components.vacuum.PLATFORM_SCHEMA" = "VACUUM_PLATFORM_SCHEMA" +"homeassistant.components.valve.PLATFORM_SCHEMA" = "VALVE_PLATFORM_SCHEMA" +"homeassistant.components.update.PLATFORM_SCHEMA" = "UPDATE_PLATFORM_SCHEMA" +"homeassistant.components.wake_word.PLATFORM_SCHEMA" = "WAKE_WORD_PLATFORM_SCHEMA" +"homeassistant.components.water_heater.PLATFORM_SCHEMA" = "WATER_HEATER_PLATFORM_SCHEMA" +"homeassistant.components.weather.PLATFORM_SCHEMA" = "WEATHER_PLATFORM_SCHEMA" +"homeassistant.core.DOMAIN" = "HOMEASSISTANT_DOMAIN" +"homeassistant.helpers.area_registry" = "ar" +"homeassistant.helpers.category_registry" = "cr" +"homeassistant.helpers.config_validation" = "cv" +"homeassistant.helpers.device_registry" = "dr" +"homeassistant.helpers.entity_registry" = "er" +"homeassistant.helpers.floor_registry" = "fr" +"homeassistant.helpers.issue_registry" = "ir" +"homeassistant.helpers.label_registry" = "lr" +"homeassistant.util.color" = "color_util" +"homeassistant.util.dt" = "dt_util" +"homeassistant.util.json" = "json_util" +"homeassistant.util.location" = "location_util" +"homeassistant.util.logging" = "logging_util" +"homeassistant.util.network" = "network_util" +"homeassistant.util.ulid" = "ulid_util" +"homeassistant.util.uuid" = "uuid_util" +"homeassistant.util.yaml" = "yaml_util" + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false + +[tool.ruff.lint.flake8-tidy-imports.banned-api] +"async_timeout".msg = "use asyncio.timeout instead" +"pytz".msg = "use zoneinfo instead" +"tests".msg = "You should not import tests" + +[tool.ruff.lint.isort] +force-sort-within-sections = true +known-first-party = [ + "homeassistant", +] +combine-as-imports = true +split-on-trailing-comma = false + +#[tool.ruff.lint.per-file-ignores] + + + +[tool.ruff.lint.mccabe] +max-complexity = 25 + +[tool.ruff.lint.pydocstyle] +property-decorators = ["propcache.api.cached_property"] From 638f85fa6edd9f3e7252994a6e8a787b4500aba8 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 18:58:20 +0000 Subject: [PATCH 06/34] add initial ruff github actions test --- .github/workflows/ruff.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/workflows/ruff.yaml diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml new file mode 100644 index 0000000..fefe858 --- /dev/null +++ b/.github/workflows/ruff.yaml @@ -0,0 +1,9 @@ +name: Ruff + +on: [ push, pull_request ] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/ruff-action@v3 \ No newline at end of file From 6aca2da88dfeba621e89c675f400558e11ccb852 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 19:03:08 +0000 Subject: [PATCH 07/34] use an similair toml file as ha core --- pyproject.toml | 613 ++++++++++++++++++++++++++++++++++++++++++++++++ ruff.toml | 617 +------------------------------------------------ 2 files changed, 621 insertions(+), 609 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f77e935 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,613 @@ +[build-system] +requires = ["setuptools==75.1.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "hass-victron" +version = "0.5.0" +license = {text = "Apache-2.0"} +description = "Open-source integration for the victron modbus endpoint" +readme = "README.md" +keywords = ["home", "automation","homeassistant","victron"] +requires-python = ">=3.13.0" + +[project.urls] +"Source Code" = "https://github.com/sfstar/hass-victron" +"Bug Reports" = "https://github.com/home-assistant/sfstar/hass-victron/issues" + +[tool.pylint.MAIN] +py-version = "3.13" +# Use a conservative default here; 2 should speed up most setups and not hurt +# any too bad. Override on command line as appropriate. +jobs = 2 +init-hook = """\ + from pathlib import Path; \ + import sys; \ + + from pylint.config import find_default_config_files; \ + + sys.path.append( \ + str(Path(next(find_default_config_files())).parent.joinpath('pylint/plugins')) + ) \ + """ +load-plugins = [ + "pylint.extensions.code_style", + "pylint.extensions.typing", + "pylint_per_file_ignores", +] +persistent = false +extension-pkg-allow-list = [ + "av.audio.stream", + "av.logging", + "av.stream", + "ciso8601", + "orjson", + "cv2", +] +fail-on = [ + "I", +] + +[tool.pylint.BASIC] +class-const-naming-style = "any" + +[tool.pylint."MESSAGES CONTROL"] +# Reasons disabled: +# format - handled by ruff +# locally-disabled - it spams too much +# duplicate-code - unavoidable +# cyclic-import - doesn't test if both import on load +# abstract-class-little-used - prevents from setting right foundation +# unused-argument - generic callbacks and setup methods create a lot of warnings +# too-many-* - are not enforced for the sake of readability +# too-few-* - same as too-many-* +# abstract-method - with intro of async there are always methods missing +# inconsistent-return-statements - doesn't handle raise +# too-many-ancestors - it's too strict. +# wrong-import-order - isort guards this +# possibly-used-before-assignment - too many errors / not necessarily issues +# --- +# Pylint CodeStyle plugin +# consider-using-namedtuple-or-dataclass - too opinionated +# consider-using-assignment-expr - decision to use := better left to devs +disable = [ + "format", + "abstract-method", + "cyclic-import", + "duplicate-code", + "inconsistent-return-statements", + "locally-disabled", + "not-context-manager", + "too-few-public-methods", + "too-many-ancestors", + "too-many-arguments", + "too-many-instance-attributes", + "too-many-lines", + "too-many-locals", + "too-many-public-methods", + "too-many-boolean-expressions", + "too-many-positional-arguments", + "wrong-import-order", + "consider-using-namedtuple-or-dataclass", + "consider-using-assignment-expr", + "possibly-used-before-assignment", + + # Handled by ruff + # Ref: + "await-outside-async", # PLE1142 + "bad-str-strip-call", # PLE1310 + "bad-string-format-type", # PLE1307 + "bidirectional-unicode", # PLE2502 + "continue-in-finally", # PLE0116 + "duplicate-bases", # PLE0241 + "misplaced-bare-raise", # PLE0704 + "format-needs-mapping", # F502 + "function-redefined", # F811 + # Needed because ruff does not understand type of __all__ generated by a function + # "invalid-all-format", # PLE0605 + "invalid-all-object", # PLE0604 + "invalid-character-backspace", # PLE2510 + "invalid-character-esc", # PLE2513 + "invalid-character-nul", # PLE2514 + "invalid-character-sub", # PLE2512 + "invalid-character-zero-width-space", # PLE2515 + "logging-too-few-args", # PLE1206 + "logging-too-many-args", # PLE1205 + "missing-format-string-key", # F524 + "mixed-format-string", # F506 + "no-method-argument", # N805 + "no-self-argument", # N805 + "nonexistent-operator", # B002 + "nonlocal-without-binding", # PLE0117 + "not-in-loop", # F701, F702 + "notimplemented-raised", # F901 + "return-in-init", # PLE0101 + "return-outside-function", # F706 + "syntax-error", # E999 + "too-few-format-args", # F524 + "too-many-format-args", # F522 + "too-many-star-expressions", # F622 + "truncated-format-string", # F501 + "undefined-all-variable", # F822 + "undefined-variable", # F821 + "used-prior-global-declaration", # PLE0118 + "yield-inside-async-function", # PLE1700 + "yield-outside-function", # F704 + "anomalous-backslash-in-string", # W605 + "assert-on-string-literal", # PLW0129 + "assert-on-tuple", # F631 + "bad-format-string", # W1302, F + "bad-format-string-key", # W1300, F + "bare-except", # E722 + "binary-op-exception", # PLW0711 + "cell-var-from-loop", # B023 + # "dangerous-default-value", # B006, ruff catches new occurrences, needs more work + "duplicate-except", # B014 + "duplicate-key", # F601 + "duplicate-string-formatting-argument", # F + "duplicate-value", # F + "eval-used", # S307 + "exec-used", # S102 + "expression-not-assigned", # B018 + "f-string-without-interpolation", # F541 + "forgotten-debug-statement", # T100 + "format-string-without-interpolation", # F + # "global-statement", # PLW0603, ruff catches new occurrences, needs more work + "global-variable-not-assigned", # PLW0602 + "implicit-str-concat", # ISC001 + "import-self", # PLW0406 + "inconsistent-quotes", # Q000 + "invalid-envvar-default", # PLW1508 + "keyword-arg-before-vararg", # B026 + "logging-format-interpolation", # G + "logging-fstring-interpolation", # G + "logging-not-lazy", # G + "misplaced-future", # F404 + "named-expr-without-context", # PLW0131 + "nested-min-max", # PLW3301 + "pointless-statement", # B018 + "raise-missing-from", # B904 + "redefined-builtin", # A001 + "try-except-raise", # TRY302 + "unused-argument", # ARG001, we don't use it + "unused-format-string-argument", #F507 + "unused-format-string-key", # F504 + "unused-import", # F401 + "unused-variable", # F841 + "useless-else-on-loop", # PLW0120 + "wildcard-import", # F403 + "bad-classmethod-argument", # N804 + "consider-iterating-dictionary", # SIM118 + "empty-docstring", # D419 + "invalid-name", # N815 + "line-too-long", # E501, disabled globally + "missing-class-docstring", # D101 + "missing-final-newline", # W292 + "missing-function-docstring", # D103 + "missing-module-docstring", # D100 + "multiple-imports", #E401 + "singleton-comparison", # E711, E712 + "subprocess-run-check", # PLW1510 + "superfluous-parens", # UP034 + "ungrouped-imports", # I001 + "unidiomatic-typecheck", # E721 + "unnecessary-direct-lambda-call", # PLC3002 + "unnecessary-lambda-assignment", # PLC3001 + "unnecessary-pass", # PIE790 + "unneeded-not", # SIM208 + "useless-import-alias", # PLC0414 + "wrong-import-order", # I001 + "wrong-import-position", # E402 + "comparison-of-constants", # PLR0133 + "comparison-with-itself", # PLR0124 + "consider-alternative-union-syntax", # UP007 + "consider-merging-isinstance", # PLR1701 + "consider-using-alias", # UP006 + "consider-using-dict-comprehension", # C402 + "consider-using-generator", # C417 + "consider-using-get", # SIM401 + "consider-using-set-comprehension", # C401 + "consider-using-sys-exit", # PLR1722 + "consider-using-ternary", # SIM108 + "literal-comparison", # F632 + "property-with-parameters", # PLR0206 + "super-with-arguments", # UP008 + "too-many-branches", # PLR0912 + "too-many-return-statements", # PLR0911 + "too-many-statements", # PLR0915 + "trailing-comma-tuple", # COM818 + "unnecessary-comprehension", # C416 + "use-a-generator", # C417 + "use-dict-literal", # C406 + "use-list-literal", # C405 + "useless-object-inheritance", # UP004 + "useless-return", # PLR1711 + "no-else-break", # RET508 + "no-else-continue", # RET507 + "no-else-raise", # RET506 + "no-else-return", # RET505 + "broad-except", # BLE001 + "protected-access", # SLF001 + "broad-exception-raised", # TRY002 + "consider-using-f-string", # PLC0209 + # "no-self-use", # PLR6301 # Optional plugin, not enabled + + # Handled by mypy + # Ref: + "abstract-class-instantiated", + "arguments-differ", + "assigning-non-slot", + "assignment-from-no-return", + "assignment-from-none", + "bad-exception-cause", + "bad-format-character", + "bad-reversed-sequence", + "bad-super-call", + "bad-thread-instantiation", + "catching-non-exception", + "comparison-with-callable", + "deprecated-class", + "dict-iter-missing-items", + "format-combined-specification", + "global-variable-undefined", + "import-error", + "inconsistent-mro", + "inherit-non-class", + "init-is-generator", + "invalid-class-object", + "invalid-enum-extension", + "invalid-envvar-value", + "invalid-format-returned", + "invalid-hash-returned", + "invalid-metaclass", + "invalid-overridden-method", + "invalid-repr-returned", + "invalid-sequence-index", + "invalid-slice-index", + "invalid-slots-object", + "invalid-slots", + "invalid-star-assignment-target", + "invalid-str-returned", + "invalid-unary-operand-type", + "invalid-unicode-codec", + "isinstance-second-argument-not-valid-type", + "method-hidden", + "misplaced-format-function", + "missing-format-argument-key", + "missing-format-attribute", + "missing-kwoa", + "no-member", + "no-value-for-parameter", + "non-iterator-returned", + "non-str-assignment-to-dunder-name", + "nonlocal-and-global", + "not-a-mapping", + "not-an-iterable", + "not-async-context-manager", + "not-callable", + "not-context-manager", + "overridden-final-method", + "raising-bad-type", + "raising-non-exception", + "redundant-keyword-arg", + "relative-beyond-top-level", + "self-cls-assignment", + "signature-differs", + "star-needs-assignment-target", + "subclassed-final-class", + "super-without-brackets", + "too-many-function-args", + "typevar-double-variance", + "typevar-name-mismatch", + "unbalanced-dict-unpacking", + "unbalanced-tuple-unpacking", + "unexpected-keyword-arg", + "unhashable-member", + "unpacking-non-sequence", + "unsubscriptable-object", + "unsupported-assignment-operation", + "unsupported-binary-operation", + "unsupported-delete-operation", + "unsupported-membership-test", + "used-before-assignment", + "using-final-decorator-in-unsupported-version", + "wrong-exception-operation", +] +enable = [ + #"useless-suppression", # temporarily every now and then to clean them up + "use-symbolic-message-instead", +] +per-file-ignores = [ + # redefined-outer-name: Tests reference fixtures in the test function + # use-implicit-booleaness-not-comparison: Tests need to validate that a list + # or a dict is returned + "/tests/:redefined-outer-name,use-implicit-booleaness-not-comparison", +] + +[tool.pylint.REPORTS] +score = false + +[tool.pylint.TYPECHECK] +ignored-classes = [ + "_CountingAttr", # for attrs +] +mixin-class-rgx = ".*[Mm]ix[Ii]n" + +[tool.pylint.FORMAT] +expected-line-ending-format = "LF" + +[tool.pylint.EXCEPTIONS] +overgeneral-exceptions = [ + "builtins.BaseException", + "builtins.Exception", + # "homeassistant.exceptions.HomeAssistantError", # too many issues +] + +[tool.pylint.TYPING] +runtime-typing = false + +[tool.pylint.CODE_STYLE] +max-line-length-suggestions = 72 + +[tool.pytest.ini_options] +testpaths = [ + "tests", +] +norecursedirs = [ + ".git", + "testing_config", +] +log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s" +log_date_format = "%Y-%m-%d %H:%M:%S" +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" + +[tool.ruff] +required-version = ">=0.9.1" + +[tool.ruff.lint] +select = [ + "A001", # Variable {name} is shadowing a Python builtin + "ASYNC210", # Async functions should not call blocking HTTP methods + "ASYNC220", # Async functions should not create subprocesses with blocking methods + "ASYNC221", # Async functions should not run processes with blocking methods + "ASYNC222", # Async functions should not wait on processes with blocking methods + "ASYNC230", # Async functions should not open files with blocking methods like open + "ASYNC251", # Async functions should not call time.sleep + "B002", # Python does not support the unary prefix increment + "B005", # Using .strip() with multi-character strings is misleading + "B007", # Loop control variable {name} not used within loop body + "B014", # Exception handler with duplicate exception + "B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. + "B017", # pytest.raises(BaseException) should be considered evil + "B018", # Found useless attribute access. Either assign it to a variable or remove it. + "B023", # Function definition does not bind loop variable {name} + "B024", # `{name}` is an abstract base class, but it has no abstract methods or properties + "B026", # Star-arg unpacking after a keyword argument is strongly discouraged + "B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)? + "B035", # Dictionary comprehension uses static key + "B904", # Use raise from to specify exception cause + "B905", # zip() without an explicit strict= parameter + "BLE", + "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 + "F541", # f-string without any placeholders + "FLY", # flynt + "FURB", # refurb + "G", # flake8-logging-format + "I", # isort + "INP", # flake8-no-pep420 + "ISC", # flake8-implicit-str-concat + "ICN001", # import concentions; {name} should be imported as {asname} + "LOG", # flake8-logging + "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 + "PERF", # Perflint + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-pathlib + "PYI", # flake8-pyi + "RET", # flake8-return + "RSE", # flake8-raise + "RUF005", # Consider iterable unpacking instead of concatenation + "RUF006", # Store a reference to the return value of asyncio.create_task + "RUF007", # Prefer itertools.pairwise() over zip() when iterating over successive pairs + "RUF008", # Do not use mutable default values for dataclass attributes + "RUF010", # Use explicit conversion flag + "RUF013", # PEP 484 prohibits implicit Optional + "RUF016", # Slice in indexed access to type {value_type} uses type {index_type} instead of an integer + "RUF017", # Avoid quadratic list summation + "RUF018", # Avoid assignment expressions in assert statements + "RUF019", # Unnecessary key check before dictionary access + "RUF020", # {never_like} | T is equivalent to T + "RUF021", # Parenthesize a and b expressions when chaining and and or together, to make the precedence clear + "RUF022", # Sort __all__ + "RUF023", # Sort __slots__ + "RUF024", # Do not pass mutable objects as values to dict.fromkeys + "RUF026", # default_factory is a positional-only argument to defaultdict + "RUF030", # print() call in assert statement is likely unintentional + "RUF032", # Decimal() called with float literal argument + "RUF033", # __post_init__ method with argument defaults + "RUF034", # Useless if-else condition + "RUF100", # Unused `noqa` directive + "RUF101", # noqa directives that use redirected rule codes + "RUF200", # Failed to parse pyproject.toml: {message} + "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 + "SIM", # flake8-simplify + "SLF", # flake8-self + "SLOT", # flake8-slots + "T100", # Trace found: {name} used + "T20", # flake8-print + "TC", # flake8-type-checking + "TID", # Tidy imports + "TRY", # tryceratops + "UP", # pyupgrade + "UP031", # Use format specifiers instead of percent format + "UP032", # Use f-string instead of `format` call + "W", # pycodestyle +] + +ignore = [ + "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 + + "PLC1901", # {existing} can be simplified to {replacement} as an empty string is falsey; too many false positives + "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 + "PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception + "PT018", # Assertion should be broken down into multiple parts + "RUF001", # String contains ambiguous unicode character. + "RUF002", # Docstring contains ambiguous unicode character. + "RUF003", # Comment contains ambiguous unicode character. + "RUF015", # Prefer next(...) over single element slice + "SIM102", # Use a single if statement instead of nested if statements + "SIM103", # Return the condition {condition} directly + "SIM108", # Use ternary operator {contents} instead of if-else-block + "SIM115", # Use context handler for opening files + + # Moving imports into type-checking blocks can mess with pytest.patch() + "TC001", # Move application import {} into a type-checking block + "TC002", # Move third-party import {} into a type-checking block + "TC003", # Move standard library import {} into a type-checking block + + "TRY003", # Avoid specifying long messages outside the exception class + "TRY400", # Use `logging.exception` instead of `logging.error` + # 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", + "Q", + "COM812", + "COM819", + + # Disabled because ruff does not understand type of __all__ generated by a function + "PLE0605" +] + +[tool.ruff.lint.flake8-import-conventions.extend-aliases] +voluptuous = "vol" +"homeassistant.components.air_quality.PLATFORM_SCHEMA" = "AIR_QUALITY_PLATFORM_SCHEMA" +"homeassistant.components.alarm_control_panel.PLATFORM_SCHEMA" = "ALARM_CONTROL_PANEL_PLATFORM_SCHEMA" +"homeassistant.components.binary_sensor.PLATFORM_SCHEMA" = "BINARY_SENSOR_PLATFORM_SCHEMA" +"homeassistant.components.button.PLATFORM_SCHEMA" = "BUTTON_PLATFORM_SCHEMA" +"homeassistant.components.calendar.PLATFORM_SCHEMA" = "CALENDAR_PLATFORM_SCHEMA" +"homeassistant.components.camera.PLATFORM_SCHEMA" = "CAMERA_PLATFORM_SCHEMA" +"homeassistant.components.climate.PLATFORM_SCHEMA" = "CLIMATE_PLATFORM_SCHEMA" +"homeassistant.components.conversation.PLATFORM_SCHEMA" = "CONVERSATION_PLATFORM_SCHEMA" +"homeassistant.components.cover.PLATFORM_SCHEMA" = "COVER_PLATFORM_SCHEMA" +"homeassistant.components.date.PLATFORM_SCHEMA" = "DATE_PLATFORM_SCHEMA" +"homeassistant.components.datetime.PLATFORM_SCHEMA" = "DATETIME_PLATFORM_SCHEMA" +"homeassistant.components.device_tracker.PLATFORM_SCHEMA" = "DEVICE_TRACKER_PLATFORM_SCHEMA" +"homeassistant.components.event.PLATFORM_SCHEMA" = "EVENT_PLATFORM_SCHEMA" +"homeassistant.components.fan.PLATFORM_SCHEMA" = "FAN_PLATFORM_SCHEMA" +"homeassistant.components.geo_location.PLATFORM_SCHEMA" = "GEO_LOCATION_PLATFORM_SCHEMA" +"homeassistant.components.humidifier.PLATFORM_SCHEMA" = "HUMIDIFIER_PLATFORM_SCHEMA" +"homeassistant.components.image.PLATFORM_SCHEMA" = "IMAGE_PLATFORM_SCHEMA" +"homeassistant.components.image_processing.PLATFORM_SCHEMA" = "IMAGE_PROCESSING_PLATFORM_SCHEMA" +"homeassistant.components.lawn_mower.PLATFORM_SCHEMA" = "LAWN_MOWER_PLATFORM_SCHEMA" +"homeassistant.components.light.PLATFORM_SCHEMA" = "LIGHT_PLATFORM_SCHEMA" +"homeassistant.components.lock.PLATFORM_SCHEMA" = "LOCK_PLATFORM_SCHEMA" +"homeassistant.components.media_player.PLATFORM_SCHEMA" = "MEDIA_PLAYER_PLATFORM_SCHEMA" +"homeassistant.components.notify.PLATFORM_SCHEMA" = "NOTIFY_PLATFORM_SCHEMA" +"homeassistant.components.number.PLATFORM_SCHEMA" = "NUMBER_PLATFORM_SCHEMA" +"homeassistant.components.remote.PLATFORM_SCHEMA" = "REMOTE_PLATFORM_SCHEMA" +"homeassistant.components.scene.PLATFORM_SCHEMA" = "SCENE_PLATFORM_SCHEMA" +"homeassistant.components.select.PLATFORM_SCHEMA" = "SELECT_PLATFORM_SCHEMA" +"homeassistant.components.sensor.PLATFORM_SCHEMA" = "SENSOR_PLATFORM_SCHEMA" +"homeassistant.components.siren.PLATFORM_SCHEMA" = "SIREN_PLATFORM_SCHEMA" +"homeassistant.components.stt.PLATFORM_SCHEMA" = "STT_PLATFORM_SCHEMA" +"homeassistant.components.switch.PLATFORM_SCHEMA" = "SWITCH_PLATFORM_SCHEMA" +"homeassistant.components.text.PLATFORM_SCHEMA" = "TEXT_PLATFORM_SCHEMA" +"homeassistant.components.time.PLATFORM_SCHEMA" = "TIME_PLATFORM_SCHEMA" +"homeassistant.components.todo.PLATFORM_SCHEMA" = "TODO_PLATFORM_SCHEMA" +"homeassistant.components.tts.PLATFORM_SCHEMA" = "TTS_PLATFORM_SCHEMA" +"homeassistant.components.vacuum.PLATFORM_SCHEMA" = "VACUUM_PLATFORM_SCHEMA" +"homeassistant.components.valve.PLATFORM_SCHEMA" = "VALVE_PLATFORM_SCHEMA" +"homeassistant.components.update.PLATFORM_SCHEMA" = "UPDATE_PLATFORM_SCHEMA" +"homeassistant.components.wake_word.PLATFORM_SCHEMA" = "WAKE_WORD_PLATFORM_SCHEMA" +"homeassistant.components.water_heater.PLATFORM_SCHEMA" = "WATER_HEATER_PLATFORM_SCHEMA" +"homeassistant.components.weather.PLATFORM_SCHEMA" = "WEATHER_PLATFORM_SCHEMA" +"homeassistant.core.DOMAIN" = "HOMEASSISTANT_DOMAIN" +"homeassistant.helpers.area_registry" = "ar" +"homeassistant.helpers.category_registry" = "cr" +"homeassistant.helpers.config_validation" = "cv" +"homeassistant.helpers.device_registry" = "dr" +"homeassistant.helpers.entity_registry" = "er" +"homeassistant.helpers.floor_registry" = "fr" +"homeassistant.helpers.issue_registry" = "ir" +"homeassistant.helpers.label_registry" = "lr" +"homeassistant.util.color" = "color_util" +"homeassistant.util.dt" = "dt_util" +"homeassistant.util.json" = "json_util" +"homeassistant.util.location" = "location_util" +"homeassistant.util.logging" = "logging_util" +"homeassistant.util.network" = "network_util" +"homeassistant.util.ulid" = "ulid_util" +"homeassistant.util.uuid" = "uuid_util" +"homeassistant.util.yaml" = "yaml_util" + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false + +[tool.ruff.lint.flake8-tidy-imports.banned-api] +"async_timeout".msg = "use asyncio.timeout instead" +"pytz".msg = "use zoneinfo instead" +"tests".msg = "You should not import tests" + +[tool.ruff.lint.isort] +force-sort-within-sections = true +known-first-party = [ + "homeassistant", +] +combine-as-imports = true +split-on-trailing-comma = false + +#[tool.ruff.lint.per-file-ignores] + + + +[tool.ruff.lint.mccabe] +max-complexity = 25 + +[tool.ruff.lint.pydocstyle] +property-decorators = ["propcache.api.cached_property"] diff --git a/ruff.toml b/ruff.toml index f77e935..4e1a038 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,613 +1,12 @@ -[build-system] -requires = ["setuptools==75.1.0"] -build-backend = "setuptools.build_meta" +# This extend our general Ruff rules specifically for tests +extend = "../pyproject.toml" -[project] -name = "hass-victron" -version = "0.5.0" -license = {text = "Apache-2.0"} -description = "Open-source integration for the victron modbus endpoint" -readme = "README.md" -keywords = ["home", "automation","homeassistant","victron"] -requires-python = ">=3.13.0" - -[project.urls] -"Source Code" = "https://github.com/sfstar/hass-victron" -"Bug Reports" = "https://github.com/home-assistant/sfstar/hass-victron/issues" - -[tool.pylint.MAIN] -py-version = "3.13" -# Use a conservative default here; 2 should speed up most setups and not hurt -# any too bad. Override on command line as appropriate. -jobs = 2 -init-hook = """\ - from pathlib import Path; \ - import sys; \ - - from pylint.config import find_default_config_files; \ - - sys.path.append( \ - str(Path(next(find_default_config_files())).parent.joinpath('pylint/plugins')) - ) \ - """ -load-plugins = [ - "pylint.extensions.code_style", - "pylint.extensions.typing", - "pylint_per_file_ignores", -] -persistent = false -extension-pkg-allow-list = [ - "av.audio.stream", - "av.logging", - "av.stream", - "ciso8601", - "orjson", - "cv2", -] -fail-on = [ - "I", -] - -[tool.pylint.BASIC] -class-const-naming-style = "any" - -[tool.pylint."MESSAGES CONTROL"] -# Reasons disabled: -# format - handled by ruff -# locally-disabled - it spams too much -# duplicate-code - unavoidable -# cyclic-import - doesn't test if both import on load -# abstract-class-little-used - prevents from setting right foundation -# unused-argument - generic callbacks and setup methods create a lot of warnings -# too-many-* - are not enforced for the sake of readability -# too-few-* - same as too-many-* -# abstract-method - with intro of async there are always methods missing -# inconsistent-return-statements - doesn't handle raise -# too-many-ancestors - it's too strict. -# wrong-import-order - isort guards this -# possibly-used-before-assignment - too many errors / not necessarily issues -# --- -# Pylint CodeStyle plugin -# consider-using-namedtuple-or-dataclass - too opinionated -# consider-using-assignment-expr - decision to use := better left to devs -disable = [ - "format", - "abstract-method", - "cyclic-import", - "duplicate-code", - "inconsistent-return-statements", - "locally-disabled", - "not-context-manager", - "too-few-public-methods", - "too-many-ancestors", - "too-many-arguments", - "too-many-instance-attributes", - "too-many-lines", - "too-many-locals", - "too-many-public-methods", - "too-many-boolean-expressions", - "too-many-positional-arguments", - "wrong-import-order", - "consider-using-namedtuple-or-dataclass", - "consider-using-assignment-expr", - "possibly-used-before-assignment", - - # Handled by ruff - # Ref: - "await-outside-async", # PLE1142 - "bad-str-strip-call", # PLE1310 - "bad-string-format-type", # PLE1307 - "bidirectional-unicode", # PLE2502 - "continue-in-finally", # PLE0116 - "duplicate-bases", # PLE0241 - "misplaced-bare-raise", # PLE0704 - "format-needs-mapping", # F502 - "function-redefined", # F811 - # Needed because ruff does not understand type of __all__ generated by a function - # "invalid-all-format", # PLE0605 - "invalid-all-object", # PLE0604 - "invalid-character-backspace", # PLE2510 - "invalid-character-esc", # PLE2513 - "invalid-character-nul", # PLE2514 - "invalid-character-sub", # PLE2512 - "invalid-character-zero-width-space", # PLE2515 - "logging-too-few-args", # PLE1206 - "logging-too-many-args", # PLE1205 - "missing-format-string-key", # F524 - "mixed-format-string", # F506 - "no-method-argument", # N805 - "no-self-argument", # N805 - "nonexistent-operator", # B002 - "nonlocal-without-binding", # PLE0117 - "not-in-loop", # F701, F702 - "notimplemented-raised", # F901 - "return-in-init", # PLE0101 - "return-outside-function", # F706 - "syntax-error", # E999 - "too-few-format-args", # F524 - "too-many-format-args", # F522 - "too-many-star-expressions", # F622 - "truncated-format-string", # F501 - "undefined-all-variable", # F822 - "undefined-variable", # F821 - "used-prior-global-declaration", # PLE0118 - "yield-inside-async-function", # PLE1700 - "yield-outside-function", # F704 - "anomalous-backslash-in-string", # W605 - "assert-on-string-literal", # PLW0129 - "assert-on-tuple", # F631 - "bad-format-string", # W1302, F - "bad-format-string-key", # W1300, F - "bare-except", # E722 - "binary-op-exception", # PLW0711 - "cell-var-from-loop", # B023 - # "dangerous-default-value", # B006, ruff catches new occurrences, needs more work - "duplicate-except", # B014 - "duplicate-key", # F601 - "duplicate-string-formatting-argument", # F - "duplicate-value", # F - "eval-used", # S307 - "exec-used", # S102 - "expression-not-assigned", # B018 - "f-string-without-interpolation", # F541 - "forgotten-debug-statement", # T100 - "format-string-without-interpolation", # F - # "global-statement", # PLW0603, ruff catches new occurrences, needs more work - "global-variable-not-assigned", # PLW0602 - "implicit-str-concat", # ISC001 - "import-self", # PLW0406 - "inconsistent-quotes", # Q000 - "invalid-envvar-default", # PLW1508 - "keyword-arg-before-vararg", # B026 - "logging-format-interpolation", # G - "logging-fstring-interpolation", # G - "logging-not-lazy", # G - "misplaced-future", # F404 - "named-expr-without-context", # PLW0131 - "nested-min-max", # PLW3301 - "pointless-statement", # B018 - "raise-missing-from", # B904 - "redefined-builtin", # A001 - "try-except-raise", # TRY302 - "unused-argument", # ARG001, we don't use it - "unused-format-string-argument", #F507 - "unused-format-string-key", # F504 - "unused-import", # F401 - "unused-variable", # F841 - "useless-else-on-loop", # PLW0120 - "wildcard-import", # F403 - "bad-classmethod-argument", # N804 - "consider-iterating-dictionary", # SIM118 - "empty-docstring", # D419 - "invalid-name", # N815 - "line-too-long", # E501, disabled globally - "missing-class-docstring", # D101 - "missing-final-newline", # W292 - "missing-function-docstring", # D103 - "missing-module-docstring", # D100 - "multiple-imports", #E401 - "singleton-comparison", # E711, E712 - "subprocess-run-check", # PLW1510 - "superfluous-parens", # UP034 - "ungrouped-imports", # I001 - "unidiomatic-typecheck", # E721 - "unnecessary-direct-lambda-call", # PLC3002 - "unnecessary-lambda-assignment", # PLC3001 - "unnecessary-pass", # PIE790 - "unneeded-not", # SIM208 - "useless-import-alias", # PLC0414 - "wrong-import-order", # I001 - "wrong-import-position", # E402 - "comparison-of-constants", # PLR0133 - "comparison-with-itself", # PLR0124 - "consider-alternative-union-syntax", # UP007 - "consider-merging-isinstance", # PLR1701 - "consider-using-alias", # UP006 - "consider-using-dict-comprehension", # C402 - "consider-using-generator", # C417 - "consider-using-get", # SIM401 - "consider-using-set-comprehension", # C401 - "consider-using-sys-exit", # PLR1722 - "consider-using-ternary", # SIM108 - "literal-comparison", # F632 - "property-with-parameters", # PLR0206 - "super-with-arguments", # UP008 - "too-many-branches", # PLR0912 - "too-many-return-statements", # PLR0911 - "too-many-statements", # PLR0915 - "trailing-comma-tuple", # COM818 - "unnecessary-comprehension", # C416 - "use-a-generator", # C417 - "use-dict-literal", # C406 - "use-list-literal", # C405 - "useless-object-inheritance", # UP004 - "useless-return", # PLR1711 - "no-else-break", # RET508 - "no-else-continue", # RET507 - "no-else-raise", # RET506 - "no-else-return", # RET505 - "broad-except", # BLE001 - "protected-access", # SLF001 - "broad-exception-raised", # TRY002 - "consider-using-f-string", # PLC0209 - # "no-self-use", # PLR6301 # Optional plugin, not enabled - - # Handled by mypy - # Ref: - "abstract-class-instantiated", - "arguments-differ", - "assigning-non-slot", - "assignment-from-no-return", - "assignment-from-none", - "bad-exception-cause", - "bad-format-character", - "bad-reversed-sequence", - "bad-super-call", - "bad-thread-instantiation", - "catching-non-exception", - "comparison-with-callable", - "deprecated-class", - "dict-iter-missing-items", - "format-combined-specification", - "global-variable-undefined", - "import-error", - "inconsistent-mro", - "inherit-non-class", - "init-is-generator", - "invalid-class-object", - "invalid-enum-extension", - "invalid-envvar-value", - "invalid-format-returned", - "invalid-hash-returned", - "invalid-metaclass", - "invalid-overridden-method", - "invalid-repr-returned", - "invalid-sequence-index", - "invalid-slice-index", - "invalid-slots-object", - "invalid-slots", - "invalid-star-assignment-target", - "invalid-str-returned", - "invalid-unary-operand-type", - "invalid-unicode-codec", - "isinstance-second-argument-not-valid-type", - "method-hidden", - "misplaced-format-function", - "missing-format-argument-key", - "missing-format-attribute", - "missing-kwoa", - "no-member", - "no-value-for-parameter", - "non-iterator-returned", - "non-str-assignment-to-dunder-name", - "nonlocal-and-global", - "not-a-mapping", - "not-an-iterable", - "not-async-context-manager", - "not-callable", - "not-context-manager", - "overridden-final-method", - "raising-bad-type", - "raising-non-exception", - "redundant-keyword-arg", - "relative-beyond-top-level", - "self-cls-assignment", - "signature-differs", - "star-needs-assignment-target", - "subclassed-final-class", - "super-without-brackets", - "too-many-function-args", - "typevar-double-variance", - "typevar-name-mismatch", - "unbalanced-dict-unpacking", - "unbalanced-tuple-unpacking", - "unexpected-keyword-arg", - "unhashable-member", - "unpacking-non-sequence", - "unsubscriptable-object", - "unsupported-assignment-operation", - "unsupported-binary-operation", - "unsupported-delete-operation", - "unsupported-membership-test", - "used-before-assignment", - "using-final-decorator-in-unsupported-version", - "wrong-exception-operation", -] -enable = [ - #"useless-suppression", # temporarily every now and then to clean them up - "use-symbolic-message-instead", -] -per-file-ignores = [ - # redefined-outer-name: Tests reference fixtures in the test function - # use-implicit-booleaness-not-comparison: Tests need to validate that a list - # or a dict is returned - "/tests/:redefined-outer-name,use-implicit-booleaness-not-comparison", -] - -[tool.pylint.REPORTS] -score = false - -[tool.pylint.TYPECHECK] -ignored-classes = [ - "_CountingAttr", # for attrs -] -mixin-class-rgx = ".*[Mm]ix[Ii]n" - -[tool.pylint.FORMAT] -expected-line-ending-format = "LF" - -[tool.pylint.EXCEPTIONS] -overgeneral-exceptions = [ - "builtins.BaseException", - "builtins.Exception", - # "homeassistant.exceptions.HomeAssistantError", # too many issues -] - -[tool.pylint.TYPING] -runtime-typing = false - -[tool.pylint.CODE_STYLE] -max-line-length-suggestions = 72 - -[tool.pytest.ini_options] -testpaths = [ - "tests", -] -norecursedirs = [ - ".git", - "testing_config", -] -log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s" -log_date_format = "%Y-%m-%d %H:%M:%S" -asyncio_mode = "auto" -asyncio_default_fixture_loop_scope = "function" - -[tool.ruff] -required-version = ">=0.9.1" - -[tool.ruff.lint] -select = [ - "A001", # Variable {name} is shadowing a Python builtin - "ASYNC210", # Async functions should not call blocking HTTP methods - "ASYNC220", # Async functions should not create subprocesses with blocking methods - "ASYNC221", # Async functions should not run processes with blocking methods - "ASYNC222", # Async functions should not wait on processes with blocking methods - "ASYNC230", # Async functions should not open files with blocking methods like open - "ASYNC251", # Async functions should not call time.sleep - "B002", # Python does not support the unary prefix increment - "B005", # Using .strip() with multi-character strings is misleading - "B007", # Loop control variable {name} not used within loop body - "B014", # Exception handler with duplicate exception - "B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. - "B017", # pytest.raises(BaseException) should be considered evil - "B018", # Found useless attribute access. Either assign it to a variable or remove it. - "B023", # Function definition does not bind loop variable {name} - "B024", # `{name}` is an abstract base class, but it has no abstract methods or properties - "B026", # Star-arg unpacking after a keyword argument is strongly discouraged - "B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)? - "B035", # Dictionary comprehension uses static key - "B904", # Use raise from to specify exception cause - "B905", # zip() without an explicit strict= parameter - "BLE", - "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 - "F541", # f-string without any placeholders - "FLY", # flynt - "FURB", # refurb - "G", # flake8-logging-format - "I", # isort - "INP", # flake8-no-pep420 - "ISC", # flake8-implicit-str-concat - "ICN001", # import concentions; {name} should be imported as {asname} - "LOG", # flake8-logging - "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 - "PERF", # Perflint - "PGH", # pygrep-hooks - "PIE", # flake8-pie - "PL", # pylint - "PT", # flake8-pytest-style - "PTH", # flake8-pathlib - "PYI", # flake8-pyi - "RET", # flake8-return - "RSE", # flake8-raise - "RUF005", # Consider iterable unpacking instead of concatenation - "RUF006", # Store a reference to the return value of asyncio.create_task - "RUF007", # Prefer itertools.pairwise() over zip() when iterating over successive pairs - "RUF008", # Do not use mutable default values for dataclass attributes - "RUF010", # Use explicit conversion flag - "RUF013", # PEP 484 prohibits implicit Optional - "RUF016", # Slice in indexed access to type {value_type} uses type {index_type} instead of an integer - "RUF017", # Avoid quadratic list summation - "RUF018", # Avoid assignment expressions in assert statements - "RUF019", # Unnecessary key check before dictionary access - "RUF020", # {never_like} | T is equivalent to T - "RUF021", # Parenthesize a and b expressions when chaining and and or together, to make the precedence clear - "RUF022", # Sort __all__ - "RUF023", # Sort __slots__ - "RUF024", # Do not pass mutable objects as values to dict.fromkeys - "RUF026", # default_factory is a positional-only argument to defaultdict - "RUF030", # print() call in assert statement is likely unintentional - "RUF032", # Decimal() called with float literal argument - "RUF033", # __post_init__ method with argument defaults - "RUF034", # Useless if-else condition - "RUF100", # Unused `noqa` directive - "RUF101", # noqa directives that use redirected rule codes - "RUF200", # Failed to parse pyproject.toml: {message} - "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 - "SIM", # flake8-simplify - "SLF", # flake8-self - "SLOT", # flake8-slots - "T100", # Trace found: {name} used - "T20", # flake8-print - "TC", # flake8-type-checking - "TID", # Tidy imports - "TRY", # tryceratops - "UP", # pyupgrade - "UP031", # Use format specifiers instead of percent format - "UP032", # Use f-string instead of `format` call - "W", # pycodestyle +[lint] +extend-ignore = [ + "INP001", # File is part of an implicit namespace package. Add an `__init__.py`. ] -ignore = [ - "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 - - "PLC1901", # {existing} can be simplified to {replacement} as an empty string is falsey; too many false positives - "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 - "PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception - "PT018", # Assertion should be broken down into multiple parts - "RUF001", # String contains ambiguous unicode character. - "RUF002", # Docstring contains ambiguous unicode character. - "RUF003", # Comment contains ambiguous unicode character. - "RUF015", # Prefer next(...) over single element slice - "SIM102", # Use a single if statement instead of nested if statements - "SIM103", # Return the condition {condition} directly - "SIM108", # Use ternary operator {contents} instead of if-else-block - "SIM115", # Use context handler for opening files - - # Moving imports into type-checking blocks can mess with pytest.patch() - "TC001", # Move application import {} into a type-checking block - "TC002", # Move third-party import {} into a type-checking block - "TC003", # Move standard library import {} into a type-checking block - - "TRY003", # Avoid specifying long messages outside the exception class - "TRY400", # Use `logging.exception` instead of `logging.error` - # 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", - "Q", - "COM812", - "COM819", - - # Disabled because ruff does not understand type of __all__ generated by a function - "PLE0605" +[lint.isort] +known-third-party = [ + "pylint", ] - -[tool.ruff.lint.flake8-import-conventions.extend-aliases] -voluptuous = "vol" -"homeassistant.components.air_quality.PLATFORM_SCHEMA" = "AIR_QUALITY_PLATFORM_SCHEMA" -"homeassistant.components.alarm_control_panel.PLATFORM_SCHEMA" = "ALARM_CONTROL_PANEL_PLATFORM_SCHEMA" -"homeassistant.components.binary_sensor.PLATFORM_SCHEMA" = "BINARY_SENSOR_PLATFORM_SCHEMA" -"homeassistant.components.button.PLATFORM_SCHEMA" = "BUTTON_PLATFORM_SCHEMA" -"homeassistant.components.calendar.PLATFORM_SCHEMA" = "CALENDAR_PLATFORM_SCHEMA" -"homeassistant.components.camera.PLATFORM_SCHEMA" = "CAMERA_PLATFORM_SCHEMA" -"homeassistant.components.climate.PLATFORM_SCHEMA" = "CLIMATE_PLATFORM_SCHEMA" -"homeassistant.components.conversation.PLATFORM_SCHEMA" = "CONVERSATION_PLATFORM_SCHEMA" -"homeassistant.components.cover.PLATFORM_SCHEMA" = "COVER_PLATFORM_SCHEMA" -"homeassistant.components.date.PLATFORM_SCHEMA" = "DATE_PLATFORM_SCHEMA" -"homeassistant.components.datetime.PLATFORM_SCHEMA" = "DATETIME_PLATFORM_SCHEMA" -"homeassistant.components.device_tracker.PLATFORM_SCHEMA" = "DEVICE_TRACKER_PLATFORM_SCHEMA" -"homeassistant.components.event.PLATFORM_SCHEMA" = "EVENT_PLATFORM_SCHEMA" -"homeassistant.components.fan.PLATFORM_SCHEMA" = "FAN_PLATFORM_SCHEMA" -"homeassistant.components.geo_location.PLATFORM_SCHEMA" = "GEO_LOCATION_PLATFORM_SCHEMA" -"homeassistant.components.humidifier.PLATFORM_SCHEMA" = "HUMIDIFIER_PLATFORM_SCHEMA" -"homeassistant.components.image.PLATFORM_SCHEMA" = "IMAGE_PLATFORM_SCHEMA" -"homeassistant.components.image_processing.PLATFORM_SCHEMA" = "IMAGE_PROCESSING_PLATFORM_SCHEMA" -"homeassistant.components.lawn_mower.PLATFORM_SCHEMA" = "LAWN_MOWER_PLATFORM_SCHEMA" -"homeassistant.components.light.PLATFORM_SCHEMA" = "LIGHT_PLATFORM_SCHEMA" -"homeassistant.components.lock.PLATFORM_SCHEMA" = "LOCK_PLATFORM_SCHEMA" -"homeassistant.components.media_player.PLATFORM_SCHEMA" = "MEDIA_PLAYER_PLATFORM_SCHEMA" -"homeassistant.components.notify.PLATFORM_SCHEMA" = "NOTIFY_PLATFORM_SCHEMA" -"homeassistant.components.number.PLATFORM_SCHEMA" = "NUMBER_PLATFORM_SCHEMA" -"homeassistant.components.remote.PLATFORM_SCHEMA" = "REMOTE_PLATFORM_SCHEMA" -"homeassistant.components.scene.PLATFORM_SCHEMA" = "SCENE_PLATFORM_SCHEMA" -"homeassistant.components.select.PLATFORM_SCHEMA" = "SELECT_PLATFORM_SCHEMA" -"homeassistant.components.sensor.PLATFORM_SCHEMA" = "SENSOR_PLATFORM_SCHEMA" -"homeassistant.components.siren.PLATFORM_SCHEMA" = "SIREN_PLATFORM_SCHEMA" -"homeassistant.components.stt.PLATFORM_SCHEMA" = "STT_PLATFORM_SCHEMA" -"homeassistant.components.switch.PLATFORM_SCHEMA" = "SWITCH_PLATFORM_SCHEMA" -"homeassistant.components.text.PLATFORM_SCHEMA" = "TEXT_PLATFORM_SCHEMA" -"homeassistant.components.time.PLATFORM_SCHEMA" = "TIME_PLATFORM_SCHEMA" -"homeassistant.components.todo.PLATFORM_SCHEMA" = "TODO_PLATFORM_SCHEMA" -"homeassistant.components.tts.PLATFORM_SCHEMA" = "TTS_PLATFORM_SCHEMA" -"homeassistant.components.vacuum.PLATFORM_SCHEMA" = "VACUUM_PLATFORM_SCHEMA" -"homeassistant.components.valve.PLATFORM_SCHEMA" = "VALVE_PLATFORM_SCHEMA" -"homeassistant.components.update.PLATFORM_SCHEMA" = "UPDATE_PLATFORM_SCHEMA" -"homeassistant.components.wake_word.PLATFORM_SCHEMA" = "WAKE_WORD_PLATFORM_SCHEMA" -"homeassistant.components.water_heater.PLATFORM_SCHEMA" = "WATER_HEATER_PLATFORM_SCHEMA" -"homeassistant.components.weather.PLATFORM_SCHEMA" = "WEATHER_PLATFORM_SCHEMA" -"homeassistant.core.DOMAIN" = "HOMEASSISTANT_DOMAIN" -"homeassistant.helpers.area_registry" = "ar" -"homeassistant.helpers.category_registry" = "cr" -"homeassistant.helpers.config_validation" = "cv" -"homeassistant.helpers.device_registry" = "dr" -"homeassistant.helpers.entity_registry" = "er" -"homeassistant.helpers.floor_registry" = "fr" -"homeassistant.helpers.issue_registry" = "ir" -"homeassistant.helpers.label_registry" = "lr" -"homeassistant.util.color" = "color_util" -"homeassistant.util.dt" = "dt_util" -"homeassistant.util.json" = "json_util" -"homeassistant.util.location" = "location_util" -"homeassistant.util.logging" = "logging_util" -"homeassistant.util.network" = "network_util" -"homeassistant.util.ulid" = "ulid_util" -"homeassistant.util.uuid" = "uuid_util" -"homeassistant.util.yaml" = "yaml_util" - -[tool.ruff.lint.flake8-pytest-style] -fixture-parentheses = false -mark-parentheses = false - -[tool.ruff.lint.flake8-tidy-imports.banned-api] -"async_timeout".msg = "use asyncio.timeout instead" -"pytz".msg = "use zoneinfo instead" -"tests".msg = "You should not import tests" - -[tool.ruff.lint.isort] -force-sort-within-sections = true -known-first-party = [ - "homeassistant", -] -combine-as-imports = true -split-on-trailing-comma = false - -#[tool.ruff.lint.per-file-ignores] - - - -[tool.ruff.lint.mccabe] -max-complexity = 25 - -[tool.ruff.lint.pydocstyle] -property-decorators = ["propcache.api.cached_property"] From 507c6966a4196dda6a03004baea4e8daad8014f0 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 19:04:24 +0000 Subject: [PATCH 08/34] remove path --- ruff.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruff.toml b/ruff.toml index 4e1a038..a137396 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,5 +1,5 @@ # This extend our general Ruff rules specifically for tests -extend = "../pyproject.toml" +extend = "pyproject.toml" [lint] extend-ignore = [ From 3dd7c3c36086a677447b23a6abb22b77b5c7edc7 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 19:11:38 +0000 Subject: [PATCH 09/34] check if this resolves the docstring ruff finding --- custom_components/victron/base.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/custom_components/victron/base.py b/custom_components/victron/base.py index f5d741e..394b31a 100644 --- a/custom_components/victron/base.py +++ b/custom_components/victron/base.py @@ -1,3 +1,22 @@ +""" +This module defines entity descriptions for Victron components. + +Classes: + VictronBaseEntityDescription: Describes a base entity for Victron components. + VictronWriteBaseEntityDescription: Describes a writable base entity for Victron components. + +VictronBaseEntityDescription: + Attributes: + slave (int): The slave identifier. + value_fn (Callable[[dict], StateType]): A function to extract the value from data. + + Methods: + lambda_func(): Returns a lambda function to extract data based on slave and key. + +VictronWriteBaseEntityDescription: + Attributes: + address (int): The address for the writable entity. +""" from collections.abc import Callable from dataclasses import dataclass From aaa56a6c1fdaaba1ae17e4814a2642aa52e0c60c Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 19:13:51 +0000 Subject: [PATCH 10/34] resolve comment related findings to check if this resolves docstring reports --- custom_components/victron/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/custom_components/victron/base.py b/custom_components/victron/base.py index 394b31a..06f2efd 100644 --- a/custom_components/victron/base.py +++ b/custom_components/victron/base.py @@ -1,5 +1,4 @@ -""" -This module defines entity descriptions for Victron components. +"""Module defines entity descriptions for Victron components. Classes: VictronBaseEntityDescription: Describes a base entity for Victron components. @@ -10,13 +9,15 @@ slave (int): The slave identifier. value_fn (Callable[[dict], StateType]): A function to extract the value from data. - Methods: +Methods: lambda_func(): Returns a lambda function to extract data based on slave and key. VictronWriteBaseEntityDescription: Attributes: address (int): The address for the writable entity. + """ + from collections.abc import Callable from dataclasses import dataclass From 000902dc00f8cb616d7aa59525496de58d568daa Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 19:18:35 +0000 Subject: [PATCH 11/34] resolve the linting errors for the base.py file --- custom_components/victron/base.py | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/custom_components/victron/base.py b/custom_components/victron/base.py index 06f2efd..04722b9 100644 --- a/custom_components/victron/base.py +++ b/custom_components/victron/base.py @@ -1,22 +1,4 @@ -"""Module defines entity descriptions for Victron components. - -Classes: - VictronBaseEntityDescription: Describes a base entity for Victron components. - VictronWriteBaseEntityDescription: Describes a writable base entity for Victron components. - -VictronBaseEntityDescription: - Attributes: - slave (int): The slave identifier. - value_fn (Callable[[dict], StateType]): A function to extract the value from data. - -Methods: - lambda_func(): Returns a lambda function to extract data based on slave and key. - -VictronWriteBaseEntityDescription: - Attributes: - address (int): The address for the writable entity. - -""" +"""Module defines entity descriptions for Victron components.""" from collections.abc import Callable from dataclasses import dataclass @@ -27,8 +9,11 @@ @dataclass class VictronBaseEntityDescription(EntityDescription): + """An extension of EntityDescription for Victron components.""" + @staticmethod def lambda_func(): + """Return an entitydescription.""" return lambda data, slave, key: data["data"][str(slave) + "." + str(key)] slave: int = None @@ -37,4 +22,6 @@ def lambda_func(): @dataclass class VictronWriteBaseEntityDescription(VictronBaseEntityDescription): + """An extension of VictronBaseEntityDescription for writeable Victron components.""" + address: int = None From 2af83a0fe83088f920af524808e5f2cd57dc4663 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 19:21:40 +0000 Subject: [PATCH 12/34] resolve some of the linting errors in binary_sensor.py --- custom_components/victron/binary_sensor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/custom_components/victron/binary_sensor.py b/custom_components/victron/binary_sensor.py index dfaf629..3593873 100644 --- a/custom_components/victron/binary_sensor.py +++ b/custom_components/victron/binary_sensor.py @@ -43,11 +43,9 @@ async def async_setup_entry( for name in registerLedger: for register_name, registerInfo in register_info_dict[name].items(): _LOGGER.debug( - "unit == " - + str(slave) - + " registerLedger == " - + str(registerLedger) - + " registerInfo " + "unit == %s registerLedger == %s registerInfo", + slave, + registerLedger, ) if isinstance(registerInfo.entityType, BoolReadEntityType): From 6fab858f158c7848bdb7741a1326aaf18fb58696 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 20:10:45 +0000 Subject: [PATCH 13/34] resolve more of the linting issues --- custom_components/victron/binary_sensor.py | 3 ++- custom_components/victron/button.py | 11 +++++------ custom_components/victron/config_flow.py | 8 +++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/custom_components/victron/binary_sensor.py b/custom_components/victron/binary_sensor.py index 3593873..0cf2de8 100644 --- a/custom_components/victron/binary_sensor.py +++ b/custom_components/victron/binary_sensor.py @@ -54,7 +54,7 @@ async def async_setup_entry( name=register_name.replace("_", " "), slave=slave, ) - _LOGGER.debug("composed description == " + str(description)) + _LOGGER.debug("composed description == %s", description) descriptions.append(description) entities = [] @@ -110,6 +110,7 @@ def is_on(self) -> bool: @property def available(self) -> bool: + """Return True if entity is available.""" full_key = str(self.description.slave) + "." + self.description.key return self.coordinator.processed_data()["availability"][full_key] diff --git a/custom_components/victron/button.py b/custom_components/victron/button.py index b590b2d..84d71fc 100644 --- a/custom_components/victron/button.py +++ b/custom_components/victron/button.py @@ -41,11 +41,9 @@ async def async_setup_entry( for name in registerLedger: for register_name, registerInfo in register_info_dict[name].items(): _LOGGER.debug( - "unit == " - + str(slave) - + " registerLedger == " - + str(registerLedger) - + " registerInfo " + "unit == %s registerLedger == %s registerInfo", + slave, + registerLedger, ) if not config_entry.options[CONF_ADVANCED_OPTIONS]: continue @@ -58,7 +56,7 @@ async def async_setup_entry( device_class=ButtonDeviceClass.RESTART, address=registerInfo.register, ) - _LOGGER.debug("composed description == " + str(description)) + _LOGGER.debug("composed description == %s", description) descriptions.append(description) entities = [] @@ -109,6 +107,7 @@ async def async_press(self) -> None: @property def available(self) -> bool: + """Return True if entity available.""" full_key = str(self.description.slave) + "." + self.description.key return self.coordinator.processed_data()["availability"][full_key] diff --git a/custom_components/victron/config_flow.py b/custom_components/victron/config_flow.py index 1131a07..c4e56bc 100644 --- a/custom_components/victron/config_flow.py +++ b/custom_components/victron/config_flow.py @@ -65,8 +65,8 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, # your_validate_func, data["username"], data["password"] # ) - _LOGGER.debug("host = " + data[CONF_HOST]) - _LOGGER.debug("port = " + str(data[CONF_PORT])) + _LOGGER.debug("host = %s", data[CONF_HOST]) + _LOGGER.debug("port = %s", data[CONF_PORT]) hub = VictronHub(data[CONF_HOST], data[CONF_PORT]) try: @@ -75,11 +75,12 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, discovered_devices = await scan_connected_devices(hub=hub) _LOGGER.debug("successfully discovered devices") except HomeAssistantError: - _LOGGER.error("failed to connect to the victron device:") + _LOGGER.error("Failed to connect to the victron device:") return {"title": DOMAIN, "data": discovered_devices} async def scan_connected_devices(hub: VictronHub) -> list: + """Scan for connected devices.""" return hub.determine_present_devices() @@ -90,6 +91,7 @@ class VictronFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 def __init__(self): + """Initialize the config flow.""" self.advanced_options = None self.interval = None self.port = None From 57bfc40f8f473393f695977891069693cd4d356c Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 20:24:29 +0000 Subject: [PATCH 14/34] ruff part 2 --- custom_components/victron/config_flow.py | 6 +++++ custom_components/victron/const.py | 32 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/custom_components/victron/config_flow.py b/custom_components/victron/config_flow.py index c4e56bc..0eabab3 100644 --- a/custom_components/victron/config_flow.py +++ b/custom_components/victron/config_flow.py @@ -284,7 +284,10 @@ async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None) class parsedEntry: + """Parsed entry.""" + def __init__(self, decoderInfo: RegisterInfo, value): + """Initialize the parsed entry.""" self.decoderInfo = decoderInfo self.value = value @@ -400,6 +403,7 @@ async def async_step_init( return None def init_read_form(self, errors: dict): + """Handle read support and limit settings if requested.""" return self.async_show_form( step_id="init_read", errors=errors, @@ -415,6 +419,7 @@ def init_read_form(self, errors: dict): ) def init_write_form(self, errors: dict): + """Handle write support and limit settings if requested.""" config = dict(self.config_entry.options) system_ac_voltage_default = self.config_entry.options.get( CONF_AC_SYSTEM_VOLTAGE, AC_VOLTAGES["US (120)"] @@ -496,6 +501,7 @@ def init_write_form(self, errors: dict): @staticmethod def get_dict_key(dict, val): + """Get the key from a dictionary.""" for key, value in dict.items(): if val == value: return key diff --git a/custom_components/victron/const.py b/custom_components/victron/const.py index 71d0fa2..74cf90f 100644 --- a/custom_components/victron/const.py +++ b/custom_components/victron/const.py @@ -22,6 +22,8 @@ class DeviceType(Enum): + """Enum for device types.""" + GRID = 1 TANK = 2 MULTI = 3 @@ -70,50 +72,76 @@ def __init__(self, length=1, read_length=None): class EntityType: + """Base entityType.""" + def __init__(self, entityTypeName) -> None: + """Initialize the entity type.""" self.entityTypeName = entityTypeName class ReadEntityType(EntityType): + """Read entity type.""" + def __init__(self, entityTypeName: str = "read") -> None: + """Initialize the read entity type.""" super().__init__(entityTypeName=entityTypeName) class TextReadEntityType(ReadEntityType): + """Text read entity type.""" + def __init__(self, decodeEnum: Enum) -> None: + """Initialize the text read entity type.""" super().__init__() self.decodeEnum = decodeEnum class BoolReadEntityType(ReadEntityType): + """Bool read entity type.""" + def __init__(self) -> None: + """Initialize the bool read entity type.""" super().__init__(entityTypeName="bool") class ButtonWriteType(EntityType): + """Button write type.""" + def __init__(self) -> None: + """Initialize the button write type.""" super().__init__(entityTypeName="button") class SwitchWriteType(EntityType): + """Switch write type.""" + def __init__(self) -> None: + """Initialize the switch write type.""" super().__init__(entityTypeName="switch") class SliderWriteType(EntityType): + """Slider write type.""" + def __init__(self, powerType="", negative: bool = False) -> None: + """Initialize the slider write type.""" super().__init__(entityTypeName="slider") self.powerType = powerType self.negative = negative class SelectWriteType(EntityType): + """Select write type.""" + def __init__(self, optionsEnum: Enum) -> None: + """Initialize the select write type.""" super().__init__(entityTypeName="select") self.options = optionsEnum class RegisterInfo: + """Class for register information.""" + def __init__( self, register, @@ -123,6 +151,7 @@ def __init__( entityType: EntityType = ReadEntityType(), step=0, ) -> None: + """Initialize the register info.""" self.register = register self.dataType = dataType self.unit = ( @@ -137,6 +166,7 @@ def __init__( self.entityType = entityType def determine_stateclass(self): + """Determine the state class.""" if self.unit == UnitOfEnergy.KILO_WATT_HOUR: return SensorStateClass.TOTAL_INCREASING if self.unit is None: @@ -145,6 +175,8 @@ def determine_stateclass(self): class generic_alarm_ledger(Enum): + """Generic alarm ledger.""" + OK = 0 WARNING = 1 ALARM = 2 From e7f3f52c80af80270deed0fbb975289db6ecb888 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 20:35:50 +0000 Subject: [PATCH 15/34] ruff part 3 --- custom_components/victron/const.py | 73 ++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/custom_components/victron/const.py b/custom_components/victron/const.py index 74cf90f..e6c3ea3 100644 --- a/custom_components/victron/const.py +++ b/custom_components/victron/const.py @@ -57,7 +57,10 @@ class DeviceType(Enum): class STRING: + """Class for string data type.""" + def __init__(self, length=1, read_length=None): + """Initialize the string data type.""" self.length = length self.readLength = read_length if read_length is not None else length * 2 @@ -239,6 +242,8 @@ class generic_alarm_ledger(Enum): class vebus_mode(Enum): + """Vebus mode.""" + CHARGER = 1 INVERTER = 2 ON = 3 @@ -246,12 +251,16 @@ class vebus_mode(Enum): class generic_activeinput(Enum): + """Generic active input.""" + AC_INPUT_1 = 0 AC_INPUT_2 = 1 DISCONNECTED = 240 class generic_charger_state(Enum): + """Generic charger state.""" + OFF = 0 LOW_POWER = 1 FAULT = 2 @@ -268,6 +277,8 @@ class generic_charger_state(Enum): class vebus_error(Enum): + """Vebus error.""" + OK = 0 EXTERNAL_PHASE_TRIGGERED_SWITCHOFF = 1 MK2_TYPE_MISMATCH = 2 @@ -725,6 +736,8 @@ class vebus_error(Enum): class battery_state(Enum): + """Battery state.""" + WAIT_START_INIT = 0 BEFORE_BOOT_INIT = 1 BEFORE_BOOT_DELAY_INIT = 2 @@ -745,6 +758,8 @@ class battery_state(Enum): class battery_error(Enum): + """Battery error.""" + NONE = 0 BATTERY_INIT_ERROR = 1 NO_BATTERIES_CONNECTED = 2 @@ -846,11 +861,15 @@ class battery_error(Enum): class solarcharger_mode(Enum): + """Solar charger mode.""" + ON = 1 OFF = 4 class solarcharger_state(Enum): + """Solar charger state.""" + OFF = 0 FAULT = 2 BULK = 3 @@ -864,6 +883,8 @@ class solarcharger_state(Enum): class solarcharger_equalization_pending(Enum): + """Solar charger equalization pending.""" + NO = 0 YES = 1 ERROR = 2 @@ -871,6 +892,8 @@ class solarcharger_equalization_pending(Enum): class generic_charger_errorcode(Enum): + """Generic charger error code.""" + NONE = 0 TEMPERATURE_HIGH = 1 VOLTAGE_HIGH = 2 @@ -891,6 +914,8 @@ class generic_charger_errorcode(Enum): class generic_mppoperationmode(Enum): + """Generic MPP operation mode.""" + OFF = 0 LIMITED = 1 ACTIVE = 2 @@ -1041,6 +1066,8 @@ class generic_mppoperationmode(Enum): class generic_position(Enum): + """Generic position.""" + AC_INPUT_1 = 0 AC_OUTPUT = 1 AC_INPUT_2 = 2 @@ -1115,6 +1142,8 @@ class generic_position(Enum): class charger_mode(Enum): + """Charger mode.""" + OFF = 0 ON = 1 ERROR = 2 @@ -1239,6 +1268,8 @@ class charger_mode(Enum): class ess_batterylife_state(Enum): + """ESS battery life state.""" + BL_DISABLED_DUPLICATE_1 = 0 RESTARTING = 1 SELF_CONSUMPTION = 2 @@ -1255,6 +1286,8 @@ class ess_batterylife_state(Enum): class ess_mode(Enum): + """ESS mode.""" + SELF_CONSUMPTION_WITH_BATTERY_LIFE = 0 SELF_CONSUMPTION = 1 KEEP_CHARGED = 2 @@ -1278,6 +1311,8 @@ class ess_mode(Enum): class tank_fluidtype(Enum): + """Tank fluid type.""" + FUEL = 0 FRESH_WATER = 1 WASTE_WATER = 2 @@ -1293,6 +1328,8 @@ class tank_fluidtype(Enum): class tank_status(Enum): + """Tank status.""" + OK = 0 DISCONNECTED = 1 SHORT_CIRCUITED = 2 @@ -1378,6 +1415,8 @@ class tank_status(Enum): class inverter_mode(Enum): + """Inverter mode.""" + ON = 2 OFF = 4 ECO = 5 @@ -1484,6 +1523,8 @@ class inverter_mode(Enum): class genset_status(Enum): + """Genset status.""" + STANDBY = 0 STARTUP_1 = 1 STARTUP_2 = 2 @@ -1498,6 +1539,8 @@ class genset_status(Enum): class genset_errorcode(Enum): + """Genset error code.""" + NONE = 0 AC_L1_VOLTAGE_TOO_LOW = 1 AC_L1_FREQUENCY_TOO_LOW = 2 @@ -1644,12 +1687,16 @@ class genset_errorcode(Enum): class temperature_type(Enum): + """Temperature type.""" + BATTERY = 0 FRIDGE = 1 GENERIC = 2 class temperature_status(Enum): + """Temperature status.""" + OK = 0 DISCONNECTED = 1 SHORT_CIRCUITED = 2 @@ -1687,6 +1734,8 @@ class temperature_status(Enum): class digitalinput_state(Enum): + """Digital input state.""" + LOW = 0 HIGH = 1 OFF = 2 @@ -1702,6 +1751,8 @@ class digitalinput_state(Enum): class digitalinput_type(Enum): + """Digital input type.""" + DOOR = 2 BILGE_PUMP = 3 BILGE_ALARM = 4 @@ -1730,6 +1781,8 @@ class digitalinput_type(Enum): class generator_runningbyconditioncode(Enum): + """Generator running by condition code.""" + STOPPED = 0 MANUAL = 1 TEST_RUN = 2 @@ -1744,12 +1797,16 @@ class generator_runningbyconditioncode(Enum): class generator_state(Enum): + """Generator state.""" + STOPPED = 0 RUNNING = 1 ERROR = 10 class generator_error(Enum): + """Generator error.""" + NONE = 0 REMOTE_DISABLED = 1 REMOTE_FAULT = 2 @@ -1801,12 +1858,16 @@ class generator_error(Enum): class evcharger_mode(Enum): + """EV charger mode.""" + AC_INPUT_1 = 0 AC_OUTPUT = 1 AC_INPUT_2 = 2 class evcharger_status(Enum): + """EV charger status.""" + DISCONNECTED = 0 CONNECTED = 1 CHARGING = 2 @@ -1933,6 +1994,8 @@ class evcharger_status(Enum): class alternator_state(Enum): + """Alternator state.""" + OFF = 0 FAULT = 2 BULK = 3 @@ -1944,6 +2007,8 @@ class alternator_state(Enum): class alternator_errorcode(Enum): + """Alternator error code.""" + HIGH_BATTERY_TEMPERATURE = 12 HIGH_BATTERY_VOLTAGE = 13 LOW_BATTERY_VOLTAGE = 14 @@ -2177,6 +2242,8 @@ class alternator_errorcode(Enum): class multi_mode(Enum): + """Multi mode.""" + CHARGER = 1 INVERTER = 2 ON = 3 @@ -2184,6 +2251,8 @@ class multi_mode(Enum): class multi_input_type(Enum): + """Multi input type.""" + UNUSED = 0 GRID = 1 GENSET = 2 @@ -2450,6 +2519,8 @@ class multi_input_type(Enum): class register_input_source(Enum): + """Input source.""" + UNKNOWN = 0 GRID = 1 GENERATOR = 2 @@ -2492,6 +2563,8 @@ class register_input_source(Enum): class system_battery_state(Enum): + """Battery state.""" + IDLE = 0 CHARGING = 1 DISCHARGING = 2 From 3f6eff92df9eaace7bf613824ae99bf7ce31085b Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 20:59:12 +0000 Subject: [PATCH 16/34] ruff part 4 --- custom_components/victron/coordinator.py | 24 +++++++++++++++++++----- custom_components/victron/hub.py | 22 +++++++++++++++------- custom_components/victron/number.py | 21 ++++++++++++++------- custom_components/victron/select.py | 14 ++++++++------ 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/custom_components/victron/coordinator.py b/custom_components/victron/coordinator.py index e1d8019..21324ef 100644 --- a/custom_components/victron/coordinator.py +++ b/custom_components/victron/coordinator.py @@ -1,14 +1,15 @@ +"""Define the Victron Energy Device Update Coordinator.""" + from __future__ import annotations from collections import OrderedDict from datetime import timedelta import logging +import pymodbus from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder -import pymodbus - if "3.7.0" <= pymodbus.__version__ <= "3.7.4": from pymodbus.pdu.register_read_message import ReadHoldingRegistersResponse else: @@ -86,7 +87,7 @@ async def _async_update_data(self) -> dict: unavailable_entities[full_key] = False _LOGGER.warning( - "no valid data returned for entities of slave: %s (if the device continues to no longer update) check if the device was physically removed. Before opening an issue please force a rescan to attempt to resolve this issue", + "No valid data returned for entities of slave: %s (if the device continues to no longer update) check if the device was physically removed. Before opening an issue please force a rescan to attempt to resolve this issue", unit, ) else: @@ -114,6 +115,7 @@ def parse_register_data( registerInfo: OrderedDict(str, RegisterInfo), unit: int, ) -> dict: + """Parse the register data.""" decoder = BinaryPayloadDecoder.fromRegisters( buffer.registers, byteorder=Endian.BIG ) @@ -149,11 +151,13 @@ def parse_register_data( return decoded_data def decode_scaling(self, number, scale, unit): + """Decode the scaling.""" if unit == "" and scale == 1: return round(number) return number / scale def encode_scaling(self, value, unit, scale): + """Encode the scaling.""" if scale == 0: return value if unit == "" and scale == 1: @@ -161,19 +165,23 @@ def encode_scaling(self, value, unit, scale): return int(value * scale) def get_data(self): + """Return the data.""" return self.data async def async_update_local_entry(self, key, value): + """Update the local entry.""" data = self.data data["data"][key] = value self.async_set_updated_data(data) - """Force update data after change.""" + await self.async_request_refresh() def processed_data(self): + """Return the processed data.""" return self.data async def fetch_registers(self, unit, registerData): + """Fetch the registers.""" try: # run api_update in async job return await self.hass.async_add_executor_job( @@ -184,6 +192,7 @@ async def fetch_registers(self, unit, registerData): raise UpdateFailed("Fetching registers failed") from e def write_register(self, unit, address, value): + """Write to the register.""" # try: self.api_write(unit, address, value) @@ -193,10 +202,12 @@ def write_register(self, unit, address, value): # _LOGGER.error("failed to write to option:", e def api_write(self, unit, address, value): + """Write to the api.""" # recycle connection return self.api.write_register(unit=unit, address=address, value=value) def api_update(self, unit, registerInfo): + """Update the api.""" # recycle connection return self.api.read_holding_registers( unit=unit, @@ -206,10 +217,13 @@ def api_update(self, unit, registerInfo): class DecodeDataTypeUnsupported(Exception): - pass + """Exception for unsupported data type.""" class DataEntry: + """Data entry class.""" + def __init__(self, unit, value) -> None: + """Initialize the data entry.""" self.unit = unit self.value = value diff --git a/custom_components/victron/hub.py b/custom_components/victron/hub.py index eeed9e4..84f384b 100644 --- a/custom_components/victron/hub.py +++ b/custom_components/victron/hub.py @@ -1,3 +1,4 @@ +"""Support for Victron Energy devices.""" from collections import OrderedDict import logging import threading @@ -12,6 +13,8 @@ class VictronHub: + """Victron Hub.""" + def __init__(self, host: str, port: int) -> None: """Initialize.""" self.host = host @@ -20,17 +23,21 @@ def __init__(self, host: str, port: int) -> None: self._lock = threading.Lock() def is_still_connected(self): + """Check if the connection is still open.""" return self._client.is_socket_open() def connect(self): + """Connect to the Modbus TCP server.""" return self._client.connect() def disconnect(self): + """Disconnect from the Modbus TCP server.""" if self._client.is_socket_open(): return self._client.close() return None def write_register(self, unit, address, value): + """Write a register.""" slave = int(unit) if unit else 1 return self._client.write_register(address=address, value=value, slave=slave) @@ -42,6 +49,7 @@ def read_holding_registers(self, unit, address, count): ) def calculate_register_count(self, registerInfoDict: OrderedDict): + """Calculate the number of registers to read.""" first_key = next(iter(registerInfoDict)) last_key = next(reversed(registerInfoDict)) end_correction = 1 @@ -55,10 +63,12 @@ def calculate_register_count(self, registerInfoDict: OrderedDict): ) + end_correction def get_first_register_id(self, registerInfoDict: OrderedDict): + """Return first register id.""" first_register = next(iter(registerInfoDict)) return registerInfoDict[first_register].register def determine_present_devices(self): + """Determine which devices are present.""" valid_devices = {} for unit in valid_unit_ids: @@ -74,12 +84,10 @@ def determine_present_devices(self): result = self.read_holding_registers(unit, address, count) if result.isError(): _LOGGER.debug( - "result is error for unit: " - + str(unit) - + " address: " - + str(address) - + " count: " - + str(count) + "result is error for unit: %s address: %s count: %s", + unit, + address, + count, ) else: working_registers.append(key) @@ -89,6 +97,6 @@ def determine_present_devices(self): if len(working_registers) > 0: valid_devices[unit] = working_registers else: - _LOGGER.debug("no registers found for unit: " + str(unit)) + _LOGGER.debug("no registers found for unit: %s", unit) return valid_devices diff --git a/custom_components/victron/number.py b/custom_components/victron/number.py index fbbb315..c489fd5 100644 --- a/custom_components/victron/number.py +++ b/custom_components/victron/number.py @@ -61,11 +61,9 @@ async def async_setup_entry( for name in registerLedger: for register_name, registerInfo in register_info_dict[name].items(): _LOGGER.debug( - "unit == " - + str(slave) - + " registerLedger == " - + str(registerLedger) - + " registerInfo " + "unit == %s registerLedger == %s registerInfo", + slave, + registerLedger, ) if isinstance(registerInfo.entityType, SliderWriteType): @@ -93,7 +91,7 @@ async def async_setup_entry( scale=registerInfo.scale, native_step=registerInfo.step, ) - _LOGGER.debug("composed description == " + str(descriptions)) + _LOGGER.debug("composed description == %s", descriptions) descriptions.append(description) entities = [] @@ -109,6 +107,7 @@ async def async_setup_entry( def determine_min_value( unit, config_entry: config_entries.ConfigEntry, powerType, negative: bool ) -> int: + """Determine the minimum value for a number entity.""" if unit == PERCENTAGE: return 0 if unit == UnitOfElectricPotential.VOLT: @@ -148,6 +147,7 @@ def determine_min_value( def determine_max_value( unit, config_entry: config_entries.ConfigEntry, powerType ) -> int: + """Determine the maximum value for a number entity.""" if unit == PERCENTAGE: return 100 if unit == UnitOfElectricPotential.VOLT: @@ -188,6 +188,8 @@ class VictronNumberMixin: @dataclass class VictronNumberStep: + """A class that adds stepping to number entities.""" + native_step: float = 0 @@ -198,9 +200,10 @@ class VictronEntityDescription( VictronNumberMixin, VictronNumberStep, ): + """Describes victron number entity.""" + # Overwrite base entitydescription property to resolve automatic property ordering issues when a mix of non-default and default properties are used key: str | None = None - """Describes victron number entity.""" class VictronNumber(NumberEntity): @@ -266,6 +269,7 @@ def native_value(self) -> float: @property def native_step(self) -> float | None: + """Return the step width of the entity.""" if ( self.description.mode != NumberMode.SLIDER ): # allow users to skip stepping in case of box mode @@ -284,14 +288,17 @@ def native_step(self) -> float | None: @property def native_min_value(self) -> float: + """Return the minimum value of the entity.""" return self.description.native_min_value @property def native_max_value(self) -> float: + """Return the maximum value of the entity.""" return self.description.native_max_value @property def available(self) -> bool: + """Return True if entity is available.""" full_key = str(self.description.slave) + "." + self.description.key return self.coordinator.processed_data()["availability"][full_key] diff --git a/custom_components/victron/select.py b/custom_components/victron/select.py index 056c464..1b990be 100644 --- a/custom_components/victron/select.py +++ b/custom_components/victron/select.py @@ -45,11 +45,9 @@ async def async_setup_entry( for register_name, registerInfo in register_info_dict[name].items(): if isinstance(registerInfo.entityType, SelectWriteType): _LOGGER.debug( - "unit == " - + str(slave) - + " registerLedger == " - + str(registerLedger) - + " registerInfo " + "unit == %s registerLedger == %s registerInfo", + slave, + registerLedger, ) description = VictronEntityDescription( @@ -61,7 +59,7 @@ async def async_setup_entry( ) descriptions.append(description) - _LOGGER.debug("composed description == " + str(description)) + _LOGGER.debug("composed description == %s", description) entities = [] entity = {} @@ -93,6 +91,7 @@ def __init__( coordinator: victronEnergyDeviceUpdateCoordinator, description: VictronEntityDescription, ) -> None: + """Initialize the select.""" self._attr_native_value = None _LOGGER.debug("select init") self.coordinator = coordinator @@ -138,6 +137,7 @@ async def async_update(self) -> None: @property def current_option(self) -> str: + """Return the currently selected option.""" return self.description.options( self.description.value_fn( self.coordinator.processed_data(), @@ -148,6 +148,7 @@ def current_option(self) -> str: @property def options(self) -> list: + """Return a list of available options.""" return [option.name for option in self.description.options] async def async_select_option(self, option: str) -> None: @@ -163,6 +164,7 @@ async def async_select_option(self, option: str) -> None: # TODO extract these type of property definitions to base class @property def available(self) -> bool: + """Return True if entity available.""" full_key = str(self.description.slave) + "." + self.description.key return self.coordinator.processed_data()["availability"][full_key] From 8e3c6e3337760d8bc183bda129096618cdcc1a48 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:00:46 +0000 Subject: [PATCH 17/34] ruff part 5 --- custom_components/victron/sensor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/custom_components/victron/sensor.py b/custom_components/victron/sensor.py index c0853f5..c349e3d 100644 --- a/custom_components/victron/sensor.py +++ b/custom_components/victron/sensor.py @@ -61,11 +61,9 @@ async def async_setup_entry( for name in registerLedger: for register_name, registerInfo in register_info_dict[name].items(): _LOGGER.debug( - "unit == " - + str(slave) - + " registerLedger == " - + str(registerLedger) - + " registerInfo " + "unit == %s registerLedger == %s registerInfo", + slave, + registerLedger, ) if config_entry.options[CONF_ADVANCED_OPTIONS]: if not isinstance( @@ -86,7 +84,7 @@ async def async_setup_entry( if isinstance(registerInfo.entityType, TextReadEntityType) else None, ) - _LOGGER.debug("composed description == " + str(description)) + _LOGGER.debug("composed description == %s", description) descriptions.append(description) @@ -101,6 +99,7 @@ async def async_setup_entry( def determine_victron_device_class(name, unit): + """Determine the device class of a sensor based on its name and unit.""" if unit == PERCENTAGE and "soc" in name: return SensorDeviceClass.BATTERY if unit == PERCENTAGE: @@ -205,6 +204,7 @@ def _handle_coordinator_update(self) -> None: @property def available(self) -> bool: + """Return True if entity is available.""" full_key = str(self.description.slave) + "." + self.description.key return self.coordinator.processed_data()["availability"][full_key] From 20cab7f76e2135fe13d47da3fef0a044121c9083 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:02:57 +0000 Subject: [PATCH 18/34] ruff final part --- custom_components/victron/switch.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/custom_components/victron/switch.py b/custom_components/victron/switch.py index a2cd59e..058b020 100644 --- a/custom_components/victron/switch.py +++ b/custom_components/victron/switch.py @@ -42,11 +42,7 @@ async def async_setup_entry( for name in registerLedger: for register_name, registerInfo in register_info_dict[name].items(): _LOGGER.debug( - "unit == " - + str(slave) - + " registerLedger == " - + str(registerLedger) - + " registerInfo " + f"unit == {slave} registerLedger == {registerLedger} registerInfo " ) if isinstance(registerInfo.entityType, SwitchWriteType): @@ -57,7 +53,7 @@ async def async_setup_entry( address=registerInfo.register, ) descriptions.append(description) - _LOGGER.debug("composed description == " + str(description)) + _LOGGER.debug("composed description == %s", description) entities = [] entity = {} @@ -118,16 +114,17 @@ async def async_turn_off(self, **kwargs: Any) -> None: @property def is_on(self) -> bool: + """Return true if switch is on.""" data = self.description.value_fn( self.coordinator.processed_data(), self.description.slave, self.description.key, ) - """Return true if switch is on.""" return cast(bool, data) @property def available(self) -> bool: + """Return True if entity is available.""" full_key = str(self.description.slave) + "." + self.description.key return self.coordinator.processed_data()["availability"][full_key] From a7fbc316151ff58cf29b1eb6a772cbfefd458423 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:05:09 +0000 Subject: [PATCH 19/34] ruff remaining errors --- custom_components/victron/switch.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/custom_components/victron/switch.py b/custom_components/victron/switch.py index 058b020..2e496d5 100644 --- a/custom_components/victron/switch.py +++ b/custom_components/victron/switch.py @@ -42,7 +42,10 @@ async def async_setup_entry( for name in registerLedger: for register_name, registerInfo in register_info_dict[name].items(): _LOGGER.debug( - f"unit == {slave} registerLedger == {registerLedger} registerInfo " + "unit == %s registerLedger == %s registerInfo == %s", + slave, + registerLedger, + registerInfo, ) if isinstance(registerInfo.entityType, SwitchWriteType): @@ -81,6 +84,7 @@ def __init__( coordinator: victronEnergyDeviceUpdateCoordinator, description: VictronEntityDescription, ) -> None: + """Initialize the switch.""" self.coordinator = coordinator self.description: VictronEntityDescription = description self._attr_name = f"{description.name}" From 9e655ae856bb1a437c0d2149fb2a7cdad3e4aa6f Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:14:53 +0000 Subject: [PATCH 20/34] port the dutch translations from the remcom fork --- .../victron/translations/nl.json | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 custom_components/victron/translations/nl.json diff --git a/custom_components/victron/translations/nl.json b/custom_components/victron/translations/nl.json new file mode 100644 index 0000000..7a8c549 --- /dev/null +++ b/custom_components/victron/translations/nl.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout", + "already_configured": "Er wordt op dit moment slechts één instance van de Victron-integratie ondersteund" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Poort", + "interval": "Update-interval in (s)", + "advanced": "Schakelt schrijfondersteuning in en maakt het instellen van schrijfbeperkingen mogelijk" + } + }, + "advanced": { + "data": { + "ac_voltage": "De AC-spanning van uw netwerk in V", + "ac_current": "De AC (per fase) stroomlimiet van uw netwerk in A", + "dc_voltage": "De DC-spanning van uw batterij in V", + "dc_current": "De DC-stroomlimiet van uw batterij in A", + "number_of_phases": "De faseconfiguratie van uw systeem", + "use_sliders": "Gebruik trapsgewijze schuifregelaars voor beschrijfbare numerieke eenheden" + } + }, + "reconfigure": { + "data": { + "host": "Host", + "port": "Poort" + } + } + } + }, + "options": { + "step": { + "init_write": { + "data": { + "rescan": "Beschikbare apparaten opnieuw scannen. Dit scant alle beschikbare apparaten opnieuw", + "interval": "Update-interval in (s)", + "ac_voltage": "De AC-spanning van uw netwerk in V", + "ac_current": "De AC (per fase) stroomlimiet van uw netwerk in A", + "dc_voltage": "De DC-spanning van uw batterij in V", + "dc_current": "De DC-stroomlimiet van uw batterij in A", + "number_of_phases": "De faseconfiguratie van uw systeem", + "use_sliders": "Gebruik trapsgewijze schuifregelaars voor beschrijfbare numerieke eenheden", + "advanced": "Schakel over naar alleen-lezen modus als uitgeschakeld (bij indienen)" + } + }, + "init_read": { + "data": { + "rescan": "Beschikbare apparaten opnieuw scannen. Dit scant alle beschikbare apparaten opnieuw", + "interval": "Update-interval in (s)", + "advanced": "Schrijfondersteuning inschakelen" + } + }, + "advanced": { + "data": { + "ac_voltage": "De AC-spanning van uw netwerk in V", + "ac_current": "De AC-stroomlimiet van uw netwerk in A", + "dc_voltage": "De DC-spanning van uw batterij in V", + "dc_current": "De DC-stroomlimiet van uw batterij in A", + "number_of_phases": "De faseconfiguratie van uw systeem", + "use_sliders": "Gebruik trapsgewijze schuifregelaars voor beschrijfbare numerieke eenheden" + } + } + } + } +} \ No newline at end of file From 77aca98a2ff706aa02968db5b4011951967e9a8c Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:25:35 +0000 Subject: [PATCH 21/34] port github workflow options for auto patching version and issue label/closing management from remcom fork --- .github/workflows/build.yaml | 22 ++++++++++++++++++++++ .github/workflows/issue-label-bot.yaml | 4 ++++ .github/workflows/stale.yaml | 18 ++++++++++++++++++ hacs.json | 3 +++ 4 files changed, 47 insertions(+) create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/issue-label-bot.yaml create mode 100644 .github/workflows/stale.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..02724a6 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,22 @@ +on: + release: + types: [published] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Get version + id: version + uses: home-assistant/actions/helpers/version@master + - name: Patch manifest and zip + run: | + sed -i 's/v0.0.0/${{ steps.version.outputs.version }}/' custom_components/victron/manifest.json + + cd custom_components/victron/ + zip ../../victron.zip ./* -x '.*' + - uses: JasonEtco/upload-to-release@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: ./victron.zip application/zip \ No newline at end of file diff --git a/.github/workflows/issue-label-bot.yaml b/.github/workflows/issue-label-bot.yaml new file mode 100644 index 0000000..0ac8bab --- /dev/null +++ b/.github/workflows/issue-label-bot.yaml @@ -0,0 +1,4 @@ +label-alias: + bug: 'bug' + feature_request: 'feature_request' + question: 'question' \ No newline at end of file diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 0000000..5ed0171 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,18 @@ +name: "Close stale issues/pull requests" +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days' + stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days' + days-before-stale: 30 + days-before-close: 7 + operations-per-run: 500 + exempt-issue-labels: 'on-hold' \ No newline at end of file diff --git a/hacs.json b/hacs.json index 11fdbdf..65d07db 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,9 @@ { "name": "Victron GX modbus TCP", "render_readme": true, + "zip_release": true, + "filename": "victron.zip", + "hide_default_branch": false, "homeassistant": "2025.1", "hacs": "1.28.4" } \ No newline at end of file From 05221a22d99fde6ab3309d8aa6cd813d5b678ddc Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:26:51 +0000 Subject: [PATCH 22/34] move issue label bot file to correct position as done in remcom repo --- .github/{workflows => }/issue-label-bot.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => }/issue-label-bot.yaml (100%) diff --git a/.github/workflows/issue-label-bot.yaml b/.github/issue-label-bot.yaml similarity index 100% rename from .github/workflows/issue-label-bot.yaml rename to .github/issue-label-bot.yaml From 5b364bcacbc68bfe5c826470695938bf2dfe562c Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:29:47 +0000 Subject: [PATCH 23/34] update readme based on remcom fork --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5ca7d39..3b98ada 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ As of november 2022 the built-in home assistant modbus integration runs on a ver If you install this integration the built-in modbus integration will stop to work due to breaking changes between 2.x.x and 3.0.0 ## Important announcement: -Starting from homeassistant core version 2023.2.x the built-in modbus integration now uses pymodbus version 3.1.1. -Version 0.0.7 (and up) of this integration will also use the 3.1.1 pymodbus version. +Starting from homeassistant core version 2025.1.x the built-in modbus integration now uses pymodbus version 3.7.4. +Version 0.4.0 (and up) of this integration will also use the 3.7.4 pymodbus version. Although core version >= 2023.2 and previous versions of this integration should be compatible it is recommended that all users update both core and this integration in the same patch round. Since having multiple library version requirements might cause the built-in 3.1.1 library to be overwritten by 3.0.2 reference of versions 0.0.6 and earlier. @@ -68,7 +68,7 @@ This could cause issues if you are using specific configuration options of the b ## HACS ### Default -1. Add the integration through this link: +1. Add the integration through this link: [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=sfstar&repository=hass-victron&category=integration) 3. Restart home assistant 4. Setup integration via the integration page. @@ -87,7 +87,7 @@ Although this integration speaks to the (exposed by victron) modbusTCP server it - (when implemented) writing to write_registers (for example changing the ess setpoint value) Therefore the following applies to anyone using this code: -- This code is provided as is. +- This code is provided as is. - The developer does not assume any liability for issues caused by the use of this integration. - Use at your own risk. @@ -137,7 +137,7 @@ The voltage profile to use in order to calculate the voltage boundaries (i.e. 4s The AC voltage for a single phase in your region (currently supported is US: 120v and EU: 230v) This setting is used in combination with AC current to automatically calcultate the max wattage for applicable wattage settings. -# Resources +# Resources The following links can be helpfull resources: - [setting up modbusTCP on the gx device](https://www.victronenergy.com/live/ccgx:modbustcp_faq) - [Great UI card for the gx device data](https://github.com/flyrmyr/system-flow-card) From 72abd9836d5a6cf4de82726dd574ece23464306e Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:32:20 +0000 Subject: [PATCH 24/34] port additional translations from remcom fork --- .../victron/translations/de.json | 73 +++++++++++++++++++ .../victron/translations/es.json | 73 +++++++++++++++++++ .../victron/translations/fr.json | 73 +++++++++++++++++++ .../victron/translations/it.json | 73 +++++++++++++++++++ .../victron/translations/sv.json | 73 +++++++++++++++++++ 5 files changed, 365 insertions(+) create mode 100644 custom_components/victron/translations/de.json create mode 100644 custom_components/victron/translations/es.json create mode 100644 custom_components/victron/translations/fr.json create mode 100644 custom_components/victron/translations/it.json create mode 100644 custom_components/victron/translations/sv.json diff --git a/custom_components/victron/translations/de.json b/custom_components/victron/translations/de.json new file mode 100644 index 0000000..23f467d --- /dev/null +++ b/custom_components/victron/translations/de.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "Gerät ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ungültige Authentifizierung", + "unknown": "Unerwarteter Fehler", + "already_configured": "Nur eine Instanz der Victron-Integration wird derzeit unterstützt" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "interval": "Aktualisierungsintervall in (s)", + "advanced": "Aktiviert Schreibunterstützung und ermöglicht das Setzen von Schreibgrenzen" + } + }, + "advanced": { + "data": { + "ac_voltage": "Die Wechselspannung Ihres Netzes in V", + "ac_current": "Die Wechselstromgrenze (pro Phase) Ihres Netzes in A", + "dc_voltage": "Die Gleichspannung Ihrer Batterie in V", + "dc_current": "Die Gleichstromgrenze Ihrer Batterie in A", + "number_of_phases": "Die Phasenkonfiguration Ihres Systems", + "use_sliders": "Verwendet abgestufte Schieberegler für beschreibbare Zahlenwerte" + } + }, + "reconfigure": { + "data": { + "host": "Host", + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init_write": { + "data": { + "rescan": "Verfügbare Geräte erneut scannen. Dadurch werden alle verfügbaren Geräte erneut gescannt", + "interval": "Aktualisierungsintervall in (s)", + "ac_voltage": "Die Wechselspannung Ihres Netzes in V", + "ac_current": "Die Wechselstromgrenze (pro Phase) Ihres Netzes in A", + "dc_voltage": "Die Gleichspannung Ihrer Batterie in V", + "dc_current": "Die Gleichstromgrenze Ihrer Batterie in A", + "number_of_phases": "Die Phasenkonfiguration Ihres Systems", + "use_sliders": "Verwendet abgestufte Schieberegler für beschreibbare Zahlenwerte", + "advanced": "Wechselt in den Nur-Lesen-Modus, wenn nicht markiert (beim Absenden)" + } + }, + "init_read": { + "data": { + "rescan": "Verfügbare Geräte erneut scannen. Dadurch werden alle verfügbaren Geräte erneut gescannt", + "interval": "Aktualisierungsintervall in (s)", + "advanced": "Schreibunterstützung aktivieren" + } + }, + "advanced": { + "data": { + "ac_voltage": "Die Wechselspannung Ihres Netzes in V", + "ac_current": "Die Wechselstromgrenze Ihres Netzes in A", + "dc_voltage": "Die Gleichspannung Ihrer Batterie in V", + "dc_current": "Die Gleichstromgrenze Ihrer Batterie in A", + "number_of_phases": "Die Phasenkonfiguration Ihres Systems", + "use_sliders": "Verwendet abgestufte Schieberegler für beschreibbare Zahlenwerte" + } + } + } + } +} \ No newline at end of file diff --git a/custom_components/victron/translations/es.json b/custom_components/victron/translations/es.json new file mode 100644 index 0000000..4f7ddb2 --- /dev/null +++ b/custom_components/victron/translations/es.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya está configurado" + }, + "error": { + "cannot_connect": "Error al conectar", + "invalid_auth": "Autenticación inválida", + "unknown": "Error inesperado", + "already_configured": "Solo se admite una instancia de la integración Victron en este momento" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Puerto", + "interval": "Intervalo de actualización en (s)", + "advanced": "Habilita el soporte de escritura y permite establecer límites de escritura" + } + }, + "advanced": { + "data": { + "ac_voltage": "El voltaje AC de tu red en V", + "ac_current": "El límite de corriente AC (por fase) de tu red en A", + "dc_voltage": "El voltaje DC de tu batería en V", + "dc_current": "El límite de corriente DC de tu batería en A", + "number_of_phases": "La configuración de fases de tu sistema", + "use_sliders": "Usar controles deslizantes escalonados para entidades numéricas editables" + } + }, + "reconfigure": { + "data": { + "host": "Host", + "port": "Puerto" + } + } + } + }, + "options": { + "step": { + "init_write": { + "data": { + "rescan": "Reescanear dispositivos disponibles. Esto reescaneará todos los dispositivos disponibles", + "interval": "Intervalo de actualización en (s)", + "ac_voltage": "El voltaje AC de tu red en V", + "ac_current": "El límite de corriente AC (por fase) de tu red en A", + "dc_voltage": "El voltaje DC de tu batería en V", + "dc_current": "El límite de corriente DC de tu batería en A", + "number_of_phases": "La configuración de fases de tu sistema", + "use_sliders": "Usar controles deslizantes escalonados para entidades numéricas editables", + "advanced": "Cambiar al modo solo lectura si no está seleccionado (al enviar)" + } + }, + "init_read": { + "data": { + "rescan": "Reescanear dispositivos disponibles. Esto reescaneará todos los dispositivos disponibles", + "interval": "Intervalo de actualización en (s)", + "advanced": "Habilitar soporte de escritura" + } + }, + "advanced": { + "data": { + "ac_voltage": "El voltaje AC de tu red en V", + "ac_current": "El límite de corriente AC de tu red en A", + "dc_voltage": "El voltaje DC de tu batería en V", + "dc_current": "El límite de corriente DC de tu batería en A", + "number_of_phases": "La configuración de fases de tu sistema", + "use_sliders": "Usar controles deslizantes escalonados para entidades numéricas editables" + } + } + } + } +} diff --git a/custom_components/victron/translations/fr.json b/custom_components/victron/translations/fr.json new file mode 100644 index 0000000..5abe455 --- /dev/null +++ b/custom_components/victron/translations/fr.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est déjà configuré" + }, + "error": { + "cannot_connect": "Échec de la connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue", + "already_configured": "Une seule instance de l'intégration Victron est prise en charge pour le moment" + }, + "step": { + "user": { + "data": { + "host": "Hôte", + "port": "Port", + "interval": "Intervalle de mise à jour en (s)", + "advanced": "Active le support en écriture et permet de définir des limites d'écriture" + } + }, + "advanced": { + "data": { + "ac_voltage": "La tension AC de votre réseau en V", + "ac_current": "La limite de courant AC (par phase) de votre réseau en A", + "dc_voltage": "La tension DC de votre batterie en V", + "dc_current": "La limite de courant DC de votre batterie en A", + "number_of_phases": "La configuration de phase de votre système", + "use_sliders": "Utilise des curseurs gradués pour les entités numériques modifiables" + } + }, + "reconfigure": { + "data": { + "host": "Hôte", + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init_write": { + "data": { + "rescan": "Rescanner les appareils disponibles. Cela rescannera tous les appareils disponibles", + "interval": "Intervalle de mise à jour en (s)", + "ac_voltage": "La tension AC de votre réseau en V", + "ac_current": "La limite de courant AC (par phase) de votre réseau en A", + "dc_voltage": "La tension DC de votre batterie en V", + "dc_current": "La limite de courant DC de votre batterie en A", + "number_of_phases": "La configuration de phase de votre système", + "use_sliders": "Utilise des curseurs gradués pour les entités numériques modifiables", + "advanced": "Basculer en mode lecture seule si décoché (lors de l'envoi)" + } + }, + "init_read": { + "data": { + "rescan": "Rescanner les appareils disponibles. Cela rescannera tous les appareils disponibles", + "interval": "Intervalle de mise à jour en (s)", + "advanced": "Activer le support en écriture" + } + }, + "advanced": { + "data": { + "ac_voltage": "La tension AC de votre réseau en V", + "ac_current": "La limite de courant AC de votre réseau en A", + "dc_voltage": "La tension DC de votre batterie en V", + "dc_current": "La limite de courant DC de votre batterie en A", + "number_of_phases": "La configuration de phase de votre système", + "use_sliders": "Utilise des curseurs gradués pour les entités numériques modifiables" + } + } + } + } +} \ No newline at end of file diff --git a/custom_components/victron/translations/it.json b/custom_components/victron/translations/it.json new file mode 100644 index 0000000..05b7a9c --- /dev/null +++ b/custom_components/victron/translations/it.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo è già configurato" + }, + "error": { + "cannot_connect": "Connessione fallita", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore inaspettato", + "already_configured": "È supportata solo un'istanza dell'integrazione Victron al momento" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Porta", + "interval": "Intervallo di aggiornamento in (s)", + "advanced": "Abilita il supporto in scrittura e consente di impostare limiti di scrittura" + } + }, + "advanced": { + "data": { + "ac_voltage": "La tensione AC della tua rete in V", + "ac_current": "Il limite di corrente AC (per fase) della tua rete in A", + "dc_voltage": "La tensione DC della tua batteria in V", + "dc_current": "Il limite di corrente DC della tua batteria in A", + "number_of_phases": "La configurazione delle fasi del tuo sistema", + "use_sliders": "Utilizza cursori a passi per entità numeriche scrivibili" + } + }, + "reconfigure": { + "data": { + "host": "Host", + "port": "Porta" + } + } + } + }, + "options": { + "step": { + "init_write": { + "data": { + "rescan": "Esegui nuovamente la scansione dei dispositivi disponibili. Questo eseguirà la scansione di tutti i dispositivi disponibili", + "interval": "Intervallo di aggiornamento in (s)", + "ac_voltage": "La tensione AC della tua rete in V", + "ac_current": "Il limite di corrente AC (per fase) della tua rete in A", + "dc_voltage": "La tensione DC della tua batteria in V", + "dc_current": "Il limite di corrente DC della tua batteria in A", + "number_of_phases": "La configurazione delle fasi del tuo sistema", + "use_sliders": "Utilizza cursori a passi per entità numeriche scrivibili", + "advanced": "Passa alla modalità sola lettura se deselezionato (al momento dell'invio)" + } + }, + "init_read": { + "data": { + "rescan": "Esegui nuovamente la scansione dei dispositivi disponibili. Questo eseguirà la scansione di tutti i dispositivi disponibili", + "interval": "Intervallo di aggiornamento in (s)", + "advanced": "Abilita il supporto in scrittura" + } + }, + "advanced": { + "data": { + "ac_voltage": "La tensione AC della tua rete in V", + "ac_current": "Il limite di corrente AC della tua rete in A", + "dc_voltage": "La tensione DC della tua batteria in V", + "dc_current": "Il limite di corrente DC della tua batteria in A", + "number_of_phases": "La configurazione delle fasi del tuo sistema", + "use_sliders": "Utilizza cursori a passi per entità numeriche scrivibili" + } + } + } + } +} \ No newline at end of file diff --git a/custom_components/victron/translations/sv.json b/custom_components/victron/translations/sv.json new file mode 100644 index 0000000..9254244 --- /dev/null +++ b/custom_components/victron/translations/sv.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten är redan konfigurerad" + }, + "error": { + "cannot_connect": "Misslyckades med att ansluta", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Oväntat fel", + "already_configured": "Endast en instans av Victron-integrationen stöds för närvarande" + }, + "step": { + "user": { + "data": { + "host": "Värd", + "port": "Port", + "interval": "Uppdateringsintervall i (s)", + "advanced": "Aktiverar skrivstöd och möjliggör inställning av skrivgränser" + } + }, + "advanced": { + "data": { + "ac_voltage": "Växelspänningen i ditt elnät i V", + "ac_current": "Växelströmbegränsningen (per fas) i ditt elnät i A", + "dc_voltage": "Likspänningen i ditt batteri i V", + "dc_current": "Likströmsbegränsningen i ditt batteri i A", + "number_of_phases": "Faskonfigurationen i ditt system", + "use_sliders": "Använder stegade reglage för skrivbara numeriska värden" + } + }, + "reconfigure": { + "data": { + "host": "Värd", + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init_write": { + "data": { + "rescan": "Skanna om tillgängliga enheter. Detta kommer att skanna om alla tillgängliga enheter", + "interval": "Uppdateringsintervall i (s)", + "ac_voltage": "Växelspänningen i ditt elnät i V", + "ac_current": "Växelströmbegränsningen (per fas) i ditt elnät i A", + "dc_voltage": "Likspänningen i ditt batteri i V", + "dc_current": "Likströmsbegränsningen i ditt batteri i A", + "number_of_phases": "Faskonfigurationen i ditt system", + "use_sliders": "Använder stegade reglage för skrivbara numeriska värden", + "advanced": "Växla till skrivskyddat läge om det inte är markerat (vid insändning)" + } + }, + "init_read": { + "data": { + "rescan": "Skanna om tillgängliga enheter. Detta kommer att skanna om alla tillgängliga enheter", + "interval": "Uppdateringsintervall i (s)", + "advanced": "Aktivera skrivstöd" + } + }, + "advanced": { + "data": { + "ac_voltage": "Växelspänningen i ditt elnät i V", + "ac_current": "Växelströmbegränsningen i ditt elnät i A", + "dc_voltage": "Likspänningen i ditt batteri i V", + "dc_current": "Likströmsbegränsningen i ditt batteri i A", + "number_of_phases": "Faskonfigurationen i ditt system", + "use_sliders": "Använder stegade reglage för skrivbara numeriska värden" + } + } + } + } +} \ No newline at end of file From 412c332505eb87026d2c632717a066ec39d56b7e Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:37:19 +0000 Subject: [PATCH 25/34] port part of the remcom auto label workflow files. the labels and color definition file needs to be parsed to that current labels remain in place --- .github/workflows/labels.yaml | 21 +++++++++++++++++++++ .github/workflows/pr-labels.yaml | 24 ++++++++++++++++++++++++ .github/workflows/stale.yaml | 8 ++++---- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/labels.yaml create mode 100644 .github/workflows/pr-labels.yaml diff --git a/.github/workflows/labels.yaml b/.github/workflows/labels.yaml new file mode 100644 index 0000000..de5c017 --- /dev/null +++ b/.github/workflows/labels.yaml @@ -0,0 +1,21 @@ +--- +name: Sync labels +# yamllint disable-line rule:truthy +on: + push: + branches: + - main + paths: + - .github/labels.yml + workflow_dispatch: +jobs: + labels: + name: ♻️ Sync labels + runs-on: ubuntu-latest + steps: + - name: ⤵️ Check out code from GitHub + uses: actions/checkout@v4 + - name: 🚀 Run Label Syncer + uses: micnncim/action-label-syncer@v1.3.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pr-labels.yaml b/.github/workflows/pr-labels.yaml new file mode 100644 index 0000000..c932594 --- /dev/null +++ b/.github/workflows/pr-labels.yaml @@ -0,0 +1,24 @@ + +# yamllint disable-line rule:truthy +on: + pull_request_target: + types: + - opened + - labeled + - unlabeled + - synchronize + workflow_call: +jobs: + pr_labels: + name: Verify + runs-on: ubuntu-latest + steps: + - name: 🏷 Verify PR has a valid label + uses: jesusvasquez333/verify-pr-label-action@v1.4.0 + with: + pull-request-number: "${{ github.event.pull_request.number }}" + github-token: "${{ secrets.GITHUB_TOKEN }}" + valid-labels: >- + breaking-change, bugfix, documentation, enhancement, + refactor, performance, new-feature, maintenance, ci, dependencies + disable-reviews: true \ No newline at end of file diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 5ed0171..ee61bf4 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -10,9 +10,9 @@ jobs: - uses: actions/stale@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days' - stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days' - days-before-stale: 30 - days-before-close: 7 + stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days' + stale-pr-message: 'This pull request is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days' + days-before-stale: 60 + days-before-close: 30 operations-per-run: 500 exempt-issue-labels: 'on-hold' \ No newline at end of file From 5f7f694596f179370ccc789bbbb54866b1e03131 Mon Sep 17 00:00:00 2001 From: sfstar Date: Wed, 29 Jan 2025 21:37:55 +0000 Subject: [PATCH 26/34] port bump of checkout action --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 02724a6..afce85e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -5,7 +5,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Get version id: version uses: home-assistant/actions/helpers/version@master From c28aa3666d3be957eac9f3cca3f7b4320272e12d Mon Sep 17 00:00:00 2001 From: sfstar Date: Thu, 30 Jan 2025 18:38:11 +0000 Subject: [PATCH 27/34] only run ruff for PR --- .github/workflows/ruff.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index fefe858..0de43a0 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -1,6 +1,6 @@ name: Ruff -on: [ push, pull_request ] +on: [ pull_request ] jobs: ruff: runs-on: ubuntu-latest From 670322ada071e4e418b54c0a221ccbd7d00d0e77 Mon Sep 17 00:00:00 2001 From: sfstar Date: Thu, 30 Jan 2025 18:39:48 +0000 Subject: [PATCH 28/34] do not run hassfest and hass on push --- .github/workflows/hacs.yml | 1 - .github/workflows/hassfest.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/hacs.yml b/.github/workflows/hacs.yml index ea6cf6f..a9b8265 100644 --- a/.github/workflows/hacs.yml +++ b/.github/workflows/hacs.yml @@ -1,7 +1,6 @@ name: HACS Action on: - push: pull_request: schedule: - cron: "0 0 * * *" diff --git a/.github/workflows/hassfest.yml b/.github/workflows/hassfest.yml index 435962d..b94d633 100644 --- a/.github/workflows/hassfest.yml +++ b/.github/workflows/hassfest.yml @@ -1,7 +1,6 @@ name: Validate with hassfest on: - push: pull_request: schedule: - cron: '0 0 * * *' From 9f6cdd8df961d8458448dbb56f18673b382ae3af Mon Sep 17 00:00:00 2001 From: sfstar Date: Thu, 30 Jan 2025 18:49:30 +0000 Subject: [PATCH 29/34] port latest build.yaml workflow file from remcom fork --- .github/workflows/build.yaml | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index afce85e..ad0674e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,22 +1,38 @@ on: release: types: [published] + jobs: build: + name: 🚀 Release runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: - uses: actions/checkout@v4 + - name: Get version id: version - uses: home-assistant/actions/helpers/version@master - - name: Patch manifest and zip + uses: home-assistant/actions/helpers/version@main + + - name: 🔢 Adjust version number run: | sed -i 's/v0.0.0/${{ steps.version.outputs.version }}/' custom_components/victron/manifest.json - cd custom_components/victron/ - zip ../../victron.zip ./* -x '.*' - - uses: JasonEtco/upload-to-release@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: 📦 Created zipped release package + shell: bash + run: | + cd "${{ github.workspace }}/custom_components/victron" + zip victron.zip ./* -x '.*' + + - name: 🔏 Sign release package + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: ${{ github.workspace }}/custom_components/victron/victron.zip + + - name: ⬆️ Upload zip to release + uses: softprops/action-gh-release@v2.2.1 with: - args: ./victron.zip application/zip \ No newline at end of file + files: ${{ github.workspace }}/custom_components/victron/victron.zip \ No newline at end of file From c9596ac0224804d130c47f96686657f283276db1 Mon Sep 17 00:00:00 2001 From: sfstar Date: Thu, 30 Jan 2025 19:12:19 +0000 Subject: [PATCH 30/34] port release drafter and autolabeler from remcom fork and slightly modify --- .github/release-drafter.yml | 67 ++++++++++++++++++++++++++ .github/workflows/labels.yaml | 3 ++ .github/workflows/pr-labels.yaml | 3 ++ .github/workflows/release-drafter.yaml | 22 +++++++++ 4 files changed, 95 insertions(+) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/release-drafter.yaml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..91e9c5d --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,67 @@ +--- +name-template: "v$RESOLVED_VERSION" +tag-template: "v$RESOLVED_VERSION" +change-template: "- $TITLE @$AUTHOR (#$NUMBER)" +sort-direction: ascending + +categories: + - title: "🚨 Breaking changes" + labels: + - "breaking-change" + - title: "🐛 Bug fixes" + labels: + - "bugfix" + - title: "✨ New features" + labels: + - "new-feature" + - title: "🚀 Enhancements" + collapse-after: 4 + labels: + - "enhancement" + - "refactor" + - "performance" + - title: "🧰 Maintenance" + collapse-after: 4 + labels: + - "maintenance" + - "ci" + - title: "📚 Documentation" + collapse-after: 4 + labels: + - "documentation" + - title: "⬆️ Dependency updates" + collapse-after: 4 + labels: + - "dependencies" + +version-resolver: + major: + labels: + - "major" + - "breaking-change" + minor: + labels: + - "minor" + - "new-feature" + patch: + labels: + - "bugfix" + - "chore" + - "ci" + - "decoding" + - "dependencies" + - "documentation" + - "enhancement" + - "performance" + - "refactor" + default: patch + +exclude-contributors: + - sfstar + +template: | + ## Changes + + $CHANGES + + Special thanks to $CONTRIBUTORS for this release! \ No newline at end of file diff --git a/.github/workflows/labels.yaml b/.github/workflows/labels.yaml index de5c017..b4f51e9 100644 --- a/.github/workflows/labels.yaml +++ b/.github/workflows/labels.yaml @@ -1,5 +1,6 @@ --- name: Sync labels + # yamllint disable-line rule:truthy on: push: @@ -8,6 +9,7 @@ on: paths: - .github/labels.yml workflow_dispatch: + jobs: labels: name: ♻️ Sync labels @@ -15,6 +17,7 @@ jobs: steps: - name: ⤵️ Check out code from GitHub uses: actions/checkout@v4 + - name: 🚀 Run Label Syncer uses: micnncim/action-label-syncer@v1.3.0 env: diff --git a/.github/workflows/pr-labels.yaml b/.github/workflows/pr-labels.yaml index c932594..ddf21fb 100644 --- a/.github/workflows/pr-labels.yaml +++ b/.github/workflows/pr-labels.yaml @@ -1,3 +1,5 @@ +--- +name: PR Labels # yamllint disable-line rule:truthy on: @@ -8,6 +10,7 @@ on: - unlabeled - synchronize workflow_call: + jobs: pr_labels: name: Verify diff --git a/.github/workflows/release-drafter.yaml b/.github/workflows/release-drafter.yaml new file mode 100644 index 0000000..2a93b21 --- /dev/null +++ b/.github/workflows/release-drafter.yaml @@ -0,0 +1,22 @@ +--- +name: Release Drafter + +# yamllint disable-line rule:truthy +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + update_release_draft: + permissions: + contents: write + pull-requests: write + name: ✏️ Draft release + runs-on: ubuntu-latest + steps: + - name: 🚀 Run Release Drafter + uses: release-drafter/release-drafter@v6.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 57dbc11c1419a679f72790c8734c5b378eca3416 Mon Sep 17 00:00:00 2001 From: sfstar Date: Thu, 30 Jan 2025 19:16:57 +0000 Subject: [PATCH 31/34] add autolabeler for release drafter --- .github/release-drafter.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 91e9c5d..2a64875 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -64,4 +64,22 @@ template: | $CHANGES - Special thanks to $CONTRIBUTORS for this release! \ No newline at end of file + Special thanks to $CONTRIBUTORS for this release! + + +autolabeler: + - label: 'chore' + files: + - '*.md' + branch: + - '/documentation\/.+/' + - label: 'bug' + branch: + - '/bugfix\/.+/' + title: + - '/bugfix/i' + - label: 'new-feature' + branch: + - '/feature\/.+/' + body: + - '/adds/' From 983bd8c8bfeed917a27be9132a8eff1e9502799c Mon Sep 17 00:00:00 2001 From: sfstar Date: Thu, 30 Jan 2025 19:20:08 +0000 Subject: [PATCH 32/34] port labels.yml config from remcom fork --- .github/labels.yml | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/labels.yml diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..4df1072 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,78 @@ +--- +- name: "breaking-change" + color: ee0701 + description: "A breaking change for existing users." +- name: "bugfix" + color: ee0701 + description: "Inconsistencies or issues which will cause a problem for users or implementors." +- name: "documentation" + color: 0052cc + description: "Solely about the documentation of the project." +- name: "enhancement" + color: 1d76db + description: "Enhancement of the code, not introducing new features." +- name: "refactor" + color: 1d76db + description: "Improvement of existing code, not introducing new features." +- name: "performance" + color: 1d76db + description: "Improving performance, not introducing new features." +- name: "new-feature" + color: 0e8a16 + description: "New features or options." +- name: "maintenance" + color: 2af79e + description: "Generic maintenance tasks." +- name: "ci" + color: 1d76db + description: "Work that improves the continue integration." +- name: "dependencies" + color: 1d76db + description: "Upgrade or downgrade of project dependencies." + +- name: "in-progress" + color: fbca04 + description: "Issue is currently being resolved by a developer." +- name: "stale" + color: fef2c0 + description: "There has not been activity on this issue or PR for quite some time." +- name: "no-stale" + color: fef2c0 + description: "This issue or PR is exempted from the stable bot." + +- name: "security" + color: ee0701 + description: "Marks a security issue that needs to be resolved asap." +- name: "incomplete" + color: fef2c0 + description: "Marks a PR or issue that is missing information." +- name: "invalid" + color: fef2c0 + description: "Marks a PR or issue that is missing information." + +- name: "beginner-friendly" + color: 0e8a16 + description: "Good first issue for people wanting to contribute to the project." +- name: "help-wanted" + color: 0e8a16 + description: "We need some extra helping hands or expertise in order to resolve this." + +- name: "priority-critical" + color: ee0701 + description: "This should be dealt with ASAP. Not fixing this issue would be a serious error." +- name: "priority-high" + color: b60205 + description: "After critical issues are fixed, these should be dealt with before any further issues." +- name: "priority-medium" + color: 0e8a16 + description: "This issue may be useful, and needs some attention." +- name: "priority-low" + color: e4ea8a + description: "Nice addition, maybe... someday..." + +- name: "major" + color: b60205 + description: "This PR causes a major version bump in the version number." +- name: "minor" + color: 0e8a16 + description: "This PR causes a minor version bump in the version number." \ No newline at end of file From e10cf699b04367e4ca2634499a07877a8f52e244 Mon Sep 17 00:00:00 2001 From: sfstar Date: Thu, 30 Jan 2025 19:28:35 +0000 Subject: [PATCH 33/34] add already existing labels where advantagous to labels.yml config --- .github/labels.yml | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/labels.yml b/.github/labels.yml index 4df1072..ae6e74b 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -75,4 +75,31 @@ description: "This PR causes a major version bump in the version number." - name: "minor" color: 0e8a16 - description: "This PR causes a minor version bump in the version number." \ No newline at end of file + description: "This PR causes a minor version bump in the version number." + + +- name: "shipped" + color: 006b75 + description: "This issue has been shipped. 🚢" +- name: "on-hold" + color: 0052cc + description: "This issue is on hold and will not be worked on until further notice." +- name: "duplicate" + color: fef2c0 + description: "This reported issue already exists." +- name: "wontfix" + color: ffffff + description: "This issue will not be fixed." + +- name: "connection stability" + color: 32AE82 + description: "Issue related to the stability of the connection." +- name: "data accuracy" + color: 32AE82 + description: "Issue related to the format or representation of returned/to be returned data." +- name: "specsheet mismatch" + color: d93f0b + description: "Issue related to the mismatch between the victron specsheet and the actual implementation." +- name: "shipping next release (do not close)🚢" + color: b60205 + description: "This issue is scheduled to be shipped in the next release." \ No newline at end of file From 46cf17f1ebbabfd8e6f6af483bb2aebeefe2c8c5 Mon Sep 17 00:00:00 2001 From: sfstar Date: Sun, 2 Feb 2025 13:00:24 +0000 Subject: [PATCH 34/34] change version in manifest to v0.0.0 --- custom_components/victron/hub.py | 1 + custom_components/victron/manifest.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/victron/hub.py b/custom_components/victron/hub.py index 84f384b..64f9dd5 100644 --- a/custom_components/victron/hub.py +++ b/custom_components/victron/hub.py @@ -1,4 +1,5 @@ """Support for Victron Energy devices.""" + from collections import OrderedDict import logging import threading diff --git a/custom_components/victron/manifest.json b/custom_components/victron/manifest.json index 53687a2..63a96f9 100644 --- a/custom_components/victron/manifest.json +++ b/custom_components/victron/manifest.json @@ -15,6 +15,6 @@ "pymodbus>=3.7.4" ], "ssdp": [], - "version": "v0.5.0", + "version": "v0.0.0", "zeroconf": [] }