diff --git a/.coveragerc b/.coveragerc index 20c6fd2c60e1edda16f1681c56b4bc61e83582b6..4c11fe46120583c3e3e97bccd30af14813ac3a3a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -980,6 +980,7 @@ omit = homeassistant/components/rainmachine/model.py homeassistant/components/rainmachine/sensor.py homeassistant/components/rainmachine/switch.py + homeassistant/components/rainmachine/update.py homeassistant/components/rainmachine/util.py homeassistant/components/raspyrfm/* homeassistant/components/recollect_waste/__init__.py diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index f46b1aa0f4c02f2d2535fece7e1ce4add393da9c..1ad1fb734fa2b97bc0c469c8ae0e54bed35099d0 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -36,6 +36,8 @@ from homeassistant.util.network import is_ip_address from .config_flow import get_client_controller from .const import ( CONF_ZONE_RUN_TIME, + DATA_API_VERSIONS, + DATA_MACHINE_FIRMWARE_UPDATE_STATUS, DATA_PROGRAMS, DATA_PROVISION_SETTINGS, DATA_RESTRICTIONS_CURRENT, @@ -51,7 +53,13 @@ DEFAULT_SSL = True CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR, Platform.SWITCH] +PLATFORMS = [ + Platform.BINARY_SENSOR, + Platform.BUTTON, + Platform.SENSOR, + Platform.SWITCH, + Platform.UPDATE, +] CONF_CONDITION = "condition" CONF_DEWPOINT = "dewpoint" @@ -124,8 +132,10 @@ SERVICE_RESTRICT_WATERING_SCHEMA = SERVICE_SCHEMA.extend( ) COORDINATOR_UPDATE_INTERVAL_MAP = { - DATA_PROVISION_SETTINGS: timedelta(minutes=1), + DATA_API_VERSIONS: timedelta(minutes=1), + DATA_MACHINE_FIRMWARE_UPDATE_STATUS: timedelta(seconds=15), DATA_PROGRAMS: timedelta(seconds=30), + DATA_PROVISION_SETTINGS: timedelta(minutes=1), DATA_RESTRICTIONS_CURRENT: timedelta(minutes=1), DATA_RESTRICTIONS_UNIVERSAL: timedelta(minutes=1), DATA_ZONES: timedelta(seconds=15), @@ -215,7 +225,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: data: dict = {} try: - if api_category == DATA_PROGRAMS: + if api_category == DATA_API_VERSIONS: + data = await controller.api.versions() + elif api_category == DATA_MACHINE_FIRMWARE_UPDATE_STATUS: + data = await controller.machine.get_firmware_update_status() + elif api_category == DATA_PROGRAMS: data = await controller.programs.all(include_inactive=True) elif api_category == DATA_PROVISION_SETTINGS: data = await controller.provisioning.settings() @@ -414,23 +428,32 @@ class RainMachineEntity(CoordinatorEntity): """Initialize.""" super().__init__(data.coordinators[description.api_category]) - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, data.controller.mac)}, - configuration_url=f"https://{entry.data[CONF_IP_ADDRESS]}:{entry.data[CONF_PORT]}", - connections={(dr.CONNECTION_NETWORK_MAC, data.controller.mac)}, - name=str(data.controller.name).capitalize(), - manufacturer="RainMachine", - model=( - f"Version {data.controller.hardware_version} " - f"(API: {data.controller.api_version})" - ), - sw_version=data.controller.software_version, - ) self._attr_extra_state_attributes = {} self._attr_unique_id = f"{data.controller.mac}_{description.key}" + self._entry = entry self._data = data + self._version_coordinator = data.coordinators[DATA_API_VERSIONS] self.entity_description = description + @property + def device_info(self) -> DeviceInfo: + """Return device information about this controller.""" + return DeviceInfo( + identifiers={(DOMAIN, self._data.controller.mac)}, + configuration_url=( + f"https://{self._entry.data[CONF_IP_ADDRESS]}:" + f"{self._entry.data[CONF_PORT]}" + ), + connections={(dr.CONNECTION_NETWORK_MAC, self._data.controller.mac)}, + name=str(self._data.controller.name).capitalize(), + manufacturer="RainMachine", + model=( + f"Version {self._version_coordinator.data['hwVer']} " + f"(API: {self._version_coordinator.data['apiVer']})" + ), + sw_version=self._version_coordinator.data["swVer"], + ) + @callback def _handle_coordinator_update(self) -> None: """Respond to a DataUpdateCoordinator update.""" @@ -440,6 +463,11 @@ class RainMachineEntity(CoordinatorEntity): async def async_added_to_hass(self) -> None: """When entity is added to hass.""" await super().async_added_to_hass() + self.async_on_remove( + self._version_coordinator.async_add_listener( + self._handle_coordinator_update, self.coordinator_context + ) + ) self.update_from_latest_data() @callback diff --git a/homeassistant/components/rainmachine/const.py b/homeassistant/components/rainmachine/const.py index f94e7011dce2080cbc24736f43197f1c247dba77..d1b5bd9bd528a31053a8e19211e37b413c0c63b7 100644 --- a/homeassistant/components/rainmachine/const.py +++ b/homeassistant/components/rainmachine/const.py @@ -7,6 +7,8 @@ DOMAIN = "rainmachine" CONF_ZONE_RUN_TIME = "zone_run_time" +DATA_API_VERSIONS = "api.versions" +DATA_MACHINE_FIRMWARE_UPDATE_STATUS = "machine.firmware_update_status" DATA_PROGRAMS = "programs" DATA_PROVISION_SETTINGS = "provision.settings" DATA_RESTRICTIONS_CURRENT = "restrictions.current" diff --git a/homeassistant/components/rainmachine/diagnostics.py b/homeassistant/components/rainmachine/diagnostics.py index 131f2e130e7142f8bfb58d6733afde92bda2bb37..47ded7990c65ccc10fbe9037849dc32126fed3c6 100644 --- a/homeassistant/components/rainmachine/diagnostics.py +++ b/homeassistant/components/rainmachine/diagnostics.py @@ -1,10 +1,9 @@ """Diagnostics support for RainMachine.""" from __future__ import annotations -import asyncio from typing import Any -from regenmaschine.errors import RequestError +from regenmaschine.errors import RainMachineError from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry @@ -17,7 +16,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from . import RainMachineData -from .const import DOMAIN +from .const import DOMAIN, LOGGER CONF_STATION_ID = "stationID" CONF_STATION_NAME = "stationName" @@ -42,13 +41,11 @@ async def async_get_config_entry_diagnostics( """Return diagnostics for a config entry.""" data: RainMachineData = hass.data[DOMAIN][entry.entry_id] - controller_tasks = { - "versions": data.controller.api.versions(), - "current_diagnostics": data.controller.diagnostics.current(), - } - controller_results = await asyncio.gather( - *controller_tasks.values(), return_exceptions=True - ) + try: + controller_diagnostics = await data.controller.diagnostics.current() + except RainMachineError: + LOGGER.warning("Unable to download controller-specific diagnostics") + controller_diagnostics = None return { "entry": { @@ -64,10 +61,6 @@ async def async_get_config_entry_diagnostics( }, TO_REDACT, ), - "controller": { - category: result - for category, result in zip(controller_tasks, controller_results) - if not isinstance(result, RequestError) - }, + "controller_diagnostics": controller_diagnostics, }, } diff --git a/homeassistant/components/rainmachine/update.py b/homeassistant/components/rainmachine/update.py new file mode 100644 index 0000000000000000000000000000000000000000..b191f2695a0a41476d4a68f762d0eb02e3558f80 --- /dev/null +++ b/homeassistant/components/rainmachine/update.py @@ -0,0 +1,102 @@ +"""Support for RainMachine updates.""" +from __future__ import annotations + +from enum import Enum +from typing import Any + +from regenmaschine.errors import RequestError + +from homeassistant.components.update import ( + UpdateDeviceClass, + UpdateEntity, + UpdateEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import RainMachineData, RainMachineEntity +from .const import DATA_MACHINE_FIRMWARE_UPDATE_STATUS, DOMAIN +from .model import RainMachineEntityDescription + + +class UpdateStates(Enum): + """Define an enum for update states.""" + + IDLE = 1 + CHECKING = 2 + DOWNLOADING = 3 + UPGRADING = 4 + ERROR = 5 + REBOOT = 6 + + +UPDATE_STATE_MAP = { + 1: UpdateStates.IDLE, + 2: UpdateStates.CHECKING, + 3: UpdateStates.DOWNLOADING, + 4: UpdateStates.UPGRADING, + 5: UpdateStates.ERROR, + 6: UpdateStates.REBOOT, +} + + +UPDATE_DESCRIPTION = RainMachineEntityDescription( + key="update", + name="Firmware", + api_category=DATA_MACHINE_FIRMWARE_UPDATE_STATUS, +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up WLED update based on a config entry.""" + data: RainMachineData = hass.data[DOMAIN][entry.entry_id] + + async_add_entities([RainMachineUpdateEntity(entry, data, UPDATE_DESCRIPTION)]) + + +class RainMachineUpdateEntity(RainMachineEntity, UpdateEntity): + """Define a RainMachine update entity.""" + + _attr_device_class = UpdateDeviceClass.FIRMWARE + _attr_supported_features = ( + UpdateEntityFeature.INSTALL + | UpdateEntityFeature.PROGRESS + | UpdateEntityFeature.SPECIFIC_VERSION + ) + + async def async_install( + self, version: str | None, backup: bool, **kwargs: Any + ) -> None: + """Install an update.""" + try: + await self._data.controller.machine.update_firmware() + except RequestError as err: + raise HomeAssistantError(f"Error while updating firmware: {err}") from err + + await self.coordinator.async_refresh() + + @callback + def update_from_latest_data(self) -> None: + """Update the state.""" + if version := self._version_coordinator.data["swVer"]: + self._attr_installed_version = version + else: + self._attr_installed_version = None + + data = self.coordinator.data + + if not data["update"]: + self._attr_in_progress = False + self._attr_latest_version = self._attr_installed_version + return + + self._attr_in_progress = UPDATE_STATE_MAP[data["updateStatus"]] in ( + UpdateStates.DOWNLOADING, + UpdateStates.UPGRADING, + UpdateStates.REBOOT, + ) + self._attr_latest_version = data["packageDetails"]["newVersion"] diff --git a/tests/components/rainmachine/conftest.py b/tests/components/rainmachine/conftest.py index 72b4a5d42938d5a32d4c9f5d5f48e2b4fcbc30eb..1dfef7e399c8b888e14c061d3d7a0e8c867673ca 100644 --- a/tests/components/rainmachine/conftest.py +++ b/tests/components/rainmachine/conftest.py @@ -41,6 +41,7 @@ def controller_fixture( controller_mac, data_api_versions, data_diagnostics_current, + data_machine_firmare_update_status, data_programs, data_provision_settings, data_restrictions_current, @@ -59,6 +60,9 @@ def controller_fixture( controller.api.versions.return_value = data_api_versions controller.diagnostics.current.return_value = data_diagnostics_current + controller.machine.get_firmware_update_status.return_value = ( + data_machine_firmare_update_status + ) controller.programs.all.return_value = data_programs controller.provisioning.settings.return_value = data_provision_settings controller.restrictions.current.return_value = data_restrictions_current @@ -86,6 +90,14 @@ def data_diagnostics_current_fixture(): return json.loads(load_fixture("diagnostics_current_data.json", "rainmachine")) +@pytest.fixture(name="data_machine_firmare_update_status", scope="session") +def data_machine_firmare_update_status_fixture(): + """Define machine firmware update status data.""" + return json.loads( + load_fixture("machine_firmware_update_status_data.json", "rainmachine") + ) + + @pytest.fixture(name="data_programs", scope="session") def data_programs_fixture(): """Define program data.""" diff --git a/tests/components/rainmachine/fixtures/machine_firmware_update_status_data.json b/tests/components/rainmachine/fixtures/machine_firmware_update_status_data.json new file mode 100644 index 0000000000000000000000000000000000000000..01dd95e61364289c07de3ad8f2f73d960c3641c0 --- /dev/null +++ b/tests/components/rainmachine/fixtures/machine_firmware_update_status_data.json @@ -0,0 +1,7 @@ +{ + "lastUpdateCheckTimestamp": 1657825288, + "packageDetails": [], + "update": false, + "lastUpdateCheck": "2022-07-14 13:01:28", + "updateStatus": 1 +} diff --git a/tests/components/rainmachine/test_diagnostics.py b/tests/components/rainmachine/test_diagnostics.py index b0876a2f5970fc98425a3254504a0ef58f250c28..a600c5f7c34d24291c28e75a0fe65835aa72908c 100644 --- a/tests/components/rainmachine/test_diagnostics.py +++ b/tests/components/rainmachine/test_diagnostics.py @@ -1,4 +1,6 @@ """Test RainMachine diagnostics.""" +from regenmaschine.errors import RainMachineError + from homeassistant.components.diagnostics import REDACTED from tests.components.diagnostics import get_diagnostics_for_config_entry @@ -19,89 +21,13 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_rainmach }, "data": { "coordinator": { - "provision.settings": { - "system": { - "httpEnabled": True, - "rainSensorSnoozeDuration": 0, - "uiUnitsMetric": False, - "programZonesShowInactive": False, - "programSingleSchedule": False, - "standaloneMode": False, - "masterValveAfter": 0, - "touchSleepTimeout": 10, - "selfTest": False, - "useSoftwareRainSensor": False, - "defaultZoneWateringDuration": 300, - "maxLEDBrightness": 40, - "simulatorHistorySize": 0, - "vibration": False, - "masterValveBefore": 0, - "touchProgramToRun": None, - "useRainSensor": False, - "wizardHasRun": True, - "waterLogHistorySize": 365, - "netName": "Home", - "softwareRainSensorMinQPF": 5, - "touchAdvanced": False, - "useBonjourService": True, - "hardwareVersion": 3, - "touchLongPressTimeout": 3, - "showRestrictionsOnLed": False, - "parserDataSizeInDays": 6, - "programListShowInactive": True, - "parserHistorySize": 365, - "allowAlexaDiscovery": False, - "automaticUpdates": True, - "minLEDBrightness": 0, - "minWateringDurationThreshold": 0, - "localValveCount": 12, - "touchAuthAPSeconds": 60, - "useCommandLineArguments": False, - "databasePath": "/rainmachine-app/DB/Default", - "touchCyclePrograms": True, - "zoneListShowInactive": True, - "rainSensorRainStart": None, - "zoneDuration": [ - 300, - 300, - 300, - 300, - 300, - 300, - 300, - 300, - 300, - 300, - 300, - 300, - ], - "rainSensorIsNormallyClosed": True, - "useCorrectionForPast": True, - "useMasterValve": False, - "runParsersBeforePrograms": True, - "maxWateringCoef": 2, - "mixerHistorySize": 365, - }, - "location": { - "elevation": REDACTED, - "doyDownloaded": True, - "zip": None, - "windSensitivity": 0.5, - "krs": 0.16, - "stationID": REDACTED, - "stationSource": REDACTED, - "et0Average": 6.578, - "latitude": REDACTED, - "state": "Default", - "stationName": REDACTED, - "wsDays": 2, - "stationDownloaded": True, - "address": "Default", - "rainSensitivity": 0.8, - "timezone": REDACTED, - "longitude": REDACTED, - "name": "Home", - }, + "api.versions": {"apiVer": "4.6.1", "hwVer": 3, "swVer": "4.0.1144"}, + "machine.firmware_update_status": { + "lastUpdateCheckTimestamp": 1657825288, + "packageDetails": [], + "update": False, + "lastUpdateCheck": "2022-07-14 13:01:28", + "updateStatus": 1, }, "programs": [ { @@ -381,6 +307,90 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_rainmach ], }, ], + "provision.settings": { + "system": { + "httpEnabled": True, + "rainSensorSnoozeDuration": 0, + "uiUnitsMetric": False, + "programZonesShowInactive": False, + "programSingleSchedule": False, + "standaloneMode": False, + "masterValveAfter": 0, + "touchSleepTimeout": 10, + "selfTest": False, + "useSoftwareRainSensor": False, + "defaultZoneWateringDuration": 300, + "maxLEDBrightness": 40, + "simulatorHistorySize": 0, + "vibration": False, + "masterValveBefore": 0, + "touchProgramToRun": None, + "useRainSensor": False, + "wizardHasRun": True, + "waterLogHistorySize": 365, + "netName": "Home", + "softwareRainSensorMinQPF": 5, + "touchAdvanced": False, + "useBonjourService": True, + "hardwareVersion": 3, + "touchLongPressTimeout": 3, + "showRestrictionsOnLed": False, + "parserDataSizeInDays": 6, + "programListShowInactive": True, + "parserHistorySize": 365, + "allowAlexaDiscovery": False, + "automaticUpdates": True, + "minLEDBrightness": 0, + "minWateringDurationThreshold": 0, + "localValveCount": 12, + "touchAuthAPSeconds": 60, + "useCommandLineArguments": False, + "databasePath": "/rainmachine-app/DB/Default", + "touchCyclePrograms": True, + "zoneListShowInactive": True, + "rainSensorRainStart": None, + "zoneDuration": [ + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + ], + "rainSensorIsNormallyClosed": True, + "useCorrectionForPast": True, + "useMasterValve": False, + "runParsersBeforePrograms": True, + "maxWateringCoef": 2, + "mixerHistorySize": 365, + }, + "location": { + "elevation": REDACTED, + "doyDownloaded": True, + "zip": None, + "windSensitivity": 0.5, + "krs": 0.16, + "stationID": REDACTED, + "stationSource": REDACTED, + "et0Average": 6.578, + "latitude": REDACTED, + "state": "Default", + "stationName": REDACTED, + "wsDays": 2, + "stationDownloaded": True, + "address": "Default", + "rainSensitivity": 0.8, + "timezone": REDACTED, + "longitude": REDACTED, + "name": "Home", + }, + }, "restrictions.current": { "hourly": False, "freeze": False, @@ -582,33 +592,620 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_rainmach }, ], }, - "controller": { - "versions": { - "apiVer": "4.6.1", - "hwVer": 3, - "swVer": "4.0.1144", - }, - "current_diagnostics": { - "hasWifi": True, - "uptime": "3 days, 18:14:14", - "uptimeSeconds": 324854, - "memUsage": 16196, - "networkStatus": True, - "bootCompleted": True, - "lastCheckTimestamp": 1659895175, - "wizardHasRun": True, - "standaloneMode": False, - "cpuUsage": 1, - "lastCheck": "2022-08-07 11:59:35", - "softwareVersion": "4.0.1144", - "internetStatus": True, - "locationStatus": True, - "timeStatus": True, - "wifiMode": None, - "gatewayAddress": "172.16.20.1", - "cloudStatus": 0, - "weatherStatus": True, + "controller_diagnostics": { + "hasWifi": True, + "uptime": "3 days, 18:14:14", + "uptimeSeconds": 324854, + "memUsage": 16196, + "networkStatus": True, + "bootCompleted": True, + "lastCheckTimestamp": 1659895175, + "wizardHasRun": True, + "standaloneMode": False, + "cpuUsage": 1, + "lastCheck": "2022-08-07 11:59:35", + "softwareVersion": "4.0.1144", + "internetStatus": True, + "locationStatus": True, + "timeStatus": True, + "wifiMode": None, + "gatewayAddress": "172.16.20.1", + "cloudStatus": 0, + "weatherStatus": True, + }, + }, + } + + +async def test_entry_diagnostics_failed_controller_diagnostics( + hass, config_entry, controller, hass_client, setup_rainmachine +): + """Test config entry diagnostics when the controller diagnostics API call fails.""" + controller.diagnostics.current.side_effect = RainMachineError + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "entry": { + "title": "Mock Title", + "data": { + "ip_address": "192.168.1.100", + "password": REDACTED, + "port": 8080, + "ssl": True, + }, + "options": {}, + }, + "data": { + "coordinator": { + "api.versions": {"apiVer": "4.6.1", "hwVer": 3, "swVer": "4.0.1144"}, + "machine.firmware_update_status": { + "lastUpdateCheckTimestamp": 1657825288, + "packageDetails": [], + "update": False, + "lastUpdateCheck": "2022-07-14 13:01:28", + "updateStatus": 1, }, + "programs": [ + { + "uid": 1, + "name": "Morning", + "active": True, + "startTime": "06:00", + "cycles": 0, + "soak": 0, + "cs_on": False, + "delay": 0, + "delay_on": False, + "status": 0, + "startTimeParams": { + "offsetSign": 0, + "type": 0, + "offsetMinutes": 0, + }, + "frequency": {"type": 0, "param": "0"}, + "coef": 0, + "ignoreInternetWeather": False, + "futureField1": 0, + "freq_modified": 0, + "useWaterSense": False, + "nextRun": "2018-06-04", + "startDate": "2018-04-28", + "endDate": None, + "yearlyRecurring": True, + "simulationExpired": False, + "wateringTimes": [ + { + "id": 1, + "order": -1, + "name": "Landscaping", + "duration": 0, + "active": True, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 2, + "order": -1, + "name": "Flower Box", + "duration": 0, + "active": True, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 3, + "order": -1, + "name": "TEST", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 4, + "order": -1, + "name": "Zone 4", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 5, + "order": -1, + "name": "Zone 5", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 6, + "order": -1, + "name": "Zone 6", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 7, + "order": -1, + "name": "Zone 7", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 8, + "order": -1, + "name": "Zone 8", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 9, + "order": -1, + "name": "Zone 9", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 10, + "order": -1, + "name": "Zone 10", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 11, + "order": -1, + "name": "Zone 11", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 12, + "order": -1, + "name": "Zone 12", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + ], + }, + { + "uid": 2, + "name": "Evening", + "active": False, + "startTime": "06:00", + "cycles": 0, + "soak": 0, + "cs_on": False, + "delay": 0, + "delay_on": False, + "status": 0, + "startTimeParams": { + "offsetSign": 0, + "type": 0, + "offsetMinutes": 0, + }, + "frequency": {"type": 0, "param": "0"}, + "coef": 0, + "ignoreInternetWeather": False, + "futureField1": 0, + "freq_modified": 0, + "useWaterSense": False, + "nextRun": "2018-06-04", + "startDate": "2018-04-28", + "endDate": None, + "yearlyRecurring": True, + "simulationExpired": False, + "wateringTimes": [ + { + "id": 1, + "order": -1, + "name": "Landscaping", + "duration": 0, + "active": True, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 2, + "order": -1, + "name": "Flower Box", + "duration": 0, + "active": True, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 3, + "order": -1, + "name": "TEST", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 4, + "order": -1, + "name": "Zone 4", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 5, + "order": -1, + "name": "Zone 5", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 6, + "order": -1, + "name": "Zone 6", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 7, + "order": -1, + "name": "Zone 7", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 8, + "order": -1, + "name": "Zone 8", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 9, + "order": -1, + "name": "Zone 9", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 10, + "order": -1, + "name": "Zone 10", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 11, + "order": -1, + "name": "Zone 11", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + { + "id": 12, + "order": -1, + "name": "Zone 12", + "duration": 0, + "active": False, + "userPercentage": 1, + "minRuntimeCoef": 1, + }, + ], + }, + ], + "provision.settings": { + "system": { + "httpEnabled": True, + "rainSensorSnoozeDuration": 0, + "uiUnitsMetric": False, + "programZonesShowInactive": False, + "programSingleSchedule": False, + "standaloneMode": False, + "masterValveAfter": 0, + "touchSleepTimeout": 10, + "selfTest": False, + "useSoftwareRainSensor": False, + "defaultZoneWateringDuration": 300, + "maxLEDBrightness": 40, + "simulatorHistorySize": 0, + "vibration": False, + "masterValveBefore": 0, + "touchProgramToRun": None, + "useRainSensor": False, + "wizardHasRun": True, + "waterLogHistorySize": 365, + "netName": "Home", + "softwareRainSensorMinQPF": 5, + "touchAdvanced": False, + "useBonjourService": True, + "hardwareVersion": 3, + "touchLongPressTimeout": 3, + "showRestrictionsOnLed": False, + "parserDataSizeInDays": 6, + "programListShowInactive": True, + "parserHistorySize": 365, + "allowAlexaDiscovery": False, + "automaticUpdates": True, + "minLEDBrightness": 0, + "minWateringDurationThreshold": 0, + "localValveCount": 12, + "touchAuthAPSeconds": 60, + "useCommandLineArguments": False, + "databasePath": "/rainmachine-app/DB/Default", + "touchCyclePrograms": True, + "zoneListShowInactive": True, + "rainSensorRainStart": None, + "zoneDuration": [ + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + 300, + ], + "rainSensorIsNormallyClosed": True, + "useCorrectionForPast": True, + "useMasterValve": False, + "runParsersBeforePrograms": True, + "maxWateringCoef": 2, + "mixerHistorySize": 365, + }, + "location": { + "elevation": REDACTED, + "doyDownloaded": True, + "zip": None, + "windSensitivity": 0.5, + "krs": 0.16, + "stationID": REDACTED, + "stationSource": REDACTED, + "et0Average": 6.578, + "latitude": REDACTED, + "state": "Default", + "stationName": REDACTED, + "wsDays": 2, + "stationDownloaded": True, + "address": "Default", + "rainSensitivity": 0.8, + "timezone": REDACTED, + "longitude": REDACTED, + "name": "Home", + }, + }, + "restrictions.current": { + "hourly": False, + "freeze": False, + "month": False, + "weekDay": False, + "rainDelay": False, + "rainDelayCounter": -1, + "rainSensor": False, + }, + "restrictions.universal": { + "hotDaysExtraWatering": False, + "freezeProtectEnabled": True, + "freezeProtectTemp": 2, + "noWaterInWeekDays": "0000000", + "noWaterInMonths": "000000000000", + "rainDelayStartTime": 1524854551, + "rainDelayDuration": 0, + }, + "zones": [ + { + "uid": 1, + "name": "Landscaping", + "state": 0, + "active": True, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 4, + "master": False, + "waterSense": False, + }, + { + "uid": 2, + "name": "Flower Box", + "state": 0, + "active": True, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 5, + "master": False, + "waterSense": False, + }, + { + "uid": 3, + "name": "TEST", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 9, + "master": False, + "waterSense": False, + }, + { + "uid": 4, + "name": "Zone 4", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + { + "uid": 5, + "name": "Zone 5", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + { + "uid": 6, + "name": "Zone 6", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + { + "uid": 7, + "name": "Zone 7", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + { + "uid": 8, + "name": "Zone 8", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + { + "uid": 9, + "name": "Zone 9", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + { + "uid": 10, + "name": "Zone 10", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + { + "uid": 11, + "name": "Zone 11", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + { + "uid": 12, + "name": "Zone 12", + "state": 0, + "active": False, + "userDuration": 0, + "machineDuration": 0, + "remaining": 0, + "cycle": 0, + "noOfCycles": 0, + "restriction": False, + "type": 2, + "master": False, + "waterSense": False, + }, + ], }, + "controller_diagnostics": None, }, }