Skip to content

Commit 91c7366

Browse files
authored
Add channel accessibility check (#24)
* Add channel accessibility check * Add fixtures * Add ruff linter * Fix fixtures path
1 parent a5b128f commit 91c7366

23 files changed

Lines changed: 1095 additions & 157 deletions

.ruff.toml

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,113 @@
33
target-version = "py310"
44

55
select = [
6-
"B007", # Loop control variable {name} not used within loop body
7-
"B014", # Exception handler with duplicate exception
8-
"C", # complexity
9-
"D", # docstrings
10-
"E", # pycodestyle
11-
"F", # pyflakes/autoflake
12-
"ICN001", # import concentions; {name} should be imported as {asname}
6+
"B002", # Python does not support the unary prefix increment
7+
"B007", # Loop control variable {name} not used within loop body
8+
"B014", # Exception handler with duplicate exception
9+
"B023", # Function definition does not bind loop variable {name}
10+
"B026", # Star-arg unpacking after a keyword argument is strongly discouraged
11+
"B904", # Use raise from to specify exception cause
12+
"C", # complexity
13+
"COM818", # Trailing comma on bare tuple prohibited
14+
"D", # docstrings
15+
"DTZ003", # Use datetime.now(tz=) instead of datetime.utcnow()
16+
"DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts)
17+
"E", # pycodestyle
18+
"F", # pyflakes/autoflake
19+
"G", # flake8-logging-format
20+
"I", # isort
21+
"ICN001", # import concentions; {name} should be imported as {asname}
22+
"N804", # First argument of a class method should be named cls
23+
"N805", # First argument of a method should be named self
24+
"N815", # Variable {name} in class scope should not be mixedCase
1325
"PGH004", # Use specific rule codes when using noqa
1426
"PLC0414", # Useless import alias. Import alias does not rename original package.
15-
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
16-
"SIM117", # Merge with-statements that use the same scope
17-
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
18-
"SIM201", # Use {left} != {right} instead of not {left} == {right}
19-
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
20-
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
21-
"SIM401", # Use get from dict with default instead of an if block
22-
"T20", # flake8-print
23-
"TRY004", # Prefer TypeError exception for invalid type
24-
"RUF006", # Store a reference to the return value of asyncio.create_task
25-
"UP", # pyupgrade
26-
"W", # pycodestyle
27+
"PLC", # pylint
28+
"PLE", # pylint
29+
"PLR", # pylint
30+
"PLW", # pylint
31+
"Q000", # Double quotes found but single quotes preferred
32+
"RUF006", # Store a reference to the return value of asyncio.create_task
33+
"S102", # Use of exec detected
34+
"S103", # bad-file-permissions
35+
"S108", # hardcoded-temp-file
36+
"S306", # suspicious-mktemp-usage
37+
"S307", # suspicious-eval-usage
38+
"S313", # suspicious-xmlc-element-tree-usage
39+
"S314", # suspicious-xml-element-tree-usage
40+
"S315", # suspicious-xml-expat-reader-usage
41+
"S316", # suspicious-xml-expat-builder-usage
42+
"S317", # suspicious-xml-sax-usage
43+
"S318", # suspicious-xml-mini-dom-usage
44+
"S319", # suspicious-xml-pull-dom-usage
45+
"S320", # suspicious-xmle-tree-usage
46+
"S601", # paramiko-call
47+
"S602", # subprocess-popen-with-shell-equals-true
48+
"S604", # call-with-shell-equals-true
49+
"S608", # hardcoded-sql-expression
50+
"S609", # unix-command-wildcard-injection
51+
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
52+
"SIM117", # Merge with-statements that use the same scope
53+
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
54+
"SIM201", # Use {left} != {right} instead of not {left} == {right}
55+
"SIM208", # Use {expr} instead of not (not {expr})
56+
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
57+
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
58+
"SIM401", # Use get from dict with default instead of an if block
59+
"T100", # Trace found: {name} used
60+
"T20", # flake8-print
61+
"TID251", # Banned imports
62+
"TRY004", # Prefer TypeError exception for invalid type
63+
"TRY302", # Remove exception handler; error is immediately re-raised
64+
"UP", # pyupgrade
65+
"W", # pycodestyle
2766
]
2867

2968
ignore = [
30-
"D202", # No blank lines allowed after function docstring
31-
"D203", # 1 blank line required before class docstring
32-
"D213", # Multi-line docstring summary should start at the second line
33-
"D404", # First word of the docstring should not be This
34-
"D406", # Section name should end with a newline
35-
"D407", # Section name underlining
36-
"D411", # Missing blank line before section
37-
"E501", # line too long
38-
"E731", # do not assign a lambda expression, use a def
69+
"D202", # No blank lines allowed after function docstring
70+
"D203", # 1 blank line required before class docstring
71+
"D213", # Multi-line docstring summary should start at the second line
72+
"D406", # Section name should end with a newline
73+
"D407", # Section name underlining
74+
"E501", # line too long
75+
"E731", # do not assign a lambda expression, use a def
76+
77+
# Ignore ignored, as the rule is now back in preview/nursery, which cannot
78+
# be ignored anymore without warnings.
79+
# https://github.com/astral-sh/ruff/issues/7491
80+
# "PLC1901", # Lots of false positives
81+
82+
# False positives https://github.com/astral-sh/ruff/issues/5386
83+
"PLC0208", # Use a sequence type instead of a `set` when iterating over values
84+
"PLR0911", # Too many return statements ({returns} > {max_returns})
85+
"PLR0912", # Too many branches ({branches} > {max_branches})
86+
"PLR0913", # Too many arguments to function call ({c_args} > {max_args})
87+
"PLR0915", # Too many statements ({statements} > {max_statements})
88+
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
89+
"PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target
90+
"UP006", # keep type annotation style as is
91+
"UP007", # keep type annotation style as is
92+
# Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923
93+
"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
94+
95+
# May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
96+
"W191",
97+
"E111",
98+
"E114",
99+
"E117",
100+
"D206",
101+
"D300",
102+
"Q000",
103+
"Q001",
104+
"Q002",
105+
"Q003",
106+
"COM812",
107+
"COM819",
108+
"ISC001",
109+
"ISC002",
110+
111+
# Disabled because ruff does not understand type of __all__ generated by a function
112+
"PLE0605",
39113
]
40114

41115
[flake8-pytest-style]

custom_components/smaev/__init__.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import pysmaev.core
88
import pysmaev.exceptions
9-
109
from homeassistant.config_entries import ConfigEntry
1110
from homeassistant.const import (
1211
CONF_HOST,
@@ -19,20 +18,21 @@
1918
from homeassistant.core import HomeAssistant
2019
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
2120
from homeassistant.helpers.aiohttp_client import async_get_clientsession
22-
from homeassistant.helpers.entity import async_generate_entity_id, EntityDescription
2321
from homeassistant.helpers.device_registry import DeviceInfo
24-
22+
from homeassistant.helpers.entity import EntityDescription, async_generate_entity_id
2523

2624
from .const import (
2725
DOMAIN,
26+
SMAEV_CHANNELS,
2827
SMAEV_COORDINATOR,
2928
SMAEV_DEVICE_INFO,
29+
SMAEV_MEASUREMENT,
3030
SMAEV_OBJECT,
31+
SMAEV_PARAMETER,
3132
)
3233
from .coordinator import SmaEvChargerCoordinator
3334
from .services import async_setup_services, async_unload_services
3435

35-
3636
PLATFORMS: list[Platform] = [
3737
Platform.DATETIME,
3838
Platform.NUMBER,
@@ -79,12 +79,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
7979
sw_version=smaev_device_info["sw_version"],
8080
)
8181

82+
measurement_channels = await evcharger.get_measurement_channels()
83+
parameter_channels = await evcharger.get_parameter_channels()
84+
8285
coordinator = SmaEvChargerCoordinator(hass, entry, evcharger)
8386

8487
hass.data[DOMAIN][entry.entry_id] = {
8588
SMAEV_OBJECT: evcharger,
8689
SMAEV_DEVICE_INFO: device_info,
8790
SMAEV_COORDINATOR: coordinator,
91+
SMAEV_CHANNELS: {
92+
SMAEV_MEASUREMENT: measurement_channels,
93+
SMAEV_PARAMETER: parameter_channels,
94+
},
8895
}
8996

9097
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

custom_components/smaev/config_flow.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
"""Config flow for SMA EV Charger integration."""
22
from __future__ import annotations
33

4-
from collections.abc import Mapping
54
import logging
5+
from collections.abc import Mapping
66
from typing import Any
77

8+
import homeassistant.helpers.config_validation as cv
89
import pysmaev.core
910
import pysmaev.exceptions
10-
1111
import voluptuous as vol
12-
1312
from homeassistant import config_entries
14-
from homeassistant.core import callback
1513
from homeassistant.const import (
1614
CONF_BASE,
1715
CONF_HOST,
@@ -20,10 +18,9 @@
2018
CONF_USERNAME,
2119
CONF_VERIFY_SSL,
2220
)
23-
from homeassistant.core import HomeAssistant
21+
from homeassistant.core import HomeAssistant, callback
2422
from homeassistant.data_entry_flow import FlowResult
2523
from homeassistant.helpers.aiohttp_client import async_get_clientsession
26-
import homeassistant.helpers.config_validation as cv
2724

2825
from .const import DOMAIN
2926

custom_components/smaev/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Constants for the SMA EV Charger integration."""
22
DOMAIN = "smaev"
33

4+
SMAEV_CHANNELS = "channels"
45
SMAEV_COORDINATOR = "coordinator"
56
SMAEV_OBJECT = "pysmaev"
67
SMAEV_DEVICE_INFO = "device_info"

custom_components/smaev/coordinator.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
"""DataUpdateCoordinator for the SMA EV Charger integration."""
2-
from datetime import timedelta
32
import logging
4-
5-
from pysmaev.core import SmaEvCharger
6-
from pysmaev.exceptions import SmaEvChargerConnectionError, SmaEvChargerException
3+
from datetime import timedelta
74

85
from homeassistant.config_entries import ConfigEntry
96
from homeassistant.const import CONF_SCAN_INTERVAL
107
from homeassistant.core import HomeAssistant, callback
118
from homeassistant.helpers import device_registry as dr
129
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
10+
from pysmaev.core import SmaEvCharger
11+
from pysmaev.exceptions import SmaEvChargerConnectionError, SmaEvChargerException
1312

1413
from .const import (
15-
DOMAIN,
1614
DEFAULT_SCAN_INTERVAL,
15+
DOMAIN,
16+
SMAEV_COORDINATOR,
1717
SMAEV_MEASUREMENT,
1818
SMAEV_PARAMETER,
19-
SMAEV_COORDINATOR,
2019
)
2120

22-
2321
_LOGGER = logging.getLogger(__name__)
2422

2523

@@ -41,8 +39,8 @@ async def _async_update_data(self):
4139
if self.evcharger.is_closed:
4240
try:
4341
await self.evcharger.open()
44-
except SmaEvChargerException:
45-
raise UpdateFailed("Connection to device lost.")
42+
except SmaEvChargerException as exc:
43+
raise UpdateFailed("Connection to device lost.") from exc
4644

4745
data = {}
4846
try:

custom_components/smaev/datetime.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
"""DateTime platform for SMA EV Charger integration."""
22
from __future__ import annotations
33

4+
import logging
45
from dataclasses import dataclass
56
from datetime import UTC, datetime
6-
import logging
77
from typing import TYPE_CHECKING
88

9-
from pysmaev.helpers import get_parameters_channel
10-
119
from homeassistant.components.datetime import (
1210
ENTITY_ID_FORMAT,
1311
DateTimeEntity,
@@ -21,10 +19,12 @@
2119
CoordinatorEntity,
2220
DataUpdateCoordinator,
2321
)
22+
from pysmaev.helpers import get_parameters_channel
2423

2524
from . import generate_smaev_entity_id
2625
from .const import (
2726
DOMAIN,
27+
SMAEV_CHANNELS,
2828
SMAEV_COORDINATOR,
2929
SMAEV_DEVICE_INFO,
3030
SMAEV_PARAMETER,
@@ -70,11 +70,17 @@ async def async_setup_entry(
7070
entities = []
7171

7272
for entity_description in DATETIME_DESCRIPTIONS:
73-
entities.append(
74-
SmaEvChargerDateTime(
75-
hass, coordinator, config_entry, device_info, entity_description
73+
if entity_description.channel in data[SMAEV_CHANNELS][entity_description.type]:
74+
entities.append(
75+
SmaEvChargerDateTime(
76+
hass, coordinator, config_entry, device_info, entity_description
77+
)
78+
)
79+
else:
80+
_LOGGER.warning(
81+
"Channel '%s' is not accessible. Elevated rights might be required.",
82+
entity_description.channel,
7683
)
77-
)
7884

7985
async_add_entities(entities)
8086

custom_components/smaev/device_action.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
from __future__ import annotations
33

44
import voluptuous as vol
5-
65
from homeassistant.const import (
76
ATTR_DEVICE_ID,
87
CONF_DEVICE_ID,
98
CONF_DOMAIN,
109
CONF_TYPE,
1110
)
1211
from homeassistant.core import Context, HomeAssistant
13-
from homeassistant.helpers import config_validation as cv, device_registry as dr
12+
from homeassistant.helpers import config_validation as cv
13+
from homeassistant.helpers import device_registry as dr
1414

1515
from . import DOMAIN
1616
from .const import SERVICE_RESTART

0 commit comments

Comments
 (0)