-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtime.py
More file actions
133 lines (109 loc) · 4.96 KB
/
time.py
File metadata and controls
133 lines (109 loc) · 4.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""SEM Solar Energy Management time entities.
Per-charger "charge by" deadline (#246, Phase 2): the time the Min floor should
be reached by. Drives the night-charging current scaling in
``coordinator/ev_control.py`` (a short deadline raises the current; an
impossible one warns). Stored as ``HH:MM`` in the charger config dict so the
"set as default" button (#246) can copy it and it persists across restarts.
"""
from __future__ import annotations
import logging
from datetime import time as dt_time
from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEFAULT_EV_TARGET_TIME
from .coordinator import SEMCoordinator
type SEMConfigEntry = ConfigEntry[SEMCoordinator]
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
def _parse_hhmm(value: str | None) -> dt_time:
"""Parse an ``HH:MM`` string into a ``datetime.time`` (falls back to default)."""
try:
parts = str(value).split(":")
return dt_time(int(parts[0]), int(parts[1]) if len(parts) > 1 else 0)
except (ValueError, AttributeError, IndexError):
h, m = DEFAULT_EV_TARGET_TIME.split(":")
return dt_time(int(h), int(m))
async def async_setup_entry(
hass: HomeAssistant, entry: SEMConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up per-charger target-time entities."""
coordinator: SEMCoordinator = entry.runtime_data
full_config = {**entry.data, **entry.options}
ev_chargers = full_config.get("ev_chargers", [])
entities: list[TimeEntity] = []
for charger_cfg in ev_chargers:
cid = charger_cfg.get("id", "ev_charger")
cname = charger_cfg.get("name", "EV Charger")
desc = TimeEntityDescription(
key=f"charger_{cid}_target_time",
name=f"{cname} Charge By",
icon="mdi:clock-end",
entity_category=EntityCategory.CONFIG,
)
initial = charger_cfg.get("ev_target_time") or full_config.get(
"ev_target_time", DEFAULT_EV_TARGET_TIME
)
entities.append(
SEMPerChargerTime(coordinator, desc, entry, cid, "ev_target_time", initial)
)
if entities:
_LOGGER.info("Created %d per-charger target-time entities", len(entities))
async_add_entities(entities)
class SEMPerChargerTime(CoordinatorEntity, TimeEntity):
"""Per-charger charge-by deadline, persisted in the charger config dict (#246)."""
_attr_has_entity_name = True
_attr_entity_category = EntityCategory.CONFIG
def __init__(
self,
coordinator: SEMCoordinator,
description: TimeEntityDescription,
entry: ConfigEntry,
charger_id: str,
config_key: str,
initial_value: str,
) -> None:
"""Initialize per-charger time entity."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{entry.entry_id}_{description.key}"
self._attr_translation_key = description.key
self._attr_suggested_object_id = f"sem_{description.key}"
self.entity_id = f"time.sem_{description.key}"
self._entry = entry
self._charger_id = charger_id
self._config_key = config_key
self._attr_native_value = _parse_hhmm(initial_value)
@property
def available(self) -> bool:
"""Return if entity is available."""
return self.coordinator.last_update_success
@property
def device_info(self):
"""Return device information."""
return self.coordinator.device_info
async def async_set_value(self, value: dt_time) -> None:
"""Persist the deadline (HH:MM) into the charger config (snapshot-keyed skip)."""
self._attr_native_value = value
hhmm = value.strftime("%H:%M")
new_options = {**self._entry.options}
# Copy charger dicts — in-place mutation leaves entry.options unchanged so
# async_update_entry skips persisting and the value reverts on restart (#245).
ev_chargers = [dict(c) for c in new_options.get("ev_chargers", [])]
for charger in ev_chargers:
if charger.get("id") == self._charger_id:
charger[self._config_key] = hhmm
break
new_options["ev_chargers"] = ev_chargers
if isinstance(getattr(self.coordinator, "config", None), dict):
self.coordinator.config.update({**self._entry.data, **new_options})
self.coordinator._skip_options_reload = new_options
self.hass.config_entries.async_update_entry(self._entry, options=new_options)
self.async_write_ha_state()
_LOGGER.info(
"Updated per-charger %s.%s to %s",
self._charger_id, self._config_key, hhmm,
)