Home Assistant Git Exporter

This commit is contained in:
root
2024-08-09 06:45:02 +02:00
parent 60abdd866c
commit 80fc630f5e
624 changed files with 27739 additions and 4497 deletions

View File

@@ -6,7 +6,7 @@ DOMAIN = "irrigation_unlimited"
DOMAIN_DATA = f"{DOMAIN}_data"
COORDINATOR = "coordinator"
COMPONENT = "component"
VERSION = "2024.5.0"
VERSION = "2024.8.0"
ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/"
ISSUE_URL = "https://github.com/rgc99/irrigation_unlimited/issues"
@@ -141,6 +141,7 @@ SERVICE_SUSPEND = "suspend"
SERVICE_SKIP = "skip"
SERVICE_PAUSE = "pause"
SERVICE_RESUME = "resume"
SERVICE_GET_INFO = "get_info"
# Events
EVENT_START = "start"
@@ -161,6 +162,7 @@ STATUS_DELAY = "delay"
TIMELINE_STATUS = "status"
TIMELINE_START = "start"
TIMELINE_END = "end"
TIMELINE_SCHEDULE = "schedule"
TIMELINE_SCHEDULE_NAME = "schedule_name"
TIMELINE_ADJUSTMENT = "adjustment"
@@ -211,14 +213,16 @@ ATTR_VOLUME = "volume"
ATTR_FLOW_RATE = "flow_rate"
ATTR_SWITCH_ENTITIES = "switch_entity_id"
ATTR_SEQUENCE_COUNT = "sequence_count"
ATTR_CONTROLLER_ID = "controller_id"
ATTR_ZONE_ID = "zone_id"
ATTR_CONTROLLERS = "controllers"
ATTR_SEQUENCES = "sequences"
ATTR_VERSION = "version"
# Resources
RES_MANUAL = "Manual"
RES_NOT_RUNNING = "not running"
RES_NONE = "none"
RES_CONTROLLER = "Controller"
RES_ZONE = "Zone"
RES_MASTER = "Master"
RES_TIMELINE_RUNNING = "running"
RES_TIMELINE_SCHEDULED = "scheduled"
RES_TIMELINE_NEXT = "next"

View File

@@ -1,10 +1,15 @@
"""History access and caching. This module runs asynchronously collecting
and caching history data"""
from datetime import datetime, timedelta
from typing import Callable, OrderedDict, Any
from homeassistant.core import HomeAssistant, State, CALLBACK_TYPE
from homeassistant.util import dt
from homeassistant.components.recorder.const import DATA_INSTANCE as RECORDER_INSTANCE
try:
from homeassistant.helpers.recorder import DATA_INSTANCE
except ImportError:
from homeassistant.components.recorder.const import DATA_INSTANCE
from homeassistant.components.recorder import get_instance
from homeassistant.helpers.event import (
async_track_point_in_utc_time,
@@ -15,6 +20,7 @@ from homeassistant.const import STATE_ON
from .const import (
ATTR_CURRENT_ADJUSTMENT,
ATTR_CURRENT_NAME,
ATTR_CURRENT_SCHEDULE,
CONF_ENABLED,
CONF_HISTORY,
CONF_HISTORY_REFRESH,
@@ -22,6 +28,7 @@ from .const import (
CONF_REFRESH_INTERVAL,
CONF_SPAN,
TIMELINE_ADJUSTMENT,
TIMELINE_SCHEDULE,
TIMELINE_SCHEDULE_NAME,
TIMELINE_START,
TIMELINE_END,
@@ -166,6 +173,7 @@ class IUHistory:
result = OrderedDict()
result[TIMELINE_START] = round_seconds_dt(item.last_changed)
result[TIMELINE_END] = round_seconds_dt(end)
result[TIMELINE_SCHEDULE] = item.attributes.get(ATTR_CURRENT_SCHEDULE)
result[TIMELINE_SCHEDULE_NAME] = item.attributes.get(ATTR_CURRENT_NAME)
result[TIMELINE_ADJUSTMENT] = item.attributes.get(
ATTR_CURRENT_ADJUSTMENT, ""
@@ -197,7 +205,7 @@ class IUHistory:
return
start = self._stime - self._history_span
if RECORDER_INSTANCE in self._hass.data:
if DATA_INSTANCE in self._hass.data:
data = await get_instance(self._hass).async_add_executor_job(
history.get_significant_states,
self._hass,

View File

@@ -165,6 +165,7 @@ from .const import (
RES_TIMELINE_SCHEDULED,
TIMELINE_ADJUSTMENT,
TIMELINE_END,
TIMELINE_SCHEDULE,
TIMELINE_SCHEDULE_NAME,
TIMELINE_START,
MONTHS,
@@ -1064,10 +1065,10 @@ class IUVolume:
self._zone = zone
# Config parameters
self._sensor_id: str = None
self._volume_precision: int = 3
self._volume_scale: float = 1
self._flow_rate_precision: int = 3
self._flow_rate_scale: float = 3600
self._volume_precision: int = None
self._volume_scale: float = None
self._flow_rate_precision: int = None
self._flow_rate_scale: float = None
# Private variables
self._callback_remove: CALLBACK_TYPE = None
self._start_volume: Decimal = None
@@ -1078,9 +1079,11 @@ class IUVolume:
str, Callable[[datetime, "IUZone", Decimal, Decimal], None]
] = {}
self._flow_rates: list[Decimal] = []
self._flow_rate_sum = Decimal(0)
self._flow_rate_sum: Decimal = None
self._flow_rate_sma: Decimal = None
self._sensor_readings: list[IUVolumeSensorReading] = []
self.reset_config()
self.reset_readings()
@property
def total(self) -> float | None:
@@ -1096,6 +1099,25 @@ class IUVolume:
return float(self._flow_rate_sma)
return None
def reset_config(self) -> None:
"""Reset this object"""
self.end_record(None)
self._sensor_id = None
self._volume_precision = 3
self._volume_scale = 1
self._flow_rate_precision = 3
self._flow_rate_scale = 3600
def reset_readings(self) -> None:
"""Reset reading parameters"""
self._start_volume = None
self._total_volume = None
self._start_time = None
self._sensor_readings.clear()
self._flow_rates.clear()
self._flow_rate_sum = 0
self._flow_rate_sma = None
def load(self, config: OrderedDict, all_zones: OrderedDict) -> "IUSwitch":
"""Load volume data from the configuration"""
@@ -1114,6 +1136,8 @@ class IUVolume:
CONF_FLOW_RATE_SCALE, self._flow_rate_scale
)
self.reset_config()
self.reset_readings()
if all_zones is not None:
load_params(all_zones.get(CONF_VOLUME))
load_params(config.get(CONF_VOLUME))
@@ -1174,37 +1198,32 @@ class IUVolume:
parameters in the event message"""
return event
async def sensor_state_change(self, event: HAEvent):
event = self.event_hook(event)
stime = event.time_fired
try:
value = self.read_sensor(stime)
except ValueError as e:
self._coordinator.logger.log_invalid_meter_value(stime, e)
except IUVolumeSensorError:
self._coordinator.logger.log_invalid_meter_id(stime, self._sensor_id)
else:
self._total_volume = value - self._start_volume
# Notifiy our trackers
for listener in list(self._listeners.values()):
await listener(
stime,
self._zone,
self._total_volume,
self._flow_rate_sma,
)
def start_record(self, stime: datetime) -> None:
"""Start recording volume information"""
def sensor_state_change(event: HAEvent):
event = self.event_hook(event)
try:
value = self.read_sensor(event.time_fired)
except ValueError as e:
self._coordinator.logger.log_invalid_meter_value(stime, e)
except IUVolumeSensorError:
self._coordinator.logger.log_invalid_meter_id(stime, self._sensor_id)
else:
self._total_volume = value - self._start_volume
# Notifiy our trackers
for listener in list(self._listeners.values()):
listener(
event.time_fired,
self._zone,
self._total_volume,
self._flow_rate_sma,
)
self.reset_readings()
if self._sensor_id is None:
return
self._start_volume = self._total_volume = None
self._start_time = stime
self._sensor_readings.clear()
self._flow_rates.clear()
self._flow_rate_sum = 0
self._flow_rate_sma = None
try:
self._start_volume = self.read_sensor(stime)
@@ -1214,7 +1233,7 @@ class IUVolume:
self._coordinator.logger.log_invalid_meter_id(stime, self._sensor_id)
else:
self._callback_remove = async_track_state_change_event(
self._hass, self._sensor_id, sensor_state_change
self._hass, self._sensor_id, self.sensor_state_change
)
IUVolume.trackers += 1
@@ -1262,7 +1281,6 @@ class IURunStatus(Enum):
return IURunStatus.RUNNING
if stime >= end_time:
return IURunStatus.EXPIRED
return IURunStatus.UNKNOWN
class IURun(IUBase):
@@ -1300,6 +1318,14 @@ class IURun(IUBase):
self._status = self._get_status(stime)
self.master_run: "IURun" = None
def __str__(self) -> str:
return (
f"status: {self._status.name}, "
f"start: {dt2lstr(self.start_time)}, "
f"end: {dt2lstr(self.end_time)}, "
f"schedule: {self.schedule_name}"
)
@property
def expired(self) -> bool:
"""Indicate if run has expired"""
@@ -1475,22 +1501,24 @@ class IURun(IUBase):
"""Update the count down timers"""
if self.running:
self._remaining_time = self._end_time - stime
total_duration: timedelta = self._end_time - self._start_time
time_elapsed: timedelta = stime - self._start_time
self._percent_complete = int((time_elapsed / total_duration) * 100)
duration: timedelta = self._end_time - self._start_time
elapsed: timedelta = stime - self._start_time
self._percent_complete = (
int((elapsed / duration) * 100) if duration > timedelta(0) else 0
)
return True
return False
def pause(self, stime: datetime) -> None:
"""Change the pause status of the run"""
if self._pause_time is not None:
if self.expired or self._pause_time is not None:
return
self._pause_time = stime
self.update_status(stime)
def resume(self, stime: datetime) -> None:
"""Resume a paused run"""
if self._pause_time is None:
if self.expired or self._pause_time is None:
return
delta = stime - self._pause_time
self._start_time += delta
@@ -1503,6 +1531,7 @@ class IURun(IUBase):
result = OrderedDict()
result[TIMELINE_START] = self._start_time
result[TIMELINE_END] = self._end_time
result[TIMELINE_SCHEDULE] = self.schedule.id1 if self.schedule else 0
result[TIMELINE_SCHEDULE_NAME] = self.schedule_name
result[TIMELINE_ADJUSTMENT] = self.adjustment
return result
@@ -1845,13 +1874,15 @@ class IUScheduleQueue(IURunQueue):
"""Add a manual run to the queue. Cancel any existing
manual or running schedule"""
if self._current_run is not None:
self.pop_run(0)
# Remove any existing manual schedules
if not queue:
if self._current_run is not None:
self.pop_run(0)
for manual in (run for run in self if run.is_manual()):
self.remove_run(manual)
elif self._current_run is not None and not self._current_run.is_manual():
self.pop_run(0)
self._current_run = None
self._next_run = None
@@ -2651,6 +2682,15 @@ class IUSequenceZone(IUBase):
result.append(run)
return result
def start_time(runs: list[IURun]) -> datetime:
result: datetime = None
for run in runs:
if result == None or run.start_time < result:
result = run.start_time
return result
runs = zone_runs()
start = start_time(runs)
result = {}
result[ATTR_INDEX] = self.index
result[ATTR_ENABLED] = self.enabled
@@ -2659,7 +2699,8 @@ class IUSequenceZone(IUBase):
result[ATTR_ICON] = self.icon()
result[ATTR_ADJUSTMENT] = str(self.adjustment)
result[ATTR_ZONE_IDS] = self.zone_ids
result[ATTR_DURATION] = str(calc_on_time(zone_runs()))
result[ATTR_START] = dt.as_local(start) if start else None
result[ATTR_DURATION] = str(calc_on_time(runs))
return result
def muster(self, stime: datetime) -> IURQStatus:
@@ -2732,6 +2773,14 @@ class IUSequenceRun(IUBase):
self._remaining_time = timedelta(0)
self._percent_complete: int = 0
def __str__(self) -> str:
return (
f"status: {self._status.name}, "
f"start: {dt2lstr(self.start_time)}, "
f"end: {dt2lstr(self.end_time)}, "
f"schedule: {self.schedule_name}"
)
@property
def sequence(self) -> "IUSequence":
"""Return the sequence associated with this run"""
@@ -2742,6 +2791,13 @@ class IUSequenceRun(IUBase):
"""Return the schedule associated with this run"""
return self._schedule
@property
def schedule_name(self) -> str:
"""Return the name of the schedule"""
if self._schedule is not None:
return self._schedule.name
return RES_MANUAL
@property
def start_time(self) -> datetime:
"""Return the start time for this sequence"""
@@ -2882,9 +2938,7 @@ class IUSequenceRun(IUBase):
self._accumulated_duration += run.duration
zone.request_update()
self._runs_pre_allocate.clear()
self._status = IURunStatus.status(
stime, self.start_time, self.end_time, self._paused
)
self.update_status(stime)
def first_zone(self) -> IUZone:
"""Return the first zone"""
@@ -2976,8 +3030,8 @@ class IUSequenceRun(IUBase):
run.start_time = max(run.start_time + duration, stime)
run.end_time = max(run.end_time + duration, run.start_time)
run.duration = run.end_time - run.start_time
run.update_status(stime)
run.update_time_remaining(stime)
run.update_status(stime)
if self.running:
if runs is None:
@@ -2993,8 +3047,9 @@ class IUSequenceRun(IUBase):
if end_time is None or run.end_time > end_time:
end_time = run.end_time
self._end_time = end_time
self.update()
self.update_time_remaining(stime)
self.update_status(stime)
self.update(stime)
def skip(self, stime: datetime) -> None:
"""Skip to the next sequence zone"""
@@ -3066,7 +3121,9 @@ class IUSequenceRun(IUBase):
return 3
return 0
def split_run(run: IURun, start: datetime, duration=timedelta(0)) -> None:
def split_run(
run: IURun, szr: IUSequenceZoneRun, start: datetime, duration=timedelta(0)
) -> None:
split = run.zone.runs.add(
stime,
start,
@@ -3075,17 +3132,17 @@ class IUSequenceRun(IUBase):
run.schedule,
self,
)
self._runs[split] = None
self._runs[split] = szr
if self._paused is not None:
return
runs = self._runs.copy()
pause_list = self._runs.copy()
over_run = timedelta(0)
for run in runs:
for run, szr in runs.items():
state = run_state(run)
if state == 2:
split_run(run, stime - self._controller.postamble)
split_run(run, szr, stime - self._controller.postamble)
elif state == 3:
# Create a master postamble run out
if (
@@ -3095,15 +3152,17 @@ class IUSequenceRun(IUBase):
over_run = -self._controller.postamble
run.master_run.start_time = stime + over_run
run.start_time = stime
split_run(run, stime, over_run)
split_run(run, szr, stime, over_run)
elif state == 6:
pause_list.pop(run)
elif state == 5:
split_run(run, stime)
split_run(run, szr, stime)
run.start_time = stime
run.master_run.start_time = stime - self._controller.preamble
elif state == 4:
split_run(run, run.master_run.end_time - self._controller.postamble)
split_run(
run, szr, run.master_run.end_time - self._controller.postamble
)
if over_run != timedelta(0):
self.advance(stime, -over_run, runs)
pause_run(stime, pause_list)
@@ -3125,9 +3184,7 @@ class IUSequenceRun(IUBase):
resume_run(stime, self._runs)
self._end_time += stime - self._paused
self._paused = None
self._status = IURunStatus.status(
stime, self._start_time, self._end_time, self._paused
)
self.update_status(stime)
next_start = min(
(run.start_time for run in self._runs if not run.expired), default=None
@@ -3145,30 +3202,32 @@ class IUSequenceRun(IUBase):
"""Cancel the sequence run"""
self.advance(stime, -(self._end_time - stime))
def update(self) -> bool:
"""Update the status of the sequence"""
async def update_volume(
self, stime: datetime, zone: IUZone, volume: Decimal, rate: Decimal
) -> None:
# pylint: disable=unused-argument
if self._active_zone not in self._volume_stats:
self._volume_stats[self._active_zone] = {}
self._volume_stats[self._active_zone][zone] = volume
self._sequence.volume = sum(
sum(sta.values()) for sta in self._volume_stats.values()
)
if (limit := self._active_zone.sequence_zone.volume) is not None:
current_vol = sum(self._volume_stats[self._active_zone].values())
if current_vol >= limit:
await self._coordinator._hass.services.async_call(
DOMAIN,
SERVICE_SKIP,
{ATTR_ENTITY_ID: self._sequence.entity_id},
)
def update_volume(
stime: datetime, zone: IUZone, volume: Decimal, rate: Decimal
) -> None:
# pylint: disable=unused-argument
if self._active_zone not in self._volume_stats:
self._volume_stats[self._active_zone] = {}
self._volume_stats[self._active_zone][zone] = volume
self._sequence.volume = sum(
sum(sta.values()) for sta in self._volume_stats.values()
)
if (limit := self._active_zone.sequence_zone.volume) is not None:
current_vol = sum(self._volume_stats[self._active_zone].values())
if current_vol >= limit:
self._coordinator.service_call(
SERVICE_SKIP, self._controller, None, self._sequence, {}
)
def update(self, stime: datetime) -> bool:
"""Update the status of the sequence"""
def enable_trackers(sequence_zone: IUSequenceZone) -> None:
for zone in sequence_zone.zones:
self._volume_trackers.append(
zone.volume.track_volume_change(self.uid, update_volume)
zone.volume.track_volume_change(self.uid, self.update_volume)
)
def remove_trackers() -> None:
@@ -3176,17 +3235,37 @@ class IUSequenceRun(IUBase):
tracker()
self._volume_trackers.clear()
def sumarise(stime: datetime) -> dict[IUSequenceZoneRun, dict]:
"""Summarise the runs within each sequence zone run. A dict
is returned with start, end and status"""
result: dict[IUSequenceZoneRun, dict] = {}
for run, szr in self._runs.items():
item = result.get(szr)
if item is None:
item = {}
item["start_time"] = run.start_time
item["end_time"] = run.end_time
result[szr] = item
else:
item["start_time"] = min(item["start_time"], run.start_time)
item["end_time"] = max(item["end_time"], run.end_time)
for item in result.values():
item["status"] = IURunStatus.status(
stime, item["start_time"], item["end_time"], self._paused
)
return result
if self.paused:
return False
return not self._sequence.is_paused
result = False
for run, sequence_zone_run in self._runs.items():
if sequence_zone_run is None:
continue
if run.running and not self.running:
sruns = sumarise(stime)
last_date = max((run["end_time"] for run in sruns.values()), default=None)
for szr, run in sruns.items():
if run["status"] == IURunStatus.RUNNING and not self.running:
# Sequence/sequence zone is starting
self._status = IURunStatus.RUNNING
self._active_zone = sequence_zone_run
self._current_zone = sequence_zone_run
self._active_zone = szr
self._current_zone = szr
self._coordinator.notify_sequence(
EVENT_START,
self._controller,
@@ -3194,24 +3273,24 @@ class IUSequenceRun(IUBase):
self._schedule,
self,
)
enable_trackers(sequence_zone_run.sequence_zone)
enable_trackers(szr.sequence_zone)
self._sequence.volume = None
result |= True
elif run.running and sequence_zone_run != self._active_zone:
elif run["status"] == IURunStatus.RUNNING and szr != self._active_zone:
# Sequence zone is changing
self._active_zone = sequence_zone_run
self._current_zone = sequence_zone_run
self._active_zone = szr
self._current_zone = szr
remove_trackers()
enable_trackers(sequence_zone_run.sequence_zone)
enable_trackers(szr.sequence_zone)
result |= True
elif not run.running and sequence_zone_run == self._active_zone:
elif run["status"] != IURunStatus.RUNNING and szr == self._active_zone:
# Sequence zone is finishing
self._active_zone = None
remove_trackers()
self._current_zone = self.next_sequence_zone(sequence_zone_run)
if self.run_index(run) == len(self._runs) - 1:
self._current_zone = self.next_sequence_zone(szr)
if run["end_time"] == last_date:
# Sequence is finishing
self._status = IURunStatus.EXPIRED
self._coordinator.notify_sequence(
@@ -3227,7 +3306,7 @@ class IUSequenceRun(IUBase):
def update_time_remaining(self, stime: datetime) -> bool:
"""Update the count down timers"""
if not self.running:
if not (self.running or self.paused):
return False
self._remaining_time = self._end_time - stime
elapsed = stime - self._start_time
@@ -3237,6 +3316,14 @@ class IUSequenceRun(IUBase):
)
return True
def _get_status(self, stime: datetime) -> IURunStatus:
"""Determine the state of this run"""
return IURunStatus.status(stime, self._start_time, self._end_time, self._paused)
def update_status(self, stime: datetime) -> None:
"""Update the status of the run"""
self._status = self._get_status(stime)
def as_dict(self, include_expired=False) -> dict:
"""Return this sequence run as a dict"""
result = {}
@@ -3418,7 +3505,7 @@ class IUSequenceQueue(list[IUSequenceRun]):
i -= 1
return modified
def update_queue(self) -> IURQStatus:
def update_queue(self, stime: datetime) -> IURQStatus:
"""Update the run queue"""
# pylint: disable=too-many-branches
status = IURQStatus(0)
@@ -3427,14 +3514,14 @@ class IUSequenceQueue(list[IUSequenceRun]):
status |= IURQStatus.SORTED
for run in self:
if run.update():
if run.update(stime):
self._current_run = None
self._next_run = None
status |= IURQStatus.CHANGED
if self._current_run is None:
for run in self:
if run.running and run.on_time() != timedelta(0):
if (run.running or run.paused) and run.on_time() != timedelta(0):
self._current_run = run
self._next_run = None
status |= IURQStatus.UPDATED
@@ -3442,7 +3529,7 @@ class IUSequenceQueue(list[IUSequenceRun]):
if self._next_run is None:
for run in self:
if not run.running and run.on_time() != timedelta(0):
if not (run.running or run.paused) and run.on_time() != timedelta(0):
self._next_run = run
status |= IURQStatus.UPDATED
break
@@ -4074,7 +4161,11 @@ class IUSequence(IUBase):
if duration is not None and duration == timedelta(0):
duration = None
self._controller.muster_sequence(
self._controller.manual_run_start(stime, delay, queue), self, None, duration
stime,
self._controller.manual_run_start(stime, delay, queue),
self,
None,
duration,
)
def service_cancel(self, data: MappingProxyType, stime: datetime) -> bool:
@@ -4498,6 +4589,7 @@ class IUController(IUBase):
def muster_sequence(
self,
stime: datetime,
earliest: datetime,
sequence: IUSequence,
schedule: IUSchedule,
total_time: timedelta = None,
@@ -4554,7 +4646,7 @@ class IUController(IUBase):
total_time = sequence_run.build(duration_factor)
if total_time > timedelta(0):
start_time = init_run_time(
stime, sequence, schedule, sequence_run.first_zone(), total_time
earliest, sequence, schedule, sequence_run.first_zone(), total_time
)
if start_time is not None:
sequence_run.allocate_runs(stime, start_time)
@@ -4594,34 +4686,35 @@ class IUController(IUBase):
sequence.runs.clear_runs()
zone_status |= sms
if not self._coordinator.tester.enabled or self._coordinator.tester.is_testing:
# pylint: disable=too-many-nested-blocks
# Process sequence schedules
for sequence in self._sequences:
if sequence.is_enabled:
for schedule in sequence.schedules:
if not schedule.enabled:
continue
next_time = stime
while True:
if self.muster_sequence(
next_time, sequence, schedule, None
).is_empty():
break
zone_status |= IURQStatus.EXTENDED
# Process sequence schedules
for sequence in self._sequences:
if sequence.is_enabled:
for schedule in sequence.schedules:
if not schedule.enabled:
continue
next_time = stime
while True:
if self.muster_sequence(
stime, next_time, sequence, schedule, None
).is_empty():
break
zone_status |= IURQStatus.EXTENDED
# Process zone schedules
for zone in self._zones:
if zone.is_enabled:
zone_status |= zone.muster_schedules(stime)
# Process zone schedules
for zone in self._zones:
if zone.is_enabled:
zone_status |= zone.muster_schedules(stime)
# Post processing
for sequence in self._sequences:
zone_status |= sequence.runs.update_queue()
sst = sequence.runs.update_queue(stime)
if sst.has_any(IURQStatus.UPDATED):
sequence.request_update()
zone_status |= sst
for zone in self._zones:
zts = zone.runs.update_queue()
if IURQStatus.CANCELED in zts:
if zts.has_any(IURQStatus.CANCELED | IURQStatus.UPDATED):
zone.request_update()
zone_status |= zts

View File

@@ -15,5 +15,5 @@
"requirements": [
"crontab"
],
"version": "2024.5.0"
"version": "2024.8.0"
}

View File

@@ -1,11 +1,13 @@
"""This module handles the HA service call interface"""
from homeassistant.core import ServiceCall, callback
from homeassistant.core import ServiceCall, SupportsResponse, ServiceResponse, callback
from homeassistant.util import dt
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.const import (
SERVICE_RELOAD,
ATTR_ENTITY_ID,
)
from .irrigation_unlimited import IUCoordinator
@@ -35,6 +37,16 @@ from .const import (
SERVICE_SKIP,
SERVICE_PAUSE,
SERVICE_RESUME,
SERVICE_GET_INFO,
ATTR_VERSION,
ATTR_CONTROLLERS,
ATTR_CONTROLLER_ID,
ATTR_ZONES,
ATTR_ZONE_ID,
ATTR_SEQUENCES,
ATTR_INDEX,
ATTR_NAME,
ATTR_ZONE_IDS,
)
@@ -115,9 +127,54 @@ def register_component_services(
"""Reload schedule."""
coordinator.service_call(call.service, None, None, None, call.data)
@callback
async def get_info_service_handler(call: ServiceCall) -> ServiceResponse:
"""Return configuration"""
data = {}
data[ATTR_VERSION] = "1.0.0"
data[ATTR_CONTROLLERS] = [
{
ATTR_INDEX: ctl.index,
ATTR_CONTROLLER_ID: ctl.controller_id,
ATTR_NAME: ctl.name,
ATTR_ENTITY_ID: ctl.entity_id,
ATTR_ZONES: [
{
ATTR_INDEX: zone.index,
ATTR_ZONE_ID: zone.zone_id,
ATTR_NAME: zone.name,
ATTR_ENTITY_ID: zone.entity_id,
}
for zone in ctl.zones
],
ATTR_SEQUENCES: [
{
ATTR_INDEX: seq.index,
ATTR_NAME: seq.name,
ATTR_ENTITY_ID: seq.entity_id,
ATTR_ZONES: [
{ATTR_INDEX: sqz.index, ATTR_ZONE_IDS: sqz.zone_ids}
for sqz in seq.zones
],
}
for seq in ctl.sequences
],
}
for ctl in coordinator.controllers
]
return data
component.hass.services.async_register(
DOMAIN,
SERVICE_LOAD_SCHEDULE,
load_schedule_service_handler,
LOAD_SCHEDULE_SCHEMA,
)
component.hass.services.async_register(
DOMAIN,
SERVICE_GET_INFO,
get_info_service_handler,
{},
supports_response=SupportsResponse.ONLY,
)

View File

@@ -397,6 +397,10 @@ reload:
name: Reload
description: Reload the configuration
get_info:
name: Get Info
description: Get configuration information
load_schedule:
name: Load schedule
description: Load a schedule.