mirror of
https://github.com/home-assistant/core.git
synced 2026-06-17 17:32:51 +02:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44f5ad84b9 | |||
| 7330c25685 | |||
| e59086d299 | |||
| 27f44f83fb | |||
| 05c94fa578 | |||
| 64b608b439 | |||
| c5b90cf8d1 | |||
| 31259725ec | |||
| 8ad7c12405 | |||
| 66f0f170b7 | |||
| da91865130 | |||
| 3437bcfb42 | |||
| 1fd5d0a5fd | |||
| 404c58435a | |||
| 7aba1daa16 | |||
| 12397cc4c1 | |||
| 73cdf7e067 | |||
| 4e2cfecd96 | |||
| 4625f7de27 | |||
| 53a1db405c | |||
| ff7262d36f | |||
| 54feb95b76 | |||
| d9e2b49c0c | |||
| 4f9051464d | |||
| 87894fd623 | |||
| 34a70a9210 | |||
| c9fb6a13fb | |||
| 1601b5151c | |||
| da0e23093d |
Generated
+2
-2
@@ -1167,8 +1167,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/mutesync/ @currentoor
|
||||
/homeassistant/components/my/ @home-assistant/core
|
||||
/tests/components/my/ @home-assistant/core
|
||||
/homeassistant/components/myneomitis/ @l-pr
|
||||
/tests/components/myneomitis/ @l-pr
|
||||
/homeassistant/components/myneomitis/ @Epyes
|
||||
/tests/components/myneomitis/ @Epyes
|
||||
/homeassistant/components/mysensors/ @MartinHjelmare @functionpointer
|
||||
/tests/components/mysensors/ @MartinHjelmare @functionpointer
|
||||
/homeassistant/components/mystrom/ @fabaff
|
||||
|
||||
@@ -210,7 +210,7 @@ async def async_generate_image(
|
||||
|
||||
source = hass.data[DATA_MEDIA_SOURCE]
|
||||
|
||||
current_time = datetime.now()
|
||||
current_time = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
ext = mimetypes.guess_extension(task_result.mime_type, False) or ".png"
|
||||
sanitized_task_name = RE_SANITIZE_FILENAME.sub("", slugify(task_name))
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==14.0.4"]
|
||||
"requirements": ["aioamazondevices==14.1.2"]
|
||||
}
|
||||
|
||||
@@ -34,11 +34,13 @@ def generate_site_selector_name(site: Site) -> str:
|
||||
|
||||
|
||||
def filter_sites(sites: list[Site]) -> list[Site]:
|
||||
"""Deduplicates the list of sites."""
|
||||
"""Filter out closed sites and deduplicate the list of sites."""
|
||||
filtered: list[Site] = []
|
||||
filtered_nmi: set[str] = set()
|
||||
|
||||
for site in sorted(sites, key=lambda site: site.status):
|
||||
if site.status == SiteStatus.CLOSED:
|
||||
continue
|
||||
if site.status == SiteStatus.ACTIVE or site.nmi not in filtered_nmi:
|
||||
filtered.append(site)
|
||||
filtered_nmi.add(site.nmi)
|
||||
|
||||
@@ -75,7 +75,7 @@ class AquaCellConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
**user_input,
|
||||
CONF_BRAND: user_input[CONF_BRAND],
|
||||
CONF_REFRESH_TOKEN: refresh_token,
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
},
|
||||
)
|
||||
|
||||
@@ -119,7 +119,7 @@ class AquaCellConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
data_updates={
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
CONF_REFRESH_TOKEN: refresh_token,
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class AquacellCoordinator(DataUpdateCoordinator[dict[str, Softener]]):
|
||||
+ REFRESH_TOKEN_EXPIRY_TIME.total_seconds()
|
||||
)
|
||||
try:
|
||||
if datetime.now().timestamp() >= expiry_time:
|
||||
if datetime.now().timestamp() >= expiry_time: # pylint: disable=home-assistant-enforce-naive-now
|
||||
await self._reauthenticate()
|
||||
else:
|
||||
await self.aquacell_api.authenticate_refresh(self.refresh_token)
|
||||
@@ -92,7 +92,7 @@ class AquacellCoordinator(DataUpdateCoordinator[dict[str, Softener]]):
|
||||
data = {
|
||||
**self.config_entry.data,
|
||||
CONF_REFRESH_TOKEN: self.refresh_token,
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
}
|
||||
|
||||
self.hass.config_entries.async_update_entry(self.config_entry, data=data)
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyaqvify"],
|
||||
"quality_scale": "silver",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyaqvify==0.0.11"]
|
||||
}
|
||||
|
||||
@@ -53,29 +53,43 @@ rules:
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
devices: todo
|
||||
diagnostics: todo
|
||||
discovery-update-info: todo
|
||||
discovery: todo
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices: todo
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: todo
|
||||
dynamic-devices: todo
|
||||
entity-category: todo
|
||||
entity-device-class: todo
|
||||
entity-disabled-by-default: todo
|
||||
entity-translations: todo
|
||||
exception-translations: todo
|
||||
devices: done
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: |
|
||||
Discovery not possible, as device is connected via 4G only. No LAN connection.
|
||||
discovery:
|
||||
status: exempt
|
||||
comment: |
|
||||
Discovery not possible, as device is connected via 4G only. No LAN connection.
|
||||
docs-data-update: done
|
||||
docs-examples: done
|
||||
docs-known-limitations:
|
||||
status: done
|
||||
comment: |
|
||||
No known limitations
|
||||
docs-supported-devices: done
|
||||
docs-supported-functions: done
|
||||
docs-troubleshooting: done
|
||||
docs-use-cases: done
|
||||
dynamic-devices: done
|
||||
entity-category:
|
||||
status: done
|
||||
comment: |
|
||||
None of current sensors should be set as diagnostic
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: todo
|
||||
stale-devices: todo
|
||||
|
||||
reconfiguration-flow: done
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: |
|
||||
No repair issues are created.
|
||||
stale-devices: done
|
||||
# Platinum
|
||||
async-dependency: todo
|
||||
inject-websession: todo
|
||||
strict-typing: todo
|
||||
async-dependency: done
|
||||
inject-websession: done
|
||||
strict-typing: done
|
||||
|
||||
@@ -61,7 +61,7 @@ def _activity_time_based(latest: Activity) -> Activity | None:
|
||||
"""Get the latest state of the sensor."""
|
||||
start = latest.activity_start_time
|
||||
end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION
|
||||
if start <= datetime.now() <= end:
|
||||
if start <= datetime.now() <= end: # pylint: disable=home-assistant-enforce-naive-now
|
||||
return latest
|
||||
return None
|
||||
|
||||
|
||||
@@ -239,11 +239,11 @@ class BraviaTVCoordinator(DataUpdateCoordinator[None]):
|
||||
self.source = None
|
||||
if start_datetime := playing_info.get("startDateTime"):
|
||||
start_datetime = datetime.fromisoformat(start_datetime)
|
||||
current_datetime = datetime.now().replace(tzinfo=start_datetime.tzinfo)
|
||||
current_datetime = datetime.now().replace(tzinfo=start_datetime.tzinfo) # pylint: disable=home-assistant-enforce-naive-now
|
||||
self.media_position = int(
|
||||
(current_datetime - start_datetime).total_seconds()
|
||||
)
|
||||
self.media_position_updated_at = datetime.now()
|
||||
self.media_position_updated_at = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
else:
|
||||
self.media_position = None
|
||||
self.media_position_updated_at = None
|
||||
|
||||
@@ -31,7 +31,7 @@ class BroadlinkHeartbeat:
|
||||
async def async_setup(self) -> None:
|
||||
"""Set up the heartbeat."""
|
||||
if self._unsubscribe is None:
|
||||
await self.async_heartbeat(dt.datetime.now())
|
||||
await self.async_heartbeat(dt.datetime.now()) # pylint: disable=home-assistant-enforce-naive-now
|
||||
self._unsubscribe = event.async_track_time_interval(
|
||||
self._hass, self.async_heartbeat, self.HEARTBEAT_INTERVAL
|
||||
)
|
||||
|
||||
@@ -158,7 +158,7 @@ class BrData:
|
||||
|
||||
_LOGGER.debug("Buienradar parsed data: %s", result)
|
||||
if result.get(SUCCESS) is not True:
|
||||
if int(datetime.now().strftime("%H")) > 0:
|
||||
if int(datetime.now().strftime("%H")) > 0: # pylint: disable=home-assistant-enforce-naive-now
|
||||
_LOGGER.warning(
|
||||
"Unable to parse data from Buienradar. (Msg: %s)",
|
||||
result.get(MESSAGE),
|
||||
|
||||
@@ -546,11 +546,6 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
||||
"%s: Dropping sample as source total_increasing sensor decreased",
|
||||
self.entity_id,
|
||||
)
|
||||
self._last_valid_state_time = (
|
||||
new_state.state,
|
||||
new_timestamp,
|
||||
)
|
||||
self._write_native_value(Decimal(0))
|
||||
return
|
||||
|
||||
# add latest derivative to the window list
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["elevenlabs"],
|
||||
"requirements": ["elevenlabs==2.3.0", "sentence-stream==1.2.0"]
|
||||
"requirements": ["elevenlabs==2.51.0", "sentence-stream==1.3.0"]
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class FireflyDataUpdateCoordinator(DataUpdateCoordinator[FireflyCoordinatorData]
|
||||
|
||||
async def _async_update_data(self) -> FireflyCoordinatorData:
|
||||
"""Fetch data from Firefly III API."""
|
||||
now = datetime.now()
|
||||
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
end_date = now
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ class FreeboxRouter:
|
||||
"IPv6": connection_datas.get("ipv6"),
|
||||
"connection_type": connection_datas["media"],
|
||||
"uptime": datetime.fromtimestamp(
|
||||
round(datetime.now().timestamp()) - syst_datas["uptime_val"]
|
||||
round(datetime.now().timestamp()) - syst_datas["uptime_val"] # pylint: disable=home-assistant-enforce-naive-now
|
||||
),
|
||||
"firmware_version": self._sw_v,
|
||||
"serial": syst_datas["serial"],
|
||||
|
||||
@@ -54,7 +54,7 @@ class FytaCoordinator(DataUpdateCoordinator[dict[int, Plant]]):
|
||||
|
||||
if (
|
||||
self.fyta.expiration is None
|
||||
or self.fyta.expiration.timestamp() < datetime.now().timestamp()
|
||||
or self.fyta.expiration.timestamp() < datetime.now().timestamp() # pylint: disable=home-assistant-enforce-naive-now
|
||||
):
|
||||
await self.renew_authentication()
|
||||
|
||||
|
||||
@@ -117,5 +117,5 @@ class FytaPlantImageEntity(FytaPlantEntity, ImageEntity):
|
||||
|
||||
if url != self._attr_image_url:
|
||||
self._cached_image = None
|
||||
self._attr_image_last_updated = datetime.now()
|
||||
self._attr_image_last_updated = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
return url
|
||||
|
||||
@@ -154,12 +154,12 @@ class GenericCamera(Camera):
|
||||
self._last_image is not None
|
||||
and url == self._last_url
|
||||
and self._last_update + timedelta(0, self._attr_frame_interval)
|
||||
> datetime.now()
|
||||
> datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
):
|
||||
return self._last_image
|
||||
|
||||
try:
|
||||
update_time = datetime.now()
|
||||
update_time = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
async_client = get_async_client(self.hass, verify_ssl=self.verify_ssl)
|
||||
response = await async_client.get(
|
||||
url,
|
||||
|
||||
@@ -584,7 +584,7 @@ async def ws_start_preview(
|
||||
if user_input.get(CONF_STILL_IMAGE_URL):
|
||||
ha_still_url = (
|
||||
"/api/generic/preview_flow_image"
|
||||
f"/{msg['flow_id']}?t={datetime.now().isoformat()}"
|
||||
f"/{msg['flow_id']}?t={datetime.now().isoformat()}" # pylint: disable=home-assistant-enforce-naive-now
|
||||
)
|
||||
_LOGGER.debug("Got preview still URL: %s", ha_still_url)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ SYNCHRONIZE_CLOCK = GoodweButtonEntityDescription(
|
||||
key="synchronize_clock",
|
||||
translation_key="synchronize_clock",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
action=lambda inv: inv.write_setting("time", datetime.now()),
|
||||
action=lambda inv: inv.write_setting("time", datetime.now()), # pylint: disable=home-assistant-enforce-naive-now
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: GoogleConfigEntry) -> bo
|
||||
# Force a token refresh to fix a bug where tokens were persisted with
|
||||
# expires_in (relative time delta) and expires_at (absolute time) swapped.
|
||||
# A google session token typically only lasts a few days between refresh.
|
||||
now = datetime.now()
|
||||
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
if session.token["expires_at"] >= (now + timedelta(days=365)).timestamp():
|
||||
session.token["expires_in"] = 0
|
||||
session.token["expires_at"] = now.timestamp()
|
||||
|
||||
@@ -45,7 +45,7 @@ async def async_get_config_entry_diagnostics(
|
||||
payload: dict[str, Any] = {
|
||||
"now": dt_util.now().isoformat(),
|
||||
"timezone": str(dt_util.get_default_time_zone()),
|
||||
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo),
|
||||
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo), # pylint: disable=home-assistant-enforce-naive-now
|
||||
}
|
||||
|
||||
store = config_entry.runtime_data.store
|
||||
|
||||
@@ -434,49 +434,56 @@ async def google_generative_ai_config_option_schema(
|
||||
description={"suggested_value": options.get(CONF_TEMPERATURE)},
|
||||
default=RECOMMENDED_TEMPERATURE,
|
||||
): NumberSelector(NumberSelectorConfig(min=0, max=2, step=0.05)),
|
||||
vol.Optional(
|
||||
CONF_TOP_P,
|
||||
description={"suggested_value": options.get(CONF_TOP_P)},
|
||||
default=RECOMMENDED_TOP_P,
|
||||
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
|
||||
vol.Optional(
|
||||
CONF_TOP_K,
|
||||
description={"suggested_value": options.get(CONF_TOP_K)},
|
||||
default=RECOMMENDED_TOP_K,
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_MAX_TOKENS,
|
||||
description={"suggested_value": options.get(CONF_MAX_TOKENS)},
|
||||
default=RECOMMENDED_MAX_TOKENS,
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_HARASSMENT_BLOCK_THRESHOLD,
|
||||
description={
|
||||
"suggested_value": options.get(CONF_HARASSMENT_BLOCK_THRESHOLD)
|
||||
},
|
||||
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
|
||||
): harm_block_thresholds_selector,
|
||||
vol.Optional(
|
||||
CONF_HATE_BLOCK_THRESHOLD,
|
||||
description={"suggested_value": options.get(CONF_HATE_BLOCK_THRESHOLD)},
|
||||
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
|
||||
): harm_block_thresholds_selector,
|
||||
vol.Optional(
|
||||
CONF_SEXUAL_BLOCK_THRESHOLD,
|
||||
description={
|
||||
"suggested_value": options.get(CONF_SEXUAL_BLOCK_THRESHOLD)
|
||||
},
|
||||
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
|
||||
): harm_block_thresholds_selector,
|
||||
vol.Optional(
|
||||
CONF_DANGEROUS_BLOCK_THRESHOLD,
|
||||
description={
|
||||
"suggested_value": options.get(CONF_DANGEROUS_BLOCK_THRESHOLD)
|
||||
},
|
||||
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
|
||||
): harm_block_thresholds_selector,
|
||||
}
|
||||
)
|
||||
if subentry_type != "tts":
|
||||
schema.update(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_TOP_P,
|
||||
description={"suggested_value": options.get(CONF_TOP_P)},
|
||||
default=RECOMMENDED_TOP_P,
|
||||
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
|
||||
vol.Optional(
|
||||
CONF_TOP_K,
|
||||
description={"suggested_value": options.get(CONF_TOP_K)},
|
||||
default=RECOMMENDED_TOP_K,
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_MAX_TOKENS,
|
||||
description={"suggested_value": options.get(CONF_MAX_TOKENS)},
|
||||
default=RECOMMENDED_MAX_TOKENS,
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_HARASSMENT_BLOCK_THRESHOLD,
|
||||
description={
|
||||
"suggested_value": options.get(CONF_HARASSMENT_BLOCK_THRESHOLD)
|
||||
},
|
||||
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
|
||||
): harm_block_thresholds_selector,
|
||||
vol.Optional(
|
||||
CONF_HATE_BLOCK_THRESHOLD,
|
||||
description={
|
||||
"suggested_value": options.get(CONF_HATE_BLOCK_THRESHOLD)
|
||||
},
|
||||
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
|
||||
): harm_block_thresholds_selector,
|
||||
vol.Optional(
|
||||
CONF_SEXUAL_BLOCK_THRESHOLD,
|
||||
description={
|
||||
"suggested_value": options.get(CONF_SEXUAL_BLOCK_THRESHOLD)
|
||||
},
|
||||
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
|
||||
): harm_block_thresholds_selector,
|
||||
vol.Optional(
|
||||
CONF_DANGEROUS_BLOCK_THRESHOLD,
|
||||
description={
|
||||
"suggested_value": options.get(CONF_DANGEROUS_BLOCK_THRESHOLD)
|
||||
},
|
||||
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
|
||||
): harm_block_thresholds_selector,
|
||||
}
|
||||
)
|
||||
if subentry_type == "conversation":
|
||||
schema.update(
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ CONF_RECOMMENDED = "recommended"
|
||||
CONF_CHAT_MODEL = "chat_model"
|
||||
RECOMMENDED_CHAT_MODEL = "models/gemini-3.1-flash-lite"
|
||||
RECOMMENDED_STT_MODEL = RECOMMENDED_CHAT_MODEL
|
||||
RECOMMENDED_TTS_MODEL = "models/gemini-2.5-flash-preview-tts"
|
||||
RECOMMENDED_TTS_MODEL = "models/gemini-3.1-flash-tts-preview"
|
||||
RECOMMENDED_IMAGE_MODEL = "models/gemini-2.5-flash-image"
|
||||
CONF_TEMPERATURE = "temperature"
|
||||
RECOMMENDED_TEMPERATURE = 1.0
|
||||
|
||||
@@ -18,7 +18,13 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import CONF_CHAT_MODEL, LOGGER, RECOMMENDED_TTS_MODEL
|
||||
from .const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_TEMPERATURE,
|
||||
LOGGER,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
RECOMMENDED_TTS_MODEL,
|
||||
)
|
||||
from .entity import GoogleGenerativeAILLMBaseEntity
|
||||
from .helpers import convert_to_wav
|
||||
|
||||
@@ -191,7 +197,10 @@ class GoogleGenerativeAITextToSpeechEntity(
|
||||
self, message: str, language: str, options: dict[str, Any]
|
||||
) -> TtsAudioType:
|
||||
"""Load tts audio file from the engine."""
|
||||
config = self.create_generate_content_config()
|
||||
config = types.GenerateContentConfig()
|
||||
config.temperature = self.subentry.data.get(
|
||||
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
|
||||
)
|
||||
config.response_modalities = ["AUDIO"]
|
||||
config.speech_config = types.SpeechConfig(
|
||||
voice_config=types.VoiceConfig(
|
||||
|
||||
@@ -69,7 +69,7 @@ def _append_to_sheet(call: ServiceCall, entry: GoogleSheetsConfigEntry) -> None:
|
||||
worksheet = sheet.worksheet(call.data.get(WORKSHEET, sheet.sheet1.title))
|
||||
columns: list[str] = next(iter(worksheet.get_values("A1:ZZ1")), [])
|
||||
add_created_column = call.data[ADD_CREATED_COLUMN]
|
||||
now = str(datetime.now())
|
||||
now = str(datetime.now()) # pylint: disable=home-assistant-enforce-naive-now
|
||||
rows = []
|
||||
for d in call.data[DATA]:
|
||||
row_data = ({"created": now} | d) if add_created_column else d
|
||||
|
||||
@@ -128,7 +128,7 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
|
||||
"""
|
||||
if not super().available:
|
||||
return False
|
||||
return datetime.now() - self._device.lastseen < DEVICE_TIMEOUT
|
||||
return datetime.now() - self._device.lastseen < DEVICE_TIMEOUT # pylint: disable=home-assistant-enforce-naive-now
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
|
||||
@@ -387,6 +387,6 @@ def build_hass_attribution(sections: list[dict[str, Any]]) -> str | None:
|
||||
def next_datetime(simple_time: time) -> datetime:
|
||||
"""Take a time like 08:00:00 and combine it with the current date."""
|
||||
combined = datetime.combine(dt_util.start_of_local_day(), simple_time)
|
||||
if combined < datetime.now():
|
||||
if combined < datetime.now(): # pylint: disable=home-assistant-enforce-naive-now
|
||||
combined = combined + timedelta(days=1)
|
||||
return combined
|
||||
|
||||
@@ -75,7 +75,7 @@ def format_last_reset_elapsed_seconds(value: str | None) -> datetime | None:
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
last_reset = datetime.now() - timedelta(seconds=int(value))
|
||||
last_reset = datetime.now() - timedelta(seconds=int(value)) # pylint: disable=home-assistant-enforce-naive-now
|
||||
last_reset.replace(microsecond=0)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
@@ -87,11 +87,6 @@ class IcloudTrackerEntity(TrackerEntity):
|
||||
assert self._device.location is not None
|
||||
return self._device.location[DEVICE_LOCATION_LONGITUDE]
|
||||
|
||||
@property
|
||||
def battery_level(self) -> int | None:
|
||||
"""Return the battery level of the device."""
|
||||
return self._device.battery_level
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return the icon."""
|
||||
|
||||
@@ -342,7 +342,7 @@ class iOSIdentifyDeviceView(HomeAssistantView):
|
||||
|
||||
hass = request.app[KEY_HASS]
|
||||
|
||||
data[ATTR_LAST_SEEN_AT] = datetime.datetime.now().isoformat()
|
||||
data[ATTR_LAST_SEEN_AT] = datetime.datetime.now().isoformat() # pylint: disable=home-assistant-enforce-naive-now
|
||||
|
||||
device_id = data[ATTR_DEVICE_ID]
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ class IsraelRailDataUpdateCoordinator(DataUpdateCoordinator[list[DataConnection]
|
||||
self._train_schedule.query,
|
||||
self._start,
|
||||
self._destination,
|
||||
datetime.now().strftime("%Y-%m-%d"),
|
||||
datetime.now().strftime("%H:%M"),
|
||||
datetime.now().strftime("%Y-%m-%d"), # pylint: disable=home-assistant-enforce-naive-now
|
||||
datetime.now().strftime("%H:%M"), # pylint: disable=home-assistant-enforce-naive-now
|
||||
)
|
||||
except Exception as e:
|
||||
raise UpdateFailed(
|
||||
|
||||
@@ -214,7 +214,7 @@ class LgTVDevice(MediaPlayerEntity):
|
||||
def media_image_url(self):
|
||||
"""URL for obtaining a screen capture."""
|
||||
return (
|
||||
f"{self._client.url}data?target=screen_image&_={datetime.now().timestamp()}"
|
||||
f"{self._client.url}data?target=screen_image&_={datetime.now().timestamp()}" # pylint: disable=home-assistant-enforce-naive-now
|
||||
)
|
||||
|
||||
def turn_off(self) -> None:
|
||||
|
||||
@@ -18,7 +18,7 @@ async def async_get_config_entry_diagnostics(
|
||||
payload: dict[str, Any] = {
|
||||
"now": dt_util.now().isoformat(),
|
||||
"timezone": str(dt_util.get_default_time_zone()),
|
||||
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo),
|
||||
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo), # pylint: disable=home-assistant-enforce-naive-now
|
||||
}
|
||||
store = config_entry.runtime_data
|
||||
ics = await store.async_load()
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["mitsubishi-comfort==0.3.1"]
|
||||
"requirements": ["mitsubishi-comfort==0.3.2"]
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]):
|
||||
async def _async_update_data(self) -> MonarchData:
|
||||
"""Fetch data for all accounts."""
|
||||
|
||||
now = datetime.now()
|
||||
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
|
||||
account_data, cashflow_summary = await asyncio.gather(
|
||||
self.client.get_accounts_as_dict_with_id_key(),
|
||||
|
||||
@@ -1124,7 +1124,7 @@ def validate_light_platform_config(user_data: dict[str, Any]) -> dict[str, str]:
|
||||
if user_data.get(CONF_MIN_KELVIN, DEFAULT_MIN_KELVIN) >= user_data.get(
|
||||
CONF_MAX_KELVIN, DEFAULT_MAX_KELVIN
|
||||
):
|
||||
errors["advanced_settings"] = "max_below_min_kelvin"
|
||||
errors["other_settings"] = "max_below_min_kelvin"
|
||||
return errors
|
||||
|
||||
|
||||
@@ -1217,7 +1217,7 @@ def validate_text_platform_config(
|
||||
and CONF_MAX in config
|
||||
and config[CONF_MIN] > config[CONF_MAX]
|
||||
):
|
||||
errors["text_advanced_settings"] = "max_below_min"
|
||||
errors["text_other_settings"] = "max_below_min"
|
||||
|
||||
return errors
|
||||
|
||||
@@ -1506,7 +1506,7 @@ PLATFORM_ENTITY_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
selector=SUGGESTED_DISPLAY_PRECISION_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_OPTIONS: PlatformField(
|
||||
selector=OPTIONS_SELECTOR,
|
||||
@@ -1678,13 +1678,13 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
selector=TIMEOUT_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_OFF_DELAY: PlatformField(
|
||||
selector=TIMEOUT_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
},
|
||||
Platform.BUTTON: {
|
||||
@@ -3125,7 +3125,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
default=False,
|
||||
validator=cv.boolean,
|
||||
conditions=({CONF_SCHEMA: "json"},),
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_FLASH_TIME_SHORT: PlatformField(
|
||||
selector=FLASH_TIME_SELECTOR,
|
||||
@@ -3133,7 +3133,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
validator=cv.positive_int,
|
||||
default=2,
|
||||
conditions=({CONF_SCHEMA: "json"},),
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_FLASH_TIME_LONG: PlatformField(
|
||||
selector=FLASH_TIME_SELECTOR,
|
||||
@@ -3141,7 +3141,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
validator=cv.positive_int,
|
||||
default=10,
|
||||
conditions=({CONF_SCHEMA: "json"},),
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_TRANSITION: PlatformField(
|
||||
selector=BOOLEAN_SELECTOR,
|
||||
@@ -3149,21 +3149,21 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
default=False,
|
||||
validator=cv.boolean,
|
||||
conditions=({CONF_SCHEMA: "json"},),
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_MAX_KELVIN: PlatformField(
|
||||
selector=KELVIN_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
default=DEFAULT_MAX_KELVIN,
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_MIN_KELVIN: PlatformField(
|
||||
selector=KELVIN_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
default=DEFAULT_MIN_KELVIN,
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
},
|
||||
Platform.LOCK: {
|
||||
@@ -3372,7 +3372,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
selector=TIMEOUT_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
section="advanced_settings",
|
||||
section="other_settings",
|
||||
),
|
||||
},
|
||||
Platform.SIREN: {
|
||||
@@ -3437,7 +3437,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
required=False,
|
||||
validator=validate(cv.template),
|
||||
error="invalid_template",
|
||||
section="siren_advanced_settings",
|
||||
section="siren_other_settings",
|
||||
),
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
@@ -3516,26 +3516,26 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
selector=TEXT_SIZE_SELECTOR,
|
||||
required=True,
|
||||
default=0,
|
||||
section="text_advanced_settings",
|
||||
section="text_other_settings",
|
||||
),
|
||||
CONF_MAX: PlatformField(
|
||||
selector=TEXT_SIZE_SELECTOR,
|
||||
required=True,
|
||||
default=255,
|
||||
section="text_advanced_settings",
|
||||
section="text_other_settings",
|
||||
),
|
||||
CONF_MODE: PlatformField(
|
||||
selector=TEXT_MODE_SELECTOR,
|
||||
required=True,
|
||||
default=TextSelectorType.TEXT.value,
|
||||
section="text_advanced_settings",
|
||||
section="text_other_settings",
|
||||
),
|
||||
CONF_PATTERN: PlatformField(
|
||||
selector=TEXT_SELECTOR,
|
||||
required=False,
|
||||
validator=validate(cv.is_regex),
|
||||
error="invalid_regular_expression",
|
||||
section="text_advanced_settings",
|
||||
section="text_other_settings",
|
||||
),
|
||||
},
|
||||
Platform.TIME: {
|
||||
@@ -3798,10 +3798,10 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
MQTT_DEVICE_PLATFORM_FIELDS = {
|
||||
CONF_NAME: PlatformField(selector=TEXT_SELECTOR, required=True),
|
||||
CONF_SW_VERSION: PlatformField(
|
||||
selector=TEXT_SELECTOR, required=False, section="advanced_settings"
|
||||
selector=TEXT_SELECTOR, required=False, section="other_settings"
|
||||
),
|
||||
CONF_HW_VERSION: PlatformField(
|
||||
selector=TEXT_SELECTOR, required=False, section="advanced_settings"
|
||||
selector=TEXT_SELECTOR, required=False, section="other_settings"
|
||||
),
|
||||
CONF_MODEL: PlatformField(selector=TEXT_SELECTOR, required=False),
|
||||
CONF_MODEL_ID: PlatformField(selector=TEXT_SELECTOR, required=False),
|
||||
@@ -4178,7 +4178,6 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
CONF_PROTOCOL: DEFAULT_PROTOCOL,
|
||||
CONF_USERNAME: addon_discovery_config.get(CONF_USERNAME),
|
||||
CONF_PASSWORD: addon_discovery_config.get(CONF_PASSWORD),
|
||||
CONF_DISCOVERY: DEFAULT_DISCOVERY,
|
||||
}
|
||||
except AddonError:
|
||||
# We do not have discovery information yet
|
||||
@@ -4419,7 +4418,6 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
CONF_PROTOCOL: DEFAULT_PROTOCOL,
|
||||
CONF_USERNAME: data.get(CONF_USERNAME),
|
||||
CONF_PASSWORD: data.get(CONF_PASSWORD),
|
||||
CONF_DISCOVERY: DEFAULT_DISCOVERY,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -4686,8 +4684,8 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
||||
if user_input is not None:
|
||||
new_device_data: dict[str, Any] = user_input.copy()
|
||||
_, errors = validate_user_input(user_input, MQTT_DEVICE_PLATFORM_FIELDS)
|
||||
if "advanced_settings" in new_device_data:
|
||||
new_device_data |= new_device_data.pop("advanced_settings")
|
||||
if "other_settings" in new_device_data:
|
||||
new_device_data |= new_device_data.pop("other_settings")
|
||||
if not errors:
|
||||
self._subentry_data[CONF_DEVICE] = cast(MqttDeviceData, new_device_data)
|
||||
if self.source == SOURCE_RECONFIGURE:
|
||||
|
||||
@@ -184,17 +184,6 @@
|
||||
},
|
||||
"description": "Enter the MQTT device details:",
|
||||
"sections": {
|
||||
"advanced_settings": {
|
||||
"data": {
|
||||
"hw_version": "Hardware version",
|
||||
"sw_version": "Software version"
|
||||
},
|
||||
"data_description": {
|
||||
"hw_version": "The hardware version of the device. E.g. 'v1.0 rev a'.",
|
||||
"sw_version": "The software version of the device. E.g. '2025.1.0'."
|
||||
},
|
||||
"name": "Advanced device settings"
|
||||
},
|
||||
"mqtt_settings": {
|
||||
"data": {
|
||||
"message_expiry_interval": "Message Expiry Interval",
|
||||
@@ -205,6 +194,17 @@
|
||||
"qos": "The Quality of Service value the device's entities should use."
|
||||
},
|
||||
"name": "MQTT settings"
|
||||
},
|
||||
"other_settings": {
|
||||
"data": {
|
||||
"hw_version": "Hardware version",
|
||||
"sw_version": "Software version"
|
||||
},
|
||||
"data_description": {
|
||||
"hw_version": "The hardware version of the device. E.g. 'v1.0 rev a'.",
|
||||
"sw_version": "The software version of the device. E.g. '2025.1.0'."
|
||||
},
|
||||
"name": "Other device settings"
|
||||
}
|
||||
},
|
||||
"title": "Configure MQTT device details"
|
||||
@@ -286,14 +286,14 @@
|
||||
},
|
||||
"description": "Please configure specific details for {platform} entity \"{entity}\":",
|
||||
"sections": {
|
||||
"advanced_settings": {
|
||||
"other_settings": {
|
||||
"data": {
|
||||
"suggested_display_precision": "Suggested display precision"
|
||||
},
|
||||
"data_description": {
|
||||
"suggested_display_precision": "The number of decimals which should be used in the {platform} entity state after rounding. [Learn more.]({url}#suggested_display_precision)"
|
||||
},
|
||||
"name": "Advanced options"
|
||||
"name": "Other settings"
|
||||
}
|
||||
},
|
||||
"title": "Configure MQTT device \"{mqtt_device}\""
|
||||
@@ -438,29 +438,6 @@
|
||||
},
|
||||
"description": "Please configure MQTT specific details for {platform} entity \"{entity}\":",
|
||||
"sections": {
|
||||
"advanced_settings": {
|
||||
"data": {
|
||||
"expire_after": "Expire after",
|
||||
"flash": "Flash support",
|
||||
"flash_time_long": "Flash time long",
|
||||
"flash_time_short": "Flash time short",
|
||||
"max_kelvin": "Max Kelvin",
|
||||
"min_kelvin": "Min Kelvin",
|
||||
"off_delay": "OFF delay",
|
||||
"transition": "Transition support"
|
||||
},
|
||||
"data_description": {
|
||||
"expire_after": "If set, it defines the number of seconds after the sensor’s state expires, if it’s not updated. After expiry, the sensor’s state becomes unavailable. If not set, the sensor's state never expires. [Learn more.]({url}#expire_after)",
|
||||
"flash": "Enable the flash feature for this light",
|
||||
"flash_time_long": "The duration, in seconds, of a \"long\" flash.",
|
||||
"flash_time_short": "The duration, in seconds, of a \"short\" flash.",
|
||||
"max_kelvin": "The maximum color temperature in Kelvin.",
|
||||
"min_kelvin": "The minimum color temperature in Kelvin.",
|
||||
"off_delay": "For sensors that only send \"on\" state updates (like PIRs), this variable sets a delay in seconds after which the sensor’s state will be updated back to \"off\".",
|
||||
"transition": "Enable the transition feature for this light"
|
||||
},
|
||||
"name": "Advanced settings"
|
||||
},
|
||||
"alarm_control_panel_payload_settings": {
|
||||
"data": {
|
||||
"payload_arm_away": "Payload \"arm away\"",
|
||||
@@ -916,14 +893,37 @@
|
||||
},
|
||||
"name": "Lock payload settings"
|
||||
},
|
||||
"siren_advanced_settings": {
|
||||
"other_settings": {
|
||||
"data": {
|
||||
"expire_after": "Expire after",
|
||||
"flash": "Flash support",
|
||||
"flash_time_long": "Flash time long",
|
||||
"flash_time_short": "Flash time short",
|
||||
"max_kelvin": "Max Kelvin",
|
||||
"min_kelvin": "Min Kelvin",
|
||||
"off_delay": "OFF delay",
|
||||
"transition": "Transition support"
|
||||
},
|
||||
"data_description": {
|
||||
"expire_after": "If set, it defines the number of seconds after the sensor’s state expires, if it’s not updated. After expiry, the sensor’s state becomes unavailable. If not set, the sensor's state never expires. [Learn more.]({url}#expire_after)",
|
||||
"flash": "Enable the flash feature for this light",
|
||||
"flash_time_long": "The duration, in seconds, of a \"long\" flash.",
|
||||
"flash_time_short": "The duration, in seconds, of a \"short\" flash.",
|
||||
"max_kelvin": "The maximum color temperature in Kelvin.",
|
||||
"min_kelvin": "The minimum color temperature in Kelvin.",
|
||||
"off_delay": "For sensors that only send \"on\" state updates (like PIRs), this variable sets a delay in seconds after which the sensor’s state will be updated back to \"off\".",
|
||||
"transition": "Enable the transition feature for this light"
|
||||
},
|
||||
"name": "Other settings"
|
||||
},
|
||||
"siren_other_settings": {
|
||||
"data": {
|
||||
"command_off_template": "Command \"off\" template"
|
||||
},
|
||||
"data_description": {
|
||||
"command_off_template": "The [template]({command_templating_url}) for \"off\" state changes. By default the \"[Command template]({url}#command_template)\" will be used. [Learn more.]({url}#command_off_template)"
|
||||
},
|
||||
"name": "Advanced siren settings"
|
||||
"name": "Other siren settings"
|
||||
},
|
||||
"target_humidity_settings": {
|
||||
"data": {
|
||||
@@ -985,7 +985,7 @@
|
||||
},
|
||||
"name": "Target temperature settings"
|
||||
},
|
||||
"text_advanced_settings": {
|
||||
"text_other_settings": {
|
||||
"data": {
|
||||
"max": "Maximum length",
|
||||
"min": "Minimum length",
|
||||
@@ -998,7 +998,7 @@
|
||||
"mode": "Mode of the text input",
|
||||
"pattern": "A valid regex pattern"
|
||||
},
|
||||
"name": "Advanced text entity settings"
|
||||
"name": "Other text entity settings"
|
||||
},
|
||||
"valve_payload_settings": {
|
||||
"data": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "myneomitis",
|
||||
"name": "MyNeomitis",
|
||||
"codeowners": ["@l-pr"],
|
||||
"codeowners": ["@Epyes"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/myneomitis",
|
||||
"integration_type": "hub",
|
||||
|
||||
@@ -70,5 +70,8 @@ class MyUplinkDataCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
||||
points[device_id] = point_info
|
||||
|
||||
return CoordinatorData(
|
||||
systems=systems, devices=devices, points=points, time=datetime.now()
|
||||
systems=systems,
|
||||
devices=devices,
|
||||
points=points,
|
||||
time=datetime.now(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
)
|
||||
|
||||
@@ -67,7 +67,7 @@ class NextBusDataUpdateCoordinator(
|
||||
# But only if we have a reset time to unthrottle
|
||||
and self.client.rate_limit_reset is not None
|
||||
# Unless we are after the reset time
|
||||
and datetime.now() < self.client.rate_limit_reset
|
||||
and datetime.now() < self.client.rate_limit_reset # pylint: disable=home-assistant-enforce-naive-now
|
||||
):
|
||||
self.logger.debug(
|
||||
"Rate limit threshold reached. Skipping updates for. Routes: %s",
|
||||
|
||||
@@ -58,7 +58,7 @@ class NiceGOConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_EMAIL: user_input[CONF_EMAIL],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
CONF_REFRESH_TOKEN: refresh_token,
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
},
|
||||
)
|
||||
|
||||
@@ -99,7 +99,7 @@ class NiceGOConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
data={
|
||||
**user_input,
|
||||
CONF_REFRESH_TOKEN: refresh_token,
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
},
|
||||
unique_id=user_input[CONF_EMAIL],
|
||||
)
|
||||
|
||||
@@ -150,7 +150,7 @@ class NiceGOUpdateCoordinator(DataUpdateCoordinator[dict[str, NiceGODevice]]):
|
||||
+ REFRESH_TOKEN_EXPIRY_TIME.total_seconds()
|
||||
)
|
||||
try:
|
||||
if datetime.now().timestamp() >= expiry_time:
|
||||
if datetime.now().timestamp() >= expiry_time: # pylint: disable=home-assistant-enforce-naive-now
|
||||
await self.update_refresh_token()
|
||||
else:
|
||||
await self.api.authenticate_refresh(
|
||||
@@ -194,7 +194,7 @@ class NiceGOUpdateCoordinator(DataUpdateCoordinator[dict[str, NiceGODevice]]):
|
||||
data = {
|
||||
**self.config_entry.data,
|
||||
CONF_REFRESH_TOKEN: refresh_token,
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
|
||||
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
}
|
||||
self.hass.config_entries.async_update_entry(self.config_entry, data=data)
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ class NOAATidesAndCurrentsSensor(SensorEntity):
|
||||
|
||||
def update(self) -> None:
|
||||
"""Get the latest data from NOAA Tides and Currents API."""
|
||||
begin = datetime.now()
|
||||
begin = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
end = begin + DEFAULT_PREDICTION_LENGTH
|
||||
try:
|
||||
df_predictions = self._station.get_data(
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["opower"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["opower==0.18.4"]
|
||||
"requirements": ["opower==0.18.5"]
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ class DailyHistory:
|
||||
|
||||
def add_measurement(self, value, timestamp=None):
|
||||
"""Add a new measurement for a certain day."""
|
||||
day = (timestamp or datetime.now()).date()
|
||||
day = (timestamp or datetime.now()).date() # pylint: disable=home-assistant-enforce-naive-now
|
||||
if not isinstance(value, (int, float)):
|
||||
return
|
||||
if self._days is None:
|
||||
|
||||
@@ -26,6 +26,8 @@ from .coordinator import ProxmoxConfigEntry, ProxmoxCoordinator, ProxmoxNodeData
|
||||
from .entity import ProxmoxContainerEntity, ProxmoxNodeEntity, ProxmoxVMEntity
|
||||
from .helpers import is_granted
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
NO_PERM_VM_LXC_POWER = "no_permission_vm_lxc_power"
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant
|
||||
|
||||
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.FAN]
|
||||
PLATFORMS: list[Platform] = [Platform.FAN, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool:
|
||||
|
||||
@@ -25,6 +25,8 @@ MODELS = {
|
||||
class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]):
|
||||
"""Base class for Rabbit Air entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RabbitAirDataUpdateCoordinator,
|
||||
@@ -32,7 +34,6 @@ class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]):
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_name = entry.title
|
||||
self._attr_unique_id = entry.unique_id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, entry.data[CONF_MAC])},
|
||||
|
||||
@@ -46,6 +46,7 @@ async def async_setup_entry(
|
||||
class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
|
||||
"""Fan control functions of the Rabbit Air air purifier."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_translation_key = "rabbitair"
|
||||
_attr_supported_features = (
|
||||
FanEntityFeature.PRESET_MODE
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"air_quality": {
|
||||
"default": "mdi:air-filter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Support for Rabbit Air sensors."""
|
||||
|
||||
from rabbitair import Quality
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
|
||||
from .entity import RabbitAirBaseEntity
|
||||
|
||||
|
||||
def _quality_value(quality: Quality | None) -> StateType:
|
||||
"""Return the air quality state."""
|
||||
return None if quality is None else quality.name.lower()
|
||||
|
||||
|
||||
AIR_QUALITY_OPTIONS = [quality.name.lower() for quality in Quality]
|
||||
|
||||
AIR_QUALITY_DESCRIPTION = SensorEntityDescription(
|
||||
key="air_quality",
|
||||
translation_key="air_quality",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=AIR_QUALITY_OPTIONS,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: RabbitAirConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Rabbit Air sensors."""
|
||||
if entry.runtime_data.data.quality is not None:
|
||||
async_add_entities([RabbitAirAirQualitySensor(entry.runtime_data, entry)])
|
||||
|
||||
|
||||
class RabbitAirAirQualitySensor(RabbitAirBaseEntity, SensorEntity):
|
||||
"""Rabbit Air air quality sensor."""
|
||||
|
||||
entity_description = AIR_QUALITY_DESCRIPTION
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RabbitAirDataUpdateCoordinator,
|
||||
entry: RabbitAirConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, entry)
|
||||
self._attr_unique_id = f"{entry.unique_id}_{self.entity_description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the air quality state."""
|
||||
return _quality_value(self.coordinator.data.quality)
|
||||
@@ -32,6 +32,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"air_quality": {
|
||||
"name": "Air quality",
|
||||
"state": {
|
||||
"high": "[%key:common::state::high%]",
|
||||
"highest": "Highest",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"lowest": "Lowest",
|
||||
"medium": "[%key:common::state::medium%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ class CalendarUpdateCoordinator(RadarrDataUpdateCoordinator[None]):
|
||||
self._events = [
|
||||
e
|
||||
for e in self._events
|
||||
if e.start >= datetime.now().date() - timedelta(days=30)
|
||||
if e.start >= datetime.now().date() - timedelta(days=30) # pylint: disable=home-assistant-enforce-naive-now
|
||||
]
|
||||
_days = (end_date - start_date).days
|
||||
await asyncio.gather(
|
||||
|
||||
@@ -19,7 +19,7 @@ async def async_get_config_entry_diagnostics(
|
||||
payload: dict[str, Any] = {
|
||||
"now": dt_util.now().isoformat(),
|
||||
"timezone": str(dt_util.get_default_time_zone()),
|
||||
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo),
|
||||
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo), # pylint: disable=home-assistant-enforce-naive-now
|
||||
}
|
||||
payload["ics"] = "\n".join(redact_ics(coordinator.ics))
|
||||
return payload
|
||||
|
||||
@@ -31,7 +31,7 @@ async def async_setup_services(hass: HomeAssistant) -> None:
|
||||
|
||||
time_to_send = time
|
||||
if time is None:
|
||||
time_to_send = datetime.now()
|
||||
time_to_send = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
|
||||
await local_data.system.set_time(time_to_send)
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class SensoterraConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
api = CustomerApi(user_input[CONF_EMAIL], user_input[CONF_PASSWORD])
|
||||
# We need a unique tag per HA instance
|
||||
uuid = self.hass.data["core.uuid"]
|
||||
expiration = datetime.now() + timedelta(TOKEN_EXPIRATION_DAYS)
|
||||
expiration = datetime.now() + timedelta(TOKEN_EXPIRATION_DAYS) # pylint: disable=home-assistant-enforce-naive-now
|
||||
|
||||
try:
|
||||
token: str = await api.get_token(
|
||||
|
||||
@@ -70,7 +70,7 @@ class SharkIqUpdateCoordinator(DataUpdateCoordinator[bool]):
|
||||
try:
|
||||
if (
|
||||
self.ayla_api.token_expiring_soon
|
||||
or datetime.now()
|
||||
or datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
> self.ayla_api.auth_expiration - timedelta(seconds=600)
|
||||
):
|
||||
await self.ayla_api.async_refresh_auth()
|
||||
|
||||
@@ -247,7 +247,7 @@ def _async_register_base_station(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, str(system.system_id))},
|
||||
manufacturer="SimpliSafe",
|
||||
model=system.version,
|
||||
model=str(system.version),
|
||||
name=system.address,
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.util.ssl import create_client_context
|
||||
|
||||
@@ -75,7 +75,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmtpConfigEntry) -> bool
|
||||
try:
|
||||
await hass.async_add_executor_job(lambda: client.connect().quit())
|
||||
except SMTPAuthenticationError as e:
|
||||
raise ConfigEntryError(
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="authentication_error",
|
||||
) from e
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Config flow for the SMTP integration."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from smtplib import SMTP, SMTP_SSL, SMTPAuthenticationError
|
||||
import socket
|
||||
@@ -95,6 +96,22 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
vol.Required(CONF_VERIFY_SSL, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_USERNAME): TextSelector(
|
||||
TextSelectorConfig(
|
||||
type=TextSelectorType.TEXT,
|
||||
autocomplete="username",
|
||||
),
|
||||
),
|
||||
vol.Optional(CONF_PASSWORD): TextSelector(
|
||||
TextSelectorConfig(
|
||||
type=TextSelectorType.PASSWORD,
|
||||
autocomplete="current-password",
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
OPTIONS_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -201,6 +218,39 @@ class MailConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Perform reauth upon an authentication error."""
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Confirm reauthentication dialog."""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
entry = self._get_reauth_entry()
|
||||
|
||||
if user_input is not None:
|
||||
errors = await self.hass.async_add_executor_job(
|
||||
validate_input, {**entry.data, **user_input}
|
||||
)
|
||||
if not errors:
|
||||
return self.async_update_and_abort(
|
||||
entry,
|
||||
data_updates=user_input,
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
data_schema=STEP_REAUTH_DATA_SCHEMA,
|
||||
suggested_values=user_input
|
||||
or {CONF_USERNAME: entry.data.get(CONF_USERNAME)},
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_info: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Import config from yaml."""
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -208,7 +208,7 @@ class MailNotifyEntity(NotifyEntity):
|
||||
try:
|
||||
client = self._client.connect()
|
||||
except SMTPAuthenticationError as e:
|
||||
raise HomeAssistantError(
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="authentication_error",
|
||||
) from e
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
},
|
||||
"error": {
|
||||
@@ -11,6 +12,17 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
},
|
||||
"data_description": {
|
||||
"password": "[%key:component::smtp::config::step::user::data_description::password%]",
|
||||
"username": "[%key:component::smtp::config::step::user::data_description::username%]"
|
||||
},
|
||||
"title": "Re-authenticate SMTP"
|
||||
},
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"encryption": "[%key:component::smtp::config::step::user::data::encryption%]",
|
||||
|
||||
@@ -47,7 +47,7 @@ class StarlineAccount:
|
||||
|
||||
def _check_slnet_token(self, interval: int) -> None:
|
||||
"""Check SLNet token expiration and update if needed."""
|
||||
now = datetime.now().timestamp()
|
||||
now = datetime.now().timestamp() # pylint: disable=home-assistant-enforce-naive-now
|
||||
slnet_token_expires = self._config_entry.data[DATA_EXPIRES]
|
||||
|
||||
if now + interval > slnet_token_expires:
|
||||
|
||||
@@ -40,11 +40,6 @@ class StarlineDeviceTracker(StarlineEntity, TrackerEntity, RestoreEntity):
|
||||
"""Return device specific attributes."""
|
||||
return self._account.gps_attrs(self._device)
|
||||
|
||||
@property
|
||||
def battery_level(self) -> int | None:
|
||||
"""Return the battery level of the device."""
|
||||
return self._device.battery_level
|
||||
|
||||
@property
|
||||
def location_accuracy(self) -> float:
|
||||
"""Return the gps accuracy of the device."""
|
||||
|
||||
@@ -108,7 +108,7 @@ class SubaruConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
data: contains values provided by the user.
|
||||
"""
|
||||
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||
now = datetime.now()
|
||||
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
if not data.get(CONF_DEVICE_ID):
|
||||
data[CONF_DEVICE_ID] = int(now.timestamp())
|
||||
date = now.strftime("%Y-%m-%d")
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
@@ -50,6 +50,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: SunConfigEntry) -> bool:
|
||||
"""Set up from a config entry."""
|
||||
# Remove deprecated solar_rising sensor entity (removed in 2026.1)
|
||||
ent_reg = er.async_get(hass)
|
||||
if entity_id := ent_reg.async_get_entity_id(
|
||||
Platform.SENSOR, DOMAIN, f"{entry.entry_id}-solar_rising"
|
||||
):
|
||||
ent_reg.async_remove(entity_id)
|
||||
|
||||
sun = Sun(hass)
|
||||
component = EntityComponent[Sun](_LOGGER, DOMAIN, hass)
|
||||
await component.async_add_entities([sun])
|
||||
|
||||
@@ -57,7 +57,7 @@ class SwitchBotCloudImage(SwitchBotCloudEntity, ImageEntity):
|
||||
"""Set attributes from coordinator data."""
|
||||
if self.coordinator.data is None:
|
||||
return
|
||||
self._attr_image_last_updated = datetime.datetime.now()
|
||||
self._attr_image_last_updated = datetime.datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
self._attr_image_url = self.coordinator.data.get("imageUrl")
|
||||
|
||||
|
||||
|
||||
@@ -447,7 +447,7 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
async def set_meter_reading(self, reading: int) -> dict[str, Any]:
|
||||
"""Send meter reading to Tado."""
|
||||
dt: str = datetime.now().strftime("%Y-%m-%d")
|
||||
dt: str = datetime.now().strftime("%Y-%m-%d") # pylint: disable=home-assistant-enforce-naive-now
|
||||
if self._tado is None:
|
||||
raise HomeAssistantError("Tado client is not initialized")
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
"""Support for Template fans."""
|
||||
|
||||
from enum import StrEnum
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
ATTR_DIRECTION,
|
||||
ATTR_OSCILLATING,
|
||||
ATTR_PERCENTAGE,
|
||||
ATTR_PRESET_MODE,
|
||||
DIRECTION_FORWARD,
|
||||
DIRECTION_REVERSE,
|
||||
DOMAIN as FAN_DOMAIN,
|
||||
@@ -100,6 +97,15 @@ FAN_CONFIG_ENTRY_SCHEMA = FAN_COMMON_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
class FanScriptVariable(StrEnum):
|
||||
"""Variables for scripts."""
|
||||
|
||||
DIRECTION = "direction"
|
||||
OSCILLATING = "oscillating"
|
||||
PERCENTAGE = "percentage"
|
||||
PRESET_MODE = "preset_mode"
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
@@ -235,8 +241,8 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
||||
await self.async_run_script(
|
||||
self._action_scripts[CONF_ON_ACTION],
|
||||
run_variables={
|
||||
ATTR_PERCENTAGE: percentage,
|
||||
ATTR_PRESET_MODE: preset_mode,
|
||||
FanScriptVariable.PERCENTAGE: percentage,
|
||||
FanScriptVariable.PRESET_MODE: preset_mode,
|
||||
},
|
||||
context=self._context,
|
||||
)
|
||||
@@ -267,7 +273,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
||||
if script := self._action_scripts.get(CONF_SET_PERCENTAGE_ACTION):
|
||||
await self.async_run_script(
|
||||
script,
|
||||
run_variables={ATTR_PERCENTAGE: self._attr_percentage},
|
||||
run_variables={FanScriptVariable.PERCENTAGE: self._attr_percentage},
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
@@ -284,7 +290,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
||||
if script := self._action_scripts.get(CONF_SET_PRESET_MODE_ACTION):
|
||||
await self.async_run_script(
|
||||
script,
|
||||
run_variables={ATTR_PRESET_MODE: self._attr_preset_mode},
|
||||
run_variables={FanScriptVariable.PRESET_MODE: self._attr_preset_mode},
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
@@ -302,7 +308,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
||||
) is not None:
|
||||
await self.async_run_script(
|
||||
script,
|
||||
run_variables={ATTR_OSCILLATING: self.oscillating},
|
||||
run_variables={FanScriptVariable.OSCILLATING: self.oscillating},
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
@@ -318,7 +324,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
||||
) is not None:
|
||||
await self.async_run_script(
|
||||
script,
|
||||
run_variables={ATTR_DIRECTION: direction},
|
||||
run_variables={FanScriptVariable.DIRECTION: direction},
|
||||
context=self._context,
|
||||
)
|
||||
if CONF_DIRECTION not in self._templates:
|
||||
|
||||
@@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Any
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.number import (
|
||||
ATTR_VALUE,
|
||||
DEFAULT_MAX_VALUE,
|
||||
DEFAULT_MIN_VALUE,
|
||||
DEFAULT_STEP,
|
||||
@@ -161,7 +160,7 @@ class AbstractTemplateNumber(AbstractTemplateEntity, NumberEntity):
|
||||
if set_value := self._action_scripts.get(CONF_SET_VALUE):
|
||||
await self.async_run_script(
|
||||
set_value,
|
||||
run_variables={ATTR_VALUE: value},
|
||||
run_variables={"value": value},
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ from typing import TYPE_CHECKING, Any
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.select import (
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
SelectEntity,
|
||||
@@ -48,7 +46,7 @@ SCRIPT_FIELDS = (CONF_SELECT_OPTION,)
|
||||
|
||||
SELECT_COMMON_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_OPTIONS): cv.template,
|
||||
vol.Required(CONF_OPTIONS): cv.template,
|
||||
vol.Optional(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
}
|
||||
@@ -147,7 +145,7 @@ class AbstractTemplateSelect(AbstractTemplateEntity, SelectEntity):
|
||||
if select_option := self._action_scripts.get(CONF_SELECT_OPTION):
|
||||
await self.async_run_script(
|
||||
select_option,
|
||||
run_variables={ATTR_OPTION: option},
|
||||
run_variables={"option": option},
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
@@ -175,7 +173,7 @@ class TriggerSelectEntity(TriggerEntity, AbstractTemplateSelect):
|
||||
"""Select entity based on trigger data."""
|
||||
|
||||
domain = SELECT_DOMAIN
|
||||
extra_template_keys_complex = (ATTR_OPTIONS,)
|
||||
extra_template_keys_complex = (CONF_OPTIONS,)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -9,7 +9,6 @@ from typing import Any
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
ATTR_LAST_RESET,
|
||||
CONF_STATE_CLASS,
|
||||
DEVICE_CLASSES_SCHEMA,
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
@@ -50,13 +49,14 @@ from .schemas import (
|
||||
from .template_entity import TemplateEntity
|
||||
from .trigger_entity import TriggerEntity
|
||||
|
||||
CONF_LAST_RESET = "last_reset"
|
||||
DEFAULT_NAME = "Template Sensor"
|
||||
|
||||
|
||||
def validate_last_reset(val):
|
||||
"""Run extra validation checks."""
|
||||
if (
|
||||
val.get(ATTR_LAST_RESET) is not None
|
||||
val.get(CONF_LAST_RESET) is not None
|
||||
and val.get(CONF_STATE_CLASS) != SensorStateClass.TOTAL
|
||||
):
|
||||
raise vol.Invalid(
|
||||
@@ -78,7 +78,7 @@ SENSOR_COMMON_SCHEMA = vol.Schema(
|
||||
SENSOR_YAML_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_LAST_RESET): cv.template,
|
||||
vol.Optional(CONF_LAST_RESET): cv.template,
|
||||
}
|
||||
)
|
||||
.extend(SENSOR_COMMON_SCHEMA.schema)
|
||||
@@ -204,10 +204,10 @@ class AbstractTemplateSensor(AbstractTemplateEntity, RestoreSensor):
|
||||
self._validate_state,
|
||||
)
|
||||
self.setup_template(
|
||||
ATTR_LAST_RESET,
|
||||
CONF_LAST_RESET,
|
||||
"_attr_last_reset",
|
||||
validate_datetime(
|
||||
self, ATTR_LAST_RESET, SensorDeviceClass.TIMESTAMP, require_tzinfo=False
|
||||
self, CONF_LAST_RESET, SensorDeviceClass.TIMESTAMP, require_tzinfo=False
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.vacuum import (
|
||||
ATTR_FAN_SPEED,
|
||||
DOMAIN as VACUUM_DOMAIN,
|
||||
SERVICE_CLEAN_SPOT,
|
||||
SERVICE_LOCATE,
|
||||
@@ -389,7 +388,7 @@ class AbstractTemplateVacuum(AbstractTemplateEntity, StateVacuumEntity):
|
||||
|
||||
if script := self._action_scripts.get(SERVICE_SET_FAN_SPEED):
|
||||
await self.async_run_script(
|
||||
script, run_variables={ATTR_FAN_SPEED: fan_speed}, context=self._context
|
||||
script, run_variables={"fan_speed": fan_speed}, context=self._context
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ class TeslaFleetVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self.api = api
|
||||
self.data = flatten(product)
|
||||
self.updated_once = False
|
||||
self.last_active = datetime.now()
|
||||
self.last_active = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
self.endpoints = (
|
||||
ENDPOINTS
|
||||
if location
|
||||
@@ -159,12 +159,12 @@ class TeslaFleetVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
or data["vehicle_state"].get("sentry_mode")
|
||||
):
|
||||
# Vehicle is active, reset timer
|
||||
self.last_active = datetime.now()
|
||||
self.last_active = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
else:
|
||||
elapsed = datetime.now() - self.last_active
|
||||
elapsed = datetime.now() - self.last_active # pylint: disable=home-assistant-enforce-naive-now
|
||||
if elapsed > timedelta(minutes=20):
|
||||
# Vehicle didn't sleep, try again in 15 minutes
|
||||
self.last_active = datetime.now()
|
||||
self.last_active = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
elif elapsed > timedelta(minutes=15):
|
||||
# Let vehicle go to sleep now
|
||||
self.update_interval = VEHICLE_WAIT
|
||||
|
||||
@@ -129,7 +129,7 @@ class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
self.api = api
|
||||
self.data = flatten(product)
|
||||
self.last_active = datetime.now()
|
||||
self.last_active = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update vehicle data using Teslemetry API."""
|
||||
|
||||
@@ -232,7 +232,7 @@ class TwitterNotificationService(BaseNotificationService):
|
||||
"media processing waiting %s seconds to check status", str(check_after_secs)
|
||||
)
|
||||
|
||||
when = datetime.now() + timedelta(seconds=check_after_secs)
|
||||
when = datetime.now() + timedelta(seconds=check_after_secs) # pylint: disable=home-assistant-enforce-naive-now
|
||||
myself = partial(self.check_status_until_done, media_id, callback)
|
||||
async_track_point_in_time(self.hass, myself, when)
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class VeSyncDataCoordinator(DataUpdateCoordinator[None]):
|
||||
if self.update_time is None:
|
||||
return True
|
||||
|
||||
return datetime.now() - self.update_time >= timedelta(
|
||||
return datetime.now() - self.update_time >= timedelta( # pylint: disable=home-assistant-enforce-naive-now
|
||||
seconds=UPDATE_INTERVAL_ENERGY
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@ class VeSyncDataCoordinator(DataUpdateCoordinator[None]):
|
||||
await self.manager.update_all_devices()
|
||||
|
||||
if self.should_update_energy():
|
||||
self.update_time = datetime.now()
|
||||
self.update_time = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
for outlet in self.manager.devices.outlets:
|
||||
await outlet.update_energy()
|
||||
except VeSyncError as err:
|
||||
|
||||
@@ -101,7 +101,7 @@ def _require_authentication[_WallboxCoordinatorT: WallboxCoordinator, **_P](
|
||||
def check_token_validity(jwt_token_ttl: int, jwt_token_drift: int) -> bool:
|
||||
"""Check if the jwtToken is still valid in order to reuse if possible."""
|
||||
return round((jwt_token_ttl / 1000) - jwt_token_drift, 0) > datetime.timestamp(
|
||||
datetime.now()
|
||||
datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class WattsVisionHubCoordinator(DataUpdateCoordinator[dict[str, Device]]):
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Device]:
|
||||
"""Fetch data and periodic device discovery."""
|
||||
now = datetime.now()
|
||||
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
is_first_refresh = self.last_discovery is None
|
||||
discovery_interval_elapsed = (
|
||||
self.last_discovery is not None
|
||||
@@ -204,7 +204,7 @@ class WattsVisionDeviceCoordinator(DataUpdateCoordinator[WattsVisionDeviceData])
|
||||
|
||||
async def _async_update_data(self) -> WattsVisionDeviceData:
|
||||
"""Refresh specific device."""
|
||||
if self.fast_polling_until and datetime.now() > self.fast_polling_until:
|
||||
if self.fast_polling_until and datetime.now() > self.fast_polling_until: # pylint: disable=home-assistant-enforce-naive-now
|
||||
self.fast_polling_until = None
|
||||
self.update_interval = None
|
||||
_LOGGER.debug(
|
||||
@@ -242,7 +242,7 @@ class WattsVisionDeviceCoordinator(DataUpdateCoordinator[WattsVisionDeviceData])
|
||||
|
||||
def trigger_fast_polling(self, duration: int = 60) -> None:
|
||||
"""Activate fast polling for a specified duration after a command."""
|
||||
self.fast_polling_until = datetime.now() + timedelta(seconds=duration)
|
||||
self.fast_polling_until = datetime.now() + timedelta(seconds=duration) # pylint: disable=home-assistant-enforce-naive-now
|
||||
self.update_interval = timedelta(seconds=FAST_POLLING_INTERVAL_SECONDS)
|
||||
_LOGGER.debug(
|
||||
"Device %s: Activated fast polling for %d seconds", self.device_id, duration
|
||||
|
||||
@@ -21,7 +21,7 @@ async def async_get_config_entry_diagnostics(
|
||||
runtime_data = entry.runtime_data
|
||||
hub_coordinator = runtime_data.hub_coordinator
|
||||
device_coordinators = runtime_data.device_coordinators
|
||||
now = datetime.now()
|
||||
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
|
||||
return async_redact_data(
|
||||
{
|
||||
|
||||
@@ -81,12 +81,12 @@ class WeatherKitDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
except WeatherKitApiClientError as exception:
|
||||
if self.data is None or (
|
||||
self.last_updated_at is not None
|
||||
and datetime.now() - self.last_updated_at > STALE_DATA_THRESHOLD
|
||||
and datetime.now() - self.last_updated_at > STALE_DATA_THRESHOLD # pylint: disable=home-assistant-enforce-naive-now
|
||||
):
|
||||
raise UpdateFailed(exception) from exception
|
||||
|
||||
LOGGER.debug("Using stale data because update failed: %s", exception)
|
||||
return self.data
|
||||
else:
|
||||
self.last_updated_at = datetime.now()
|
||||
self.last_updated_at = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
|
||||
return updated_data
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"""The WMS WebControl pro API integration."""
|
||||
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
import aiohttp
|
||||
from wmspro.webcontrol import WebControlPro
|
||||
|
||||
@@ -9,6 +12,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.storage import STORAGE_DIR
|
||||
from homeassistant.helpers.typing import UNDEFINED
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
@@ -17,6 +21,7 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.BUTTON,
|
||||
Platform.COVER,
|
||||
Platform.LIGHT,
|
||||
Platform.NUMBER,
|
||||
Platform.SCENE,
|
||||
Platform.SWITCH,
|
||||
]
|
||||
@@ -24,13 +29,22 @@ PLATFORMS: list[Platform] = [
|
||||
type WebControlProConfigEntry = ConfigEntry[WebControlPro]
|
||||
|
||||
|
||||
def _build_storage_config_dir(
|
||||
hass: HomeAssistant, entry: WebControlProConfigEntry
|
||||
) -> Path:
|
||||
"""Build the path to the config directory for a given config entry."""
|
||||
return Path(hass.config.path(STORAGE_DIR, f"{DOMAIN}-{entry.entry_id}"))
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: WebControlProConfigEntry
|
||||
) -> bool:
|
||||
"""Set up wmspro from a config entry."""
|
||||
host = entry.data[CONF_HOST]
|
||||
session = async_get_clientsession(hass)
|
||||
hub = WebControlPro(host, session)
|
||||
config_dir = _build_storage_config_dir(hass, entry)
|
||||
config_dir.mkdir(parents=True, exist_ok=True)
|
||||
hub = WebControlPro(host, session, str(config_dir))
|
||||
|
||||
try:
|
||||
await hub.ping()
|
||||
@@ -68,3 +82,12 @@ async def async_unload_entry(
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_remove_entry(
|
||||
hass: HomeAssistant, entry: WebControlProConfigEntry
|
||||
) -> None:
|
||||
"""Remove a config entry."""
|
||||
config_dir = _build_storage_config_dir(hass, entry)
|
||||
if config_dir.is_dir():
|
||||
await hass.async_add_executor_job(shutil.rmtree, config_dir)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from wmspro.const import WMS_WebControl_pro_API_actionDescription
|
||||
|
||||
from homeassistant.components.button import ButtonDeviceClass, ButtonEntity
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -18,11 +19,12 @@ async def async_setup_entry(
|
||||
"""Set up the WMS based identify buttons from a config entry."""
|
||||
hub = config_entry.runtime_data
|
||||
|
||||
entities: list[WebControlProGenericEntity] = [
|
||||
WebControlProIdentifyButton(config_entry.entry_id, dest)
|
||||
for dest in hub.dests.values()
|
||||
if dest.hasAction(WMS_WebControl_pro_API_actionDescription.Identify)
|
||||
]
|
||||
entities: list[WebControlProGenericEntity] = []
|
||||
for d in hub.dests.values():
|
||||
if d.hasAction(WMS_WebControl_pro_API_actionDescription.Identify):
|
||||
entities.append(WebControlProIdentifyButton(config_entry.entry_id, d))
|
||||
if d.hasAction(WMS_WebControl_pro_API_actionDescription.SlatRotate):
|
||||
entities.append(WebControlProRotationResetButton(config_entry.entry_id, d))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
@@ -33,6 +35,21 @@ class WebControlProIdentifyButton(WebControlProGenericEntity, ButtonEntity):
|
||||
_attr_device_class = ButtonDeviceClass.IDENTIFY
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Handle the button press."""
|
||||
"""Handle the button press to identify the device."""
|
||||
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.Identify)
|
||||
await action()
|
||||
|
||||
|
||||
class WebControlProRotationResetButton(WebControlProGenericEntity, ButtonEntity):
|
||||
"""Representation of a WMS based reset rotation button."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_translation_key = "rotation-reset"
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Handle the button press to reset the rotation range to the default."""
|
||||
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.SlatRotate)
|
||||
# Delete the min and max override values to reset the rotation range to the default
|
||||
del action["minValue"]
|
||||
del action["maxValue"]
|
||||
# The library will take care of the update and persistence on the next poll refresh
|
||||
|
||||
@@ -8,11 +8,19 @@ from wmspro.const import (
|
||||
WMS_WebControl_pro_API_actionType,
|
||||
WMS_WebControl_pro_API_responseType,
|
||||
)
|
||||
from wmspro.destination import Destination
|
||||
|
||||
from homeassistant.components.cover import ATTR_POSITION, CoverDeviceClass, CoverEntity
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION,
|
||||
ATTR_TILT_POSITION,
|
||||
CoverDeviceClass,
|
||||
CoverEntity,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.percentage import (
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
||||
from . import WebControlProConfigEntry
|
||||
from .entity import WebControlProGenericEntity
|
||||
@@ -30,13 +38,18 @@ async def async_setup_entry(
|
||||
hub = config_entry.runtime_data
|
||||
|
||||
entities: list[WebControlProGenericEntity] = []
|
||||
for dest in hub.dests.values():
|
||||
if dest.hasAction(ACTION_DESC.AwningDrive):
|
||||
entities.append(WebControlProAwning(config_entry.entry_id, dest))
|
||||
if dest.hasAction(ACTION_DESC.ValanceDrive):
|
||||
entities.append(WebControlProValance(config_entry.entry_id, dest))
|
||||
if dest.hasAction(ACTION_DESC.RollerShutterBlindDrive):
|
||||
entities.append(WebControlProRollerShutter(config_entry.entry_id, dest))
|
||||
for d in hub.dests.values():
|
||||
if d.hasAction(ACTION_DESC.AwningDrive):
|
||||
entities.append(WebControlProAwning(config_entry.entry_id, d))
|
||||
if d.hasAction(ACTION_DESC.ValanceDrive):
|
||||
entities.append(WebControlProValance(config_entry.entry_id, d))
|
||||
elif d.hasAction(ACTION_DESC.RollerShutterBlindDrive):
|
||||
entities.append(WebControlProRollerShutter(config_entry.entry_id, d))
|
||||
elif d.hasAction(ACTION_DESC.SlatDrive):
|
||||
if d.hasAction(ACTION_DESC.SlatRotate):
|
||||
entities.append(WebControlProSlatRotate(config_entry.entry_id, d))
|
||||
else:
|
||||
entities.append(WebControlProSlat(config_entry.entry_id, d))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
@@ -51,7 +64,7 @@ class WebControlProCover(WebControlProGenericEntity, CoverEntity):
|
||||
def current_cover_position(self) -> int | None:
|
||||
"""Return current position of cover."""
|
||||
action = self._dest.action(self._drive_action_desc)
|
||||
if action is None or action["percentage"] is None:
|
||||
if action["percentage"] is None:
|
||||
return None
|
||||
return 100 - action["percentage"]
|
||||
|
||||
@@ -94,19 +107,89 @@ class WebControlProAwning(WebControlProCover):
|
||||
class WebControlProValance(WebControlProCover):
|
||||
"""Representation of a WMS based valance."""
|
||||
|
||||
_attr_translation_key = "valance"
|
||||
_attr_device_class = CoverDeviceClass.SHADE
|
||||
_attr_translation_key = "valance"
|
||||
_drive_action_desc = ACTION_DESC.ValanceDrive
|
||||
|
||||
def __init__(self, config_entry_id: str, dest: Destination) -> None:
|
||||
"""Initialize the entity with destination channel."""
|
||||
super().__init__(config_entry_id, dest)
|
||||
if self._attr_unique_id:
|
||||
self._attr_unique_id += "-valance"
|
||||
|
||||
|
||||
class WebControlProRollerShutter(WebControlProCover):
|
||||
"""Representation of a WMS based roller shutter or blind."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.SHUTTER
|
||||
_drive_action_desc = ACTION_DESC.RollerShutterBlindDrive
|
||||
|
||||
|
||||
class WebControlProSlat(WebControlProCover):
|
||||
"""Representation of a WMS based blind using a slat drive."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.BLIND
|
||||
_drive_action_desc = ACTION_DESC.SlatDrive
|
||||
|
||||
|
||||
class WebControlProSlatRotate(WebControlProSlat):
|
||||
"""Representation of a WMS based blind which supports tilting."""
|
||||
|
||||
_tilt_action_desc = ACTION_DESC.SlatRotate
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Open the cover and tilt to minimum like the WMS WebControl pro."""
|
||||
action_drive = self._dest.action(self._drive_action_desc)
|
||||
action_list = action_drive.prep(percentage=0)
|
||||
action_tilt = self._dest.action(self._tilt_action_desc)
|
||||
action_list += action_tilt.prep(rotation=action_tilt.minValue)
|
||||
await action_list()
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Close the cover and tilt to maximum like the WMS WebControl pro."""
|
||||
action_drive = self._dest.action(self._drive_action_desc)
|
||||
action_list = action_drive.prep(percentage=100)
|
||||
action_tilt = self._dest.action(self._tilt_action_desc)
|
||||
action_list += action_tilt.prep(rotation=action_tilt.maxValue)
|
||||
await action_list()
|
||||
|
||||
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
||||
"""Move the cover to a specific position and tilt for open/close."""
|
||||
target_position = kwargs[ATTR_POSITION]
|
||||
if target_position == 0:
|
||||
await self.async_close_cover()
|
||||
elif target_position == 100:
|
||||
await self.async_open_cover()
|
||||
else:
|
||||
await super().async_set_cover_position(**kwargs)
|
||||
|
||||
@property
|
||||
def current_cover_tilt_position(self) -> int | None:
|
||||
"""Return current position of cover tilt."""
|
||||
action = self._dest.action(self._tilt_action_desc)
|
||||
if action["rotation"] is None:
|
||||
return None
|
||||
return 100 - ranged_value_to_percentage(
|
||||
(action.minValue, action.maxValue),
|
||||
action["rotation"],
|
||||
)
|
||||
|
||||
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
||||
"""Set the cover tilt position."""
|
||||
action = self._dest.action(self._tilt_action_desc)
|
||||
rotation = percentage_to_ranged_value(
|
||||
(action.minValue, action.maxValue),
|
||||
100 - kwargs[ATTR_TILT_POSITION],
|
||||
)
|
||||
await action(rotation=rotation)
|
||||
|
||||
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
|
||||
"""Open the cover tilt."""
|
||||
action = self._dest.action(self._tilt_action_desc)
|
||||
# 0 is the open position for the tilt in WMS WebControl pro,
|
||||
# with the open position the slat is parallel to the ground.
|
||||
# This position will let the most light through and
|
||||
# is required to fully store the cover in the box.
|
||||
await action(rotation=0)
|
||||
|
||||
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
|
||||
"""Close the cover tilt."""
|
||||
action = self._dest.action(self._tilt_action_desc)
|
||||
# maxValue is the close position for the tilt in WMS WebControl pro,
|
||||
# with the close position the slat is perpendicular to the ground.
|
||||
# This position will block the light best.
|
||||
await action(rotation=action.maxValue)
|
||||
|
||||
@@ -19,6 +19,8 @@ class WebControlProGenericEntity(Entity):
|
||||
dest_id_str = str(dest.id)
|
||||
self._dest = dest
|
||||
self._attr_unique_id = dest_id_str
|
||||
if self.translation_key:
|
||||
self._attr_unique_id += f"-{self.translation_key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, dest_id_str)},
|
||||
manufacturer=MANUFACTURER,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"entity": {
|
||||
"button": {
|
||||
"rotation-reset": {
|
||||
"default": "mdi:numeric-0-box-multiple-outline"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"rotation": {
|
||||
"default": "mdi:rotate-360"
|
||||
},
|
||||
"rotation-max": {
|
||||
"default": "mdi:rotate-right"
|
||||
},
|
||||
"rotation-min": {
|
||||
"default": "mdi:rotate-left"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
"""Support for range-options of slat-cover connected with WMS WebControl pro."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import timedelta
|
||||
|
||||
from wmspro.const import WMS_WebControl_pro_API_actionDescription as ACTION_DESC
|
||||
|
||||
from homeassistant.components.number import NumberEntity
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import WebControlProConfigEntry
|
||||
from .entity import WebControlProGenericEntity
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: WebControlProConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the WMS based slat rotation number entities from a config entry."""
|
||||
hub = config_entry.runtime_data
|
||||
|
||||
entities: list[WebControlProGenericEntity] = []
|
||||
for d in hub.dests.values():
|
||||
if d.hasAction(ACTION_DESC.SlatRotate):
|
||||
entities.append(WebControlProSlatRangeMin(config_entry.entry_id, d))
|
||||
entities.append(WebControlProSlatRangeMax(config_entry.entry_id, d))
|
||||
entities.append(WebControlProSlatRotationRaw(config_entry.entry_id, d))
|
||||
if not d.hasAction(ACTION_DESC.SlatDrive):
|
||||
# Only add the numeric slat rotation entity if no cover entity exists
|
||||
entities.append(WebControlProSlatRotation(config_entry.entry_id, d))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class WebControlProSlatRange(WebControlProGenericEntity, NumberEntity):
|
||||
"""Representation of a WMS based range-option for a slat-based cover."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
_value_func: Callable
|
||||
_value_name: str
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the entity and learn current rotation."""
|
||||
await super().async_update()
|
||||
|
||||
# Learn min/max rotation if different from action limits
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
rotation = action["rotation"]
|
||||
if rotation and action.wms__minValue < rotation < action.wms__maxValue:
|
||||
await self.async_set_native_value(
|
||||
self._value_func(self.native_value, rotation)
|
||||
)
|
||||
|
||||
@property
|
||||
def native_min_value(self) -> float:
|
||||
"""Return the minimum value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
# Use wms__ prefix to get the raw value from the hub without overwrite
|
||||
return self._value_func(action.wms__minValue, 0)
|
||||
|
||||
@property
|
||||
def native_max_value(self) -> float:
|
||||
"""Return the maximum value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
# Use wms__ prefix to get the raw value from the hub without overwrite
|
||||
return self._value_func(0, action.wms__maxValue)
|
||||
|
||||
@property
|
||||
def native_value(self) -> float:
|
||||
"""Return the current min/max value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
# Pull the current min/max rotation from the custom overwrite if set
|
||||
value = action[self._value_name]
|
||||
# -75 and 75 are community-provided sane defaults for various devices
|
||||
if value is None:
|
||||
value = self._value_func(-75, 75)
|
||||
return value
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Update the current min/max value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
# Push the new min/max rotation to the hub as custom overwrite via the action items
|
||||
action[self._value_name] = value
|
||||
# The library will take care of the update and persistence on the next poll refresh
|
||||
|
||||
|
||||
class WebControlProSlatRangeMin(WebControlProSlatRange):
|
||||
"""Representation of the minimum rotation range-option for a slat-based cover."""
|
||||
|
||||
_attr_translation_key = "rotation-min"
|
||||
|
||||
_value_name = "minValue"
|
||||
_value_func = min
|
||||
|
||||
|
||||
class WebControlProSlatRangeMax(WebControlProSlatRange):
|
||||
"""Representation of the maximum rotation range-option for a slat-based cover."""
|
||||
|
||||
_attr_translation_key = "rotation-max"
|
||||
|
||||
_value_name = "maxValue"
|
||||
_value_func = max
|
||||
|
||||
|
||||
class WebControlProSlatRotation(WebControlProGenericEntity, NumberEntity):
|
||||
"""Representation of the WMS based slat-rotation for a slat-based cover."""
|
||||
|
||||
_attr_translation_key = "rotation"
|
||||
|
||||
@property
|
||||
def native_min_value(self) -> float:
|
||||
"""Return the minimum value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
return action.minValue
|
||||
|
||||
@property
|
||||
def native_max_value(self) -> float:
|
||||
"""Return the maximum value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
return action.maxValue
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the current value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
rotation = action["rotation"]
|
||||
if rotation is None:
|
||||
return None
|
||||
return rotation
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Update the current value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
await action(rotation=value)
|
||||
|
||||
|
||||
class WebControlProSlatRotationRaw(WebControlProSlatRotation):
|
||||
"""Representation of the WMS based raw slat-rotation for a slat-based cover."""
|
||||
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_translation_key = "rotation-raw"
|
||||
|
||||
@property
|
||||
def native_min_value(self) -> float:
|
||||
"""Return the minimum value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
return action.wms__minValue
|
||||
|
||||
@property
|
||||
def native_max_value(self) -> float:
|
||||
"""Return the maximum value."""
|
||||
action = self._dest.action(ACTION_DESC.SlatRotate)
|
||||
return action.wms__maxValue
|
||||
@@ -21,5 +21,31 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"button": {
|
||||
"rotation-reset": {
|
||||
"name": "Reset Rotation"
|
||||
}
|
||||
},
|
||||
"cover": {
|
||||
"valance": {
|
||||
"name": "Valance"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"rotation": {
|
||||
"name": "Rotation"
|
||||
},
|
||||
"rotation-max": {
|
||||
"name": "Maximum Rotation"
|
||||
},
|
||||
"rotation-min": {
|
||||
"name": "Minimum Rotation"
|
||||
},
|
||||
"rotation-raw": {
|
||||
"name": "Raw Rotation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,11 +139,11 @@ class XiaomiCoordinatedMiioEntity[_T: DataUpdateCoordinator[Any]](
|
||||
|
||||
@staticmethod
|
||||
def _parse_datetime_time(initial_time: datetime.time) -> str:
|
||||
time = datetime.datetime.now().replace(
|
||||
time = datetime.datetime.now().replace( # pylint: disable=home-assistant-enforce-naive-now
|
||||
hour=initial_time.hour, minute=initial_time.minute, second=0, microsecond=0
|
||||
)
|
||||
|
||||
if time < datetime.datetime.now():
|
||||
if time < datetime.datetime.now(): # pylint: disable=home-assistant-enforce-naive-now
|
||||
time += datetime.timedelta(days=1)
|
||||
|
||||
return time.isoformat()
|
||||
|
||||
@@ -61,7 +61,7 @@ def _activity_time_based(latest: Activity) -> Activity | None:
|
||||
"""Get the latest state of the sensor."""
|
||||
start = latest.activity_start_time
|
||||
end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION
|
||||
if start <= datetime.now() <= end:
|
||||
if start <= datetime.now() <= end: # pylint: disable=home-assistant-enforce-naive-now
|
||||
return latest
|
||||
return None
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ rules:
|
||||
comment: This integration does not register custom service actions.
|
||||
appropriate-polling:
|
||||
status: done
|
||||
comment: 5 minute interval. MQTT carries live state; polling is what surfaces the online -> offline transition since the broker doesn't push disconnect events.
|
||||
comment: Live state is pushed over MQTT; the coordinator polls REST every 5 min only for the device roster and player config (firmware, day/night times).
|
||||
brands: done
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
@@ -48,7 +48,7 @@ rules:
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: The integration supports local DHCP discovery (via hostname pattern), but does not implement a separate discovery update handling flow.
|
||||
comment: Cloud connection; DHCP discovery only triggers setup, no network address is persisted to update.
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
@@ -70,7 +70,7 @@ rules:
|
||||
comment: Authorization is the only configuration; reauth covers re-linking the account.
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: No repair issues are raised yet.
|
||||
comment: Auth failures go through the reauth flow; other errors are transient and retried by the coordinator.
|
||||
stale-devices: todo
|
||||
|
||||
# Platinum
|
||||
|
||||
@@ -1624,7 +1624,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
# save the backup to a file just in case
|
||||
self.backup_filepath = Path(
|
||||
self.hass.config.path(
|
||||
f"zwavejs_nvm_backup_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.bin"
|
||||
f"zwavejs_nvm_backup_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.bin" # pylint: disable=home-assistant-enforce-naive-now
|
||||
)
|
||||
)
|
||||
try:
|
||||
|
||||
@@ -490,11 +490,11 @@ class _HistoryPrimingManager:
|
||||
The flush a condition relies on must begin after that condition started
|
||||
tracking its entities, or the read could miss a change still queued in the
|
||||
recorder and compute too generous an anchor. A condition therefore never
|
||||
rides a flush that was already running when it arrived (the lobby); it waits
|
||||
that one out and joins the next, and re-attempts if the flush it rode was
|
||||
cancelled before completing. This mirrors `ReloadServiceHelper` minus its
|
||||
target de-duplication, which does not apply because each condition reads its
|
||||
own entities.
|
||||
relies on a flush that was already running when it arrived (the lobby); it
|
||||
waits that one out and joins the next, re-attempting if the flush it waited
|
||||
for was cancelled before completing. This mirrors `ReloadServiceHelper`
|
||||
minus its target de-duplication, which does not apply because each condition
|
||||
reads its own entities.
|
||||
"""
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
@@ -516,11 +516,13 @@ class _HistoryPrimingManager:
|
||||
async def _async_flush(self) -> None:
|
||||
"""Return once a recorder flush that began no earlier than this call ends.
|
||||
|
||||
The first condition of a generation performs the flush; the rest ride it.
|
||||
The first condition of a generation performs the flush; the rest rely on
|
||||
it.
|
||||
"""
|
||||
async with self._flush_condition:
|
||||
# Lobby: a flush already running began before we arrived, so it may
|
||||
# not capture our entity's queued changes. Wait it out, don't ride it.
|
||||
# not capture our entity's queued changes. Wait it out, don't rely on
|
||||
# it.
|
||||
if self._flushing:
|
||||
await self._flush_condition.wait()
|
||||
|
||||
@@ -530,7 +532,8 @@ class _HistoryPrimingManager:
|
||||
# First past the lobby this generation: we run the flush.
|
||||
self._flushing = True
|
||||
break
|
||||
# A peer began a fresh flush after we cleared the lobby; ride it.
|
||||
# A peer began a fresh flush after we cleared the lobby; wait for
|
||||
# it.
|
||||
await self._flush_condition.wait()
|
||||
if self._flush_ok:
|
||||
return
|
||||
|
||||
@@ -29,7 +29,7 @@ cached-ipaddress==1.1.2
|
||||
certifi>=2021.5.30
|
||||
ciso8601==2.3.3
|
||||
cronsim==2.7
|
||||
cryptography==48.0.0
|
||||
cryptography==48.0.1
|
||||
dbus-fast==5.0.16
|
||||
file-read-backwards==2.0.0
|
||||
fnv-hash-fast==2.0.3
|
||||
|
||||
@@ -31,9 +31,7 @@ DISCOVERY_INTEGRATIONS: dict[str, Iterable[str]] = {
|
||||
}
|
||||
DEPRECATED_PACKAGES: dict[str, tuple[str, str]] = {
|
||||
# old_package_name: (reason, breaks_in_ha_version)
|
||||
"pyserial": ("should be replaced by serialx", "2027.1"),
|
||||
"pyserial-asyncio": ("should be replaced by serialx", "2027.1"),
|
||||
"pyserial-asyncio-fast": ("should be replaced by serialx", "2027.1"),
|
||||
"pyserial-asyncio": ("should be replaced by pyserial-asyncio-fast", "2026.7"),
|
||||
}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ Every check has a code following the
|
||||
| `W7426` | [`home-assistant-tests-direct-async-unload-entry`](#w7426-home-assistant-tests-direct-async-unload-entry) | Tests should not call an integration's `async_unload_entry` directly |
|
||||
| `C7414` | [`home-assistant-enforce-utcnow`](#c7414-home-assistant-enforce-utcnow) | Use `homeassistant.util.dt.utcnow` instead of `datetime.now(UTC)` |
|
||||
| `C7425` | [`home-assistant-enforce-now`](#c7425-home-assistant-enforce-now) | Use `homeassistant.util.dt.now` instead of `datetime.now(<tz>)` |
|
||||
| `C7427` | [`home-assistant-enforce-naive-now`](#c7427-home-assistant-enforce-naive-now) | Use `homeassistant.util.dt.naive_now` instead of `datetime.now()` |
|
||||
| `W7423` | [`home-assistant-missing-entity-unique-id`](#w7423-home-assistant-missing-entity-unique-id) | Entity class does not statically guarantee a non-None unique id |
|
||||
| `W7424` | [`home-assistant-entity-unique-id-static`](#w7424-home-assistant-entity-unique-id-static) | Entity class sets `_attr_unique_id` to a static string at class level |
|
||||
| `C7412` | [`home-assistant-entity-description-redundant-default`](#c7412-home-assistant-entity-description-redundant-default) | Setting an EntityDescription field to its default value is redundant |
|
||||
@@ -486,8 +487,27 @@ helper returns an aware `datetime` in the given time zone (defaulting to
|
||||
`DEFAULT_TIME_ZONE`), keeping the codebase consistent in how the current
|
||||
local time is obtained. The UTC case (`datetime.now(UTC)`) is handled by
|
||||
the [`home-assistant-enforce-utcnow`](#c7414-home-assistant-enforce-utcnow)
|
||||
checker, and `datetime.now()` with no argument is not flagged since it
|
||||
returns a naive local `datetime`.
|
||||
checker, and `datetime.now()` with no argument is handled by the
|
||||
[`home-assistant-enforce-naive-now`](#c7427-home-assistant-enforce-naive-now)
|
||||
checker.
|
||||
|
||||
|
||||
## `home_assistant_enforce_naive_now` checker
|
||||
|
||||
Ensures the Home Assistant helper is used to get the current naive local time.
|
||||
|
||||
### `C7427`: `home-assistant-enforce-naive-now`
|
||||
|
||||
Use `homeassistant.util.dt.naive_now()` instead of `datetime.datetime.now()`
|
||||
called without a time zone argument. The helper returns a naive `datetime` in
|
||||
system local time, keeping the codebase consistent in how the current naive
|
||||
local time is obtained and documenting that a naive `datetime` is intentional.
|
||||
An explicit `None` argument (`datetime.now(None)` or `datetime.now(tz=None)`)
|
||||
returns a naive `datetime` too and is flagged. The aware cases
|
||||
(`datetime.now(<tz>)` and `datetime.now(UTC)`) are handled by the
|
||||
[`home-assistant-enforce-now`](#c7425-home-assistant-enforce-now) and
|
||||
[`home-assistant-enforce-utcnow`](#c7414-home-assistant-enforce-utcnow)
|
||||
checkers.
|
||||
|
||||
|
||||
## `home_assistant_entity_unique_id` checker
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Checker that enforces ``homeassistant.util.dt.naive_now`` over ``datetime.now()``.
|
||||
|
||||
Home Assistant exposes ``homeassistant.util.dt.naive_now`` -- a helper that
|
||||
returns a naive ``datetime`` in system local time. Calling
|
||||
``datetime.datetime.now()`` with no time zone argument does the same thing but
|
||||
bypasses the helper. Using ``dt_util.naive_now`` keeps the codebase consistent
|
||||
in how the current naive local time is obtained and documents that a naive
|
||||
``datetime`` is intentional.
|
||||
|
||||
The aware cases (``datetime.now(tz)`` and ``datetime.now(UTC)``) are handled by
|
||||
the ``home-assistant-enforce-now`` and ``home-assistant-enforce-utcnow``
|
||||
checkers respectively.
|
||||
"""
|
||||
|
||||
from pylint.lint import PyLinter
|
||||
|
||||
from pylint_home_assistant.helpers.datetime_now import (
|
||||
CASE_NAIVE,
|
||||
HassEnforceDatetimeNowChecker,
|
||||
)
|
||||
|
||||
|
||||
class HassEnforceNaiveNowChecker(HassEnforceDatetimeNowChecker):
|
||||
"""Checker that flags ``datetime.now()`` calls without a time zone."""
|
||||
|
||||
name = "home_assistant_enforce_naive_now"
|
||||
msgs = {
|
||||
"C7427": (
|
||||
"Use `homeassistant.util.dt.naive_now()` instead of `datetime.now()`",
|
||||
"home-assistant-enforce-naive-now",
|
||||
"Used when ``datetime.datetime.now()`` is called without a time zone "
|
||||
"to create a naive ``datetime`` in system local time. Use the "
|
||||
"``homeassistant.util.dt.naive_now`` helper instead. The aware cases "
|
||||
"are handled by the ``home-assistant-enforce-now`` and "
|
||||
"``home-assistant-enforce-utcnow`` checkers.",
|
||||
),
|
||||
}
|
||||
|
||||
message = "home-assistant-enforce-naive-now"
|
||||
flagged_case = CASE_NAIVE
|
||||
|
||||
|
||||
def register(linter: PyLinter) -> None:
|
||||
"""Register the checker."""
|
||||
linter.register_checker(HassEnforceNaiveNowChecker(linter))
|
||||
@@ -15,7 +15,10 @@ flagged.
|
||||
|
||||
from pylint.lint import PyLinter
|
||||
|
||||
from pylint_home_assistant.helpers.datetime_now import HassEnforceDatetimeNowChecker
|
||||
from pylint_home_assistant.helpers.datetime_now import (
|
||||
CASE_OTHER,
|
||||
HassEnforceDatetimeNowChecker,
|
||||
)
|
||||
|
||||
|
||||
class HassEnforceNowChecker(HassEnforceDatetimeNowChecker):
|
||||
@@ -34,7 +37,7 @@ class HassEnforceNowChecker(HassEnforceDatetimeNowChecker):
|
||||
}
|
||||
|
||||
message = "home-assistant-enforce-now"
|
||||
flags_utc = False
|
||||
flagged_case = CASE_OTHER
|
||||
|
||||
|
||||
def register(linter: PyLinter) -> None:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user