From 485752a033abff30b92d4a417fb5cdc7dc7f3fde Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 5 Aug 2020 20:32:53 +0200 Subject: [PATCH 01/77] Bumped version to 0.114.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index cedfced2f7c..f6b54465239 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0b0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From e43fb649a4534dda917cda2d73e96bb980802762 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 6 Aug 2020 12:54:18 +0200 Subject: [PATCH 02/77] Improve Xioami Aqara zeroconf discovery handling (#37469) Co-authored-by: Franck Nijhof Co-authored-by: Martin Hjelmare --- .../components/xiaomi_aqara/__init__.py | 2 +- .../components/xiaomi_aqara/config_flow.py | 108 +++++++++---- .../components/xiaomi_aqara/strings.json | 11 +- .../xiaomi_aqara/translations/en.json | 11 +- .../xiaomi_aqara/test_config_flow.py | 150 ++++++++++++++---- 5 files changed, 209 insertions(+), 73 deletions(-) diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index d759785f49f..c5b74e68af5 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -142,11 +142,11 @@ async def async_setup_entry( xiaomi_gateway = await hass.async_add_executor_job( XiaomiGateway, entry.data[CONF_HOST], - entry.data[CONF_PORT], entry.data[CONF_SID], entry.data[CONF_KEY], DEFAULT_DISCOVERY_RETRY, entry.data[CONF_INTERFACE], + entry.data[CONF_PORT], entry.data[CONF_PROTOCOL], ) hass.data[DOMAIN][GATEWAYS_KEY][entry.entry_id] = xiaomi_gateway diff --git a/homeassistant/components/xiaomi_aqara/config_flow.py b/homeassistant/components/xiaomi_aqara/config_flow.py index b9cfe58ac4b..fb66be76635 100644 --- a/homeassistant/components/xiaomi_aqara/config_flow.py +++ b/homeassistant/components/xiaomi_aqara/config_flow.py @@ -3,10 +3,11 @@ import logging from socket import gaierror import voluptuous as vol -from xiaomi_gateway import XiaomiGatewayDiscovery +from xiaomi_gateway import MULTICAST_PORT, XiaomiGateway, XiaomiGatewayDiscovery from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT +from homeassistant.core import callback from homeassistant.helpers.device_registry import format_mac # pylint: disable=unused-import @@ -15,6 +16,7 @@ from .const import ( CONF_KEY, CONF_PROTOCOL, CONF_SID, + DEFAULT_DISCOVERY_RETRY, DOMAIN, ZEROCONF_GATEWAY, ) @@ -28,6 +30,11 @@ DEFAULT_INTERFACE = "any" GATEWAY_CONFIG = vol.Schema( {vol.Optional(CONF_INTERFACE, default=DEFAULT_INTERFACE): str} ) +CONFIG_HOST = { + vol.Optional(CONF_HOST): str, + vol.Optional(CONF_MAC): str, +} +GATEWAY_CONFIG_HOST = GATEWAY_CONFIG.extend(CONFIG_HOST) GATEWAY_SETTINGS = vol.Schema( { vol.Optional(CONF_KEY): vol.All(str, vol.Length(min=16, max=16)), @@ -46,44 +53,78 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize.""" self.host = None self.interface = DEFAULT_INTERFACE + self.sid = None self.gateways = None self.selected_gateway = None + @callback + def async_show_form_step_user(self, errors): + """Show the form belonging to the user step.""" + schema = GATEWAY_CONFIG + if (self.host is None and self.sid is None) or errors: + schema = GATEWAY_CONFIG_HOST + + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" errors = {} - if user_input is not None: - self.interface = user_input[CONF_INTERFACE] + if user_input is None: + return self.async_show_form_step_user(errors) - # Discover Xiaomi Aqara Gateways in the netwerk to get required SIDs. - xiaomi = XiaomiGatewayDiscovery(self.hass.add_job, [], self.interface) - try: - await self.hass.async_add_executor_job(xiaomi.discover_gateways) - except gaierror: - errors[CONF_INTERFACE] = "invalid_interface" + self.interface = user_input[CONF_INTERFACE] - if not errors: - self.gateways = xiaomi.gateways + # allow optional manual setting of host and mac + if self.host is None and self.sid is None: + self.host = user_input.get(CONF_HOST) + mac_address = user_input.get(CONF_MAC) - # if host is already known by zeroconf discovery - if self.host is not None: - self.selected_gateway = self.gateways.get(self.host) - if self.selected_gateway is not None: - return await self.async_step_settings() + # format sid from mac_address + if mac_address is not None: + self.sid = format_mac(mac_address).replace(":", "") - errors["base"] = "not_found_error" - else: - if len(self.gateways) == 1: - self.selected_gateway = list(self.gateways.values())[0] - return await self.async_step_settings() - if len(self.gateways) > 1: - return await self.async_step_select() + # if host is already known by zeroconf discovery or manual optional settings + if self.host is not None and self.sid is not None: + # Connect to Xiaomi Aqara Gateway + self.selected_gateway = await self.hass.async_add_executor_job( + XiaomiGateway, + self.host, + self.sid, + None, + DEFAULT_DISCOVERY_RETRY, + self.interface, + MULTICAST_PORT, + None, + ) - errors["base"] = "discovery_error" + if self.selected_gateway.connection_error: + errors[CONF_HOST] = "invalid_host" + if self.selected_gateway.mac_error: + errors[CONF_MAC] = "invalid_mac" + if errors: + return self.async_show_form_step_user(errors) - return self.async_show_form( - step_id="user", data_schema=GATEWAY_CONFIG, errors=errors - ) + return await self.async_step_settings() + + # Discover Xiaomi Aqara Gateways in the netwerk to get required SIDs. + xiaomi = XiaomiGatewayDiscovery(self.hass.add_job, [], self.interface) + try: + await self.hass.async_add_executor_job(xiaomi.discover_gateways) + except gaierror: + errors[CONF_INTERFACE] = "invalid_interface" + return self.async_show_form_step_user(errors) + + self.gateways = xiaomi.gateways + + if len(self.gateways) == 1: + self.selected_gateway = list(self.gateways.values())[0] + self.sid = self.selected_gateway.sid + return await self.async_step_settings() + if len(self.gateways) > 1: + return await self.async_step_select() + + errors["base"] = "discovery_error" + return self.async_show_form_step_user(errors) async def async_step_select(self, user_input=None): """Handle multiple aqara gateways found.""" @@ -91,6 +132,7 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: ip_adress = user_input["select_ip"] self.selected_gateway = self.gateways[ip_adress] + self.sid = self.selected_gateway.sid return await self.async_step_settings() select_schema = vol.Schema( @@ -123,9 +165,12 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_abort(reason="not_xiaomi_aqara") - # format mac (include semicolns and make uppercase) + # format mac (include semicolns and make lowercase) mac_address = format_mac(mac_address) + # format sid from mac_address + self.sid = mac_address.replace(":", "") + unique_id = mac_address await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) @@ -144,19 +189,18 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): key = user_input.get(CONF_KEY) ip_adress = self.selected_gateway.ip_adress port = self.selected_gateway.port - sid = self.selected_gateway.sid protocol = self.selected_gateway.proto if key is not None: # validate key by issuing stop ringtone playback command. self.selected_gateway.key = key - valid_key = self.selected_gateway.write_to_hub(sid, mid=10000) + valid_key = self.selected_gateway.write_to_hub(self.sid, mid=10000) else: valid_key = True if valid_key: # format_mac, for a gateway the sid equels the mac address - mac_address = format_mac(sid) + mac_address = format_mac(self.sid) # set unique_id unique_id = mac_address @@ -172,7 +216,7 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_INTERFACE: self.interface, CONF_PROTOCOL: protocol, CONF_KEY: key, - CONF_SID: sid, + CONF_SID: self.sid, }, ) diff --git a/homeassistant/components/xiaomi_aqara/strings.json b/homeassistant/components/xiaomi_aqara/strings.json index 87e1d37cb93..5cbdc91a661 100644 --- a/homeassistant/components/xiaomi_aqara/strings.json +++ b/homeassistant/components/xiaomi_aqara/strings.json @@ -4,9 +4,11 @@ "step": { "user": { "title": "Xiaomi Aqara Gateway", - "description": "Connect to your Xiaomi Aqara Gateway", + "description": "Connect to your Xiaomi Aqara Gateway, if the IP and mac addresses are left empty, auto-discovery is used", "data": { - "interface": "The network interface to use" + "interface": "The network interface to use", + "host": "[%key:common::config_flow::data::ip%] (optional)", + "mac": "Mac Address (optional)" } }, "settings": { @@ -27,9 +29,10 @@ }, "error": { "discovery_error": "Failed to discover a Xiaomi Aqara Gateway, try using the IP of the device running HomeAssistant as interface", - "not_found_error": "Zeroconf discovered Gateway could not be located to get the necessary information, try using the IP of the device running HomeAssistant as interface", "invalid_interface": "Invalid network interface", - "invalid_key": "Invalid gateway key" + "invalid_key": "Invalid gateway key", + "invalid_host": "Invalid [%key:common::config_flow::data::ip%]", + "invalid_mac": "Invalid Mac Address" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", diff --git a/homeassistant/components/xiaomi_aqara/translations/en.json b/homeassistant/components/xiaomi_aqara/translations/en.json index 7b801e33089..b9f6fa7ab2a 100644 --- a/homeassistant/components/xiaomi_aqara/translations/en.json +++ b/homeassistant/components/xiaomi_aqara/translations/en.json @@ -1,15 +1,16 @@ { "config": { "abort": { - "already_configured": "Device is already configured", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_in_progress": "Config flow for this gateway is already in progress", "not_xiaomi_aqara": "Not a Xiaomi Aqara Gateway, discovered device did not match known gateways" }, "error": { "discovery_error": "Failed to discover a Xiaomi Aqara Gateway, try using the IP of the device running HomeAssistant as interface", + "invalid_host": "Invalid [%key:common::config_flow::data::ip%]", "invalid_interface": "Invalid network interface", "invalid_key": "Invalid gateway key", - "not_found_error": "Zeroconf discovered Gateway could not be located to get the necessary information, try using the IP of the device running HomeAssistant as interface" + "invalid_mac": "Invalid Mac Address" }, "flow_title": "Xiaomi Aqara Gateway: {name}", "step": { @@ -30,9 +31,11 @@ }, "user": { "data": { - "interface": "The network interface to use" + "host": "[%key:common::config_flow::data::ip%] (optional)", + "interface": "The network interface to use", + "mac": "Mac Address (optional)" }, - "description": "Connect to your Xiaomi Aqara Gateway", + "description": "Connect to your Xiaomi Aqara Gateway, if the IP and mac addresses are left empty, auto-discovery is used", "title": "Xiaomi Aqara Gateway" } } diff --git a/tests/components/xiaomi_aqara/test_config_flow.py b/tests/components/xiaomi_aqara/test_config_flow.py index b7762317fdf..06fda84c934 100644 --- a/tests/components/xiaomi_aqara/test_config_flow.py +++ b/tests/components/xiaomi_aqara/test_config_flow.py @@ -34,13 +34,22 @@ def xiaomi_aqara_fixture(): with patch( "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGatewayDiscovery", return_value=mock_gateway_discovery, + ), patch( + "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGateway", + return_value=mock_gateway_discovery.gateways[TEST_HOST], ), patch( "homeassistant.components.xiaomi_aqara.async_setup_entry", return_value=True ): yield -def get_mock_discovery(host_list, invalid_interface=False, invalid_key=False): +def get_mock_discovery( + host_list, + invalid_interface=False, + invalid_key=False, + invalid_host=False, + invalid_mac=False, +): """Return a mock gateway info instance.""" gateway_discovery = Mock() @@ -52,6 +61,8 @@ def get_mock_discovery(host_list, invalid_interface=False, invalid_key=False): gateway.port = TEST_PORT gateway.sid = TEST_SID gateway.proto = TEST_PROTOCOL + gateway.connection_error = invalid_host + gateway.mac_error = invalid_mac if invalid_key: gateway.write_to_hub = Mock(return_value=False) @@ -185,6 +196,52 @@ async def test_config_flow_user_no_key_success(hass): } +async def test_config_flow_user_host_mac_success(hass): + """Test a successful config flow initialized by the user with a host and mac specified.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + mock_gateway_discovery = get_mock_discovery([]) + + with patch( + "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGatewayDiscovery", + return_value=mock_gateway_discovery, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, + CONF_HOST: TEST_HOST, + CONF_MAC: TEST_MAC, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "settings" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_NAME: TEST_NAME}, + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + CONF_HOST: TEST_HOST, + CONF_PORT: TEST_PORT, + CONF_MAC: TEST_MAC, + const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, + const.CONF_PROTOCOL: TEST_PROTOCOL, + const.CONF_KEY: None, + const.CONF_SID: TEST_SID, + } + + async def test_config_flow_user_discovery_error(hass): """Test a failed config flow initialized by the user with no gateways discoverd.""" result = await hass.config_entries.flow.async_init( @@ -235,6 +292,66 @@ async def test_config_flow_user_invalid_interface(hass): assert result["errors"] == {const.CONF_INTERFACE: "invalid_interface"} +async def test_config_flow_user_invalid_host(hass): + """Test a failed config flow initialized by the user with an invalid host.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + mock_gateway_discovery = get_mock_discovery([TEST_HOST], invalid_host=True) + + with patch( + "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGateway", + return_value=mock_gateway_discovery.gateways[TEST_HOST], + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, + CONF_HOST: "0.0.0.0", + CONF_MAC: TEST_MAC, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {"host": "invalid_host"} + + +async def test_config_flow_user_invalid_mac(hass): + """Test a failed config flow initialized by the user with an invalid mac.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + mock_gateway_discovery = get_mock_discovery([TEST_HOST], invalid_mac=True) + + with patch( + "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGateway", + return_value=mock_gateway_discovery.gateways[TEST_HOST], + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, + CONF_HOST: TEST_HOST, + CONF_MAC: "in:va:li:d0:0m:ac", + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {"mac": "invalid_mac"} + + async def test_config_flow_user_invalid_key(hass): """Test a failed config flow initialized by the user with an invalid key.""" result = await hass.config_entries.flow.async_init( @@ -335,34 +452,3 @@ async def test_zeroconf_unknown_device(hass): assert result["type"] == "abort" assert result["reason"] == "not_xiaomi_aqara" - - -async def test_zeroconf_not_found_error(hass): - """Test a failed zeroconf discovery because the correct gateway could not be found.""" - result = await hass.config_entries.flow.async_init( - const.DOMAIN, - context={"source": config_entries.SOURCE_ZEROCONF}, - data={ - zeroconf.ATTR_HOST: TEST_HOST, - ZEROCONF_NAME: TEST_ZEROCONF_NAME, - ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, - }, - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - mock_gateway_discovery = get_mock_discovery([TEST_HOST_2]) - - with patch( - "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGatewayDiscovery", - return_value=mock_gateway_discovery, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE}, - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {"base": "not_found_error"} From 187f47233c18bc8b9f9377ff5451efba40c0dc4e Mon Sep 17 00:00:00 2001 From: Quentame Date: Thu, 6 Aug 2020 11:18:05 +0200 Subject: [PATCH 03/77] Remove Linky integration (#38565) --- .coveragerc | 2 - CODEOWNERS | 1 - homeassistant/components/linky/__init__.py | 64 ------ homeassistant/components/linky/config_flow.py | 99 ---------- homeassistant/components/linky/const.py | 5 - homeassistant/components/linky/manifest.json | 8 - homeassistant/components/linky/sensor.py | 162 ---------------- homeassistant/components/linky/strings.json | 23 --- .../components/linky/translations/bg.json | 20 -- .../components/linky/translations/ca.json | 23 --- .../components/linky/translations/cs.json | 15 -- .../components/linky/translations/da.json | 23 --- .../components/linky/translations/de.json | 23 --- .../components/linky/translations/en.json | 23 --- .../components/linky/translations/es-419.json | 23 --- .../components/linky/translations/es.json | 23 --- .../components/linky/translations/fr.json | 23 --- .../components/linky/translations/hu.json | 21 -- .../components/linky/translations/it.json | 23 --- .../components/linky/translations/ko.json | 23 --- .../components/linky/translations/lb.json | 23 --- .../components/linky/translations/lv.json | 11 -- .../components/linky/translations/nl.json | 23 --- .../components/linky/translations/nn.json | 9 - .../components/linky/translations/no.json | 23 --- .../components/linky/translations/pl.json | 23 --- .../components/linky/translations/pt-BR.json | 17 -- .../components/linky/translations/pt.json | 12 -- .../components/linky/translations/ru.json | 23 --- .../components/linky/translations/sl.json | 23 --- .../components/linky/translations/sv.json | 23 --- .../linky/translations/zh-Hans.json | 16 -- .../linky/translations/zh-Hant.json | 23 --- homeassistant/generated/config_flows.py | 1 - requirements_all.txt | 3 - requirements_test_all.txt | 3 - tests/components/linky/__init__.py | 1 - tests/components/linky/conftest.py | 11 -- tests/components/linky/test_config_flow.py | 182 ------------------ 39 files changed, 1077 deletions(-) delete mode 100644 homeassistant/components/linky/__init__.py delete mode 100644 homeassistant/components/linky/config_flow.py delete mode 100644 homeassistant/components/linky/const.py delete mode 100644 homeassistant/components/linky/manifest.json delete mode 100644 homeassistant/components/linky/sensor.py delete mode 100644 homeassistant/components/linky/strings.json delete mode 100644 homeassistant/components/linky/translations/bg.json delete mode 100644 homeassistant/components/linky/translations/ca.json delete mode 100644 homeassistant/components/linky/translations/cs.json delete mode 100644 homeassistant/components/linky/translations/da.json delete mode 100644 homeassistant/components/linky/translations/de.json delete mode 100644 homeassistant/components/linky/translations/en.json delete mode 100644 homeassistant/components/linky/translations/es-419.json delete mode 100644 homeassistant/components/linky/translations/es.json delete mode 100644 homeassistant/components/linky/translations/fr.json delete mode 100644 homeassistant/components/linky/translations/hu.json delete mode 100644 homeassistant/components/linky/translations/it.json delete mode 100644 homeassistant/components/linky/translations/ko.json delete mode 100644 homeassistant/components/linky/translations/lb.json delete mode 100644 homeassistant/components/linky/translations/lv.json delete mode 100644 homeassistant/components/linky/translations/nl.json delete mode 100644 homeassistant/components/linky/translations/nn.json delete mode 100644 homeassistant/components/linky/translations/no.json delete mode 100644 homeassistant/components/linky/translations/pl.json delete mode 100644 homeassistant/components/linky/translations/pt-BR.json delete mode 100644 homeassistant/components/linky/translations/pt.json delete mode 100644 homeassistant/components/linky/translations/ru.json delete mode 100644 homeassistant/components/linky/translations/sl.json delete mode 100644 homeassistant/components/linky/translations/sv.json delete mode 100644 homeassistant/components/linky/translations/zh-Hans.json delete mode 100644 homeassistant/components/linky/translations/zh-Hant.json delete mode 100644 tests/components/linky/__init__.py delete mode 100644 tests/components/linky/conftest.py delete mode 100644 tests/components/linky/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 6f286fc6a69..f340202cdb8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -460,8 +460,6 @@ omit = homeassistant/components/lightwave/* homeassistant/components/limitlessled/light.py homeassistant/components/linksys_smart/device_tracker.py - homeassistant/components/linky/__init__.py - homeassistant/components/linky/sensor.py homeassistant/components/linode/* homeassistant/components/linux_battery/sensor.py homeassistant/components/lirc/* diff --git a/CODEOWNERS b/CODEOWNERS index 3c4ac8dd0dd..0081057f086 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -226,7 +226,6 @@ homeassistant/components/lametric/* @robbiet480 homeassistant/components/launch_library/* @ludeeus homeassistant/components/lcn/* @alengwenus homeassistant/components/life360/* @pnbruckner -homeassistant/components/linky/* @Quentame homeassistant/components/linux_battery/* @fabaff homeassistant/components/local_ip/* @issacg homeassistant/components/logger/* @home-assistant/core diff --git a/homeassistant/components/linky/__init__.py b/homeassistant/components/linky/__init__.py deleted file mode 100644 index d21c007762c..00000000000 --- a/homeassistant/components/linky/__init__.py +++ /dev/null @@ -1,64 +0,0 @@ -"""The linky component.""" -import logging - -import voluptuous as vol - -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import HomeAssistantType - -from .const import DEFAULT_TIMEOUT, DOMAIN - -_LOGGER = logging.getLogger(__name__) - -ACCOUNT_SCHEMA = vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - } -) - -CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.Schema(vol.All(cv.ensure_list, [ACCOUNT_SCHEMA]))}, - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass, config): - """Set up Linky sensors from legacy config file.""" - - conf = config.get(DOMAIN) - if conf is None: - return True - - for linky_account_conf in conf: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=linky_account_conf.copy(), - ) - ) - - return True - - -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): - """Set up Linky sensors.""" - # For backwards compat - if entry.unique_id is None: - hass.config_entries.async_update_entry( - entry, unique_id=entry.data[CONF_USERNAME] - ) - - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "sensor") - ) - return True - - -async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): - """Unload Linky sensors.""" - return await hass.config_entries.async_forward_entry_unload(entry, "sensor") diff --git a/homeassistant/components/linky/config_flow.py b/homeassistant/components/linky/config_flow.py deleted file mode 100644 index 88fa725cc4a..00000000000 --- a/homeassistant/components/linky/config_flow.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Config flow to configure the Linky integration.""" -import logging - -from pylinky.client import LinkyClient -from pylinky.exceptions import ( - PyLinkyAccessException, - PyLinkyEnedisException, - PyLinkyException, - PyLinkyWrongLoginException, -) -import voluptuous as vol - -from homeassistant import config_entries -from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME - -from .const import DEFAULT_TIMEOUT -from .const import DOMAIN # pylint: disable=unused-import - -_LOGGER = logging.getLogger(__name__) - - -class LinkyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow.""" - - VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - - def _show_setup_form(self, user_input=None, errors=None): - """Show the setup form to the user.""" - - if user_input is None: - user_input = {} - - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required( - CONF_USERNAME, default=user_input.get(CONF_USERNAME, "") - ): str, - vol.Required( - CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "") - ): str, - } - ), - errors=errors or {}, - ) - - async def async_step_user(self, user_input=None): - """Handle a flow initiated by the user.""" - errors = {} - - if user_input is None: - return self._show_setup_form(user_input, None) - - username = user_input[CONF_USERNAME] - password = user_input[CONF_PASSWORD] - timeout = user_input.get(CONF_TIMEOUT, DEFAULT_TIMEOUT) - - # Check if already configured - if self.unique_id is None: - await self.async_set_unique_id(username) - self._abort_if_unique_id_configured() - - client = LinkyClient(username, password, None, timeout) - try: - await self.hass.async_add_executor_job(client.login) - await self.hass.async_add_executor_job(client.fetch_data) - except PyLinkyAccessException as exp: - _LOGGER.error(exp) - errors["base"] = "access" - return self._show_setup_form(user_input, errors) - except PyLinkyEnedisException as exp: - _LOGGER.error(exp) - errors["base"] = "enedis" - return self._show_setup_form(user_input, errors) - except PyLinkyWrongLoginException as exp: - _LOGGER.error(exp) - errors["base"] = "wrong_login" - return self._show_setup_form(user_input, errors) - except PyLinkyException as exp: - _LOGGER.error(exp) - errors["base"] = "unknown" - return self._show_setup_form(user_input, errors) - finally: - client.close_session() - - return self.async_create_entry( - title=username, - data={ - CONF_USERNAME: username, - CONF_PASSWORD: password, - CONF_TIMEOUT: timeout, - }, - ) - - async def async_step_import(self, user_input=None): - """Import a config entry.""" - return await self.async_step_user(user_input) diff --git a/homeassistant/components/linky/const.py b/homeassistant/components/linky/const.py deleted file mode 100644 index e8e68867528..00000000000 --- a/homeassistant/components/linky/const.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Linky component constants.""" - -DOMAIN = "linky" - -DEFAULT_TIMEOUT = 10 diff --git a/homeassistant/components/linky/manifest.json b/homeassistant/components/linky/manifest.json deleted file mode 100644 index 18ee74a78ce..00000000000 --- a/homeassistant/components/linky/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "domain": "linky", - "name": "Enedis Linky", - "config_flow": true, - "documentation": "https://www.home-assistant.io/integrations/linky", - "requirements": ["pylinky==0.4.0"], - "codeowners": ["@Quentame"] -} diff --git a/homeassistant/components/linky/sensor.py b/homeassistant/components/linky/sensor.py deleted file mode 100644 index 7e9da01eb9a..00000000000 --- a/homeassistant/components/linky/sensor.py +++ /dev/null @@ -1,162 +0,0 @@ -"""Support for Linky.""" -from datetime import timedelta -import json -import logging - -from pylinky.client import DAILY, MONTHLY, YEARLY, LinkyClient, PyLinkyException - -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_ATTRIBUTION, - CONF_PASSWORD, - CONF_TIMEOUT, - CONF_USERNAME, - ENERGY_KILO_WATT_HOUR, -) -from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.typing import HomeAssistantType - -from .const import DOMAIN - -_LOGGER = logging.getLogger(__name__) - -SCAN_INTERVAL = timedelta(hours=4) -ICON_ENERGY = "mdi:flash" -CONSUMPTION = "conso" -TIME = "time" -INDEX_CURRENT = -1 -INDEX_LAST = -2 -ATTRIBUTION = "Data provided by Enedis" - - -async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities -) -> None: - """Add Linky entries.""" - account = LinkyAccount( - entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], entry.data[CONF_TIMEOUT] - ) - - await hass.async_add_executor_job(account.update_linky_data) - - sensors = [ - LinkySensor("Linky yesterday", account, DAILY, INDEX_LAST), - LinkySensor("Linky current month", account, MONTHLY, INDEX_CURRENT), - LinkySensor("Linky last month", account, MONTHLY, INDEX_LAST), - LinkySensor("Linky current year", account, YEARLY, INDEX_CURRENT), - LinkySensor("Linky last year", account, YEARLY, INDEX_LAST), - ] - - async_track_time_interval(hass, account.update_linky_data, SCAN_INTERVAL) - - async_add_entities(sensors, True) - - -class LinkyAccount: - """Representation of a Linky account.""" - - def __init__(self, username, password, timeout): - """Initialise the Linky account.""" - self._username = username - self._password = password - self._timeout = timeout - self._data = None - - def update_linky_data(self, event_time=None): - """Fetch new state data for the sensor.""" - client = LinkyClient(self._username, self._password, None, self._timeout) - try: - client.login() - client.fetch_data() - self._data = client.get_data() - _LOGGER.debug(json.dumps(self._data, indent=2)) - except PyLinkyException as exp: - _LOGGER.error(exp) - raise PlatformNotReady - finally: - client.close_session() - - @property - def username(self): - """Return the username.""" - return self._username - - @property - def data(self): - """Return the data.""" - return self._data - - -class LinkySensor(Entity): - """Representation of a sensor entity for Linky.""" - - def __init__(self, name, account: LinkyAccount, scale, when): - """Initialize the sensor.""" - self._name = name - self._account = account - self._scale = scale - self._when = when - self._username = account.username - self._time = None - self._consumption = None - self._unique_id = f"{self._username}_{scale}_{when}" - - @property - def unique_id(self): - """Return a unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def state(self): - """Return the state of the sensor.""" - return self._consumption - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return ENERGY_KILO_WATT_HOUR - - @property - def icon(self): - """Return the icon of the sensor.""" - return ICON_ENERGY - - @property - def device_state_attributes(self): - """Return the state attributes of the sensor.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - "time": self._time, - CONF_USERNAME: self._username, - } - - @property - def device_info(self): - """Return device information.""" - return { - "identifiers": {(DOMAIN, self._username)}, - "name": "Linky meter", - "manufacturer": "Enedis", - } - - async def async_update(self) -> None: - """Retrieve the new data for the sensor.""" - if self._account.data is None: - return - - data = self._account.data[self._scale][self._when] - self._consumption = data[CONSUMPTION] - self._time = data[TIME] - - if self._scale is not YEARLY: - year_index = INDEX_CURRENT - if self._time.endswith("Dec"): - year_index = INDEX_LAST - self._time += f" {self._account.data[YEARLY][year_index][TIME]}" diff --git a/homeassistant/components/linky/strings.json b/homeassistant/components/linky/strings.json deleted file mode 100644 index dea7062d213..00000000000 --- a/homeassistant/components/linky/strings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Linky", - "description": "Enter your credentials", - "data": { - "username": "[%key:common::config_flow::data::email%]", - "password": "[%key:common::config_flow::data::password%]" - } - } - }, - "error": { - "access": "Could not access to Enedis.fr, please check your internet connection", - "enedis": "Enedis.fr answered with an error: please retry later (usually not between 11PM and 2AM)", - "wrong_login": "Login error: please check your email & password", - "unknown": "Unknown error: please retry later (usually not between 11PM and 2AM)" - }, - "abort": { - "already_configured": "Account already configured" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/bg.json b/homeassistant/components/linky/translations/bg.json deleted file mode 100644 index dd337013f59..00000000000 --- a/homeassistant/components/linky/translations/bg.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "error": { - "access": "\u041d\u044f\u043c\u0430 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e Enedis.fr, \u043c\u043e\u043b\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u043e\u0441\u0442\u0442\u0430 \u0441\u0438", - "enedis": "Enedis.fr \u043e\u0442\u0433\u043e\u0432\u043e\u0440\u0438 \u0441 \u0433\u0440\u0435\u0448\u043a\u0430: \u043c\u043e\u043b\u044f, \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e \u043f\u043e-\u043a\u044a\u0441\u043d\u043e (\u043e\u0431\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u043e \u043d\u0435 \u043c\u0435\u0436\u0434\u0443 23:00 \u0438 02:00)", - "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430: \u043c\u043e\u043b\u044f, \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e \u043f\u043e-\u043a\u044a\u0441\u043d\u043e (\u043e\u0431\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u043e \u043d\u0435 \u043c\u0435\u0436\u0434\u0443 23:00 \u0438 02:00)", - "wrong_login": "\u0413\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0432\u043b\u0438\u0437\u0430\u043d\u0435: \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0438\u043c\u0435\u0439\u043b\u0430 \u0438 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u0441\u0438" - }, - "step": { - "user": { - "data": { - "password": "\u041f\u0430\u0440\u043e\u043b\u0430", - "username": "E-mail" - }, - "description": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u043d\u0434\u0435\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438\u0442\u0435 \u0441\u0438 \u0434\u0430\u043d\u043d\u0438", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/ca.json b/homeassistant/components/linky/translations/ca.json deleted file mode 100644 index 954b873083a..00000000000 --- a/homeassistant/components/linky/translations/ca.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "El compte ja ha estat configurat" - }, - "error": { - "access": "No s'ha pogut accedir a Enedis.fr, comprova la teva connexi\u00f3 a Internet", - "enedis": "Enedis.fr ha respost amb un error: torna-ho a provar m\u00e9s tard (millo no entre les 23:00 i les 14:00)", - "unknown": "Error desconegut: torna-ho a provar m\u00e9s tard (millor no entre les 23:00 i les 14:00)", - "wrong_login": "Error d'inici de sessi\u00f3: comprova el teu correu electr\u00f2nic i la contrasenya" - }, - "step": { - "user": { - "data": { - "password": "Contrasenya", - "username": "Correu electr\u00f2nic" - }, - "description": "Introdueix les teves credencials", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/cs.json b/homeassistant/components/linky/translations/cs.json deleted file mode 100644 index 8f8c4648d5f..00000000000 --- a/homeassistant/components/linky/translations/cs.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u00da\u010det je ji\u017e nakonfigurov\u00e1n" - }, - "step": { - "user": { - "data": { - "password": "Heslo", - "username": "E-mail" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/da.json b/homeassistant/components/linky/translations/da.json deleted file mode 100644 index 2fa885d1ffa..00000000000 --- a/homeassistant/components/linky/translations/da.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Kontoen er allerede konfigureret" - }, - "error": { - "access": "Kunne ikke f\u00e5 adgang til Enedis.fr, kontroller din internetforbindelse", - "enedis": "Enedis.fr svarede med en fejl: Pr\u00f8v igen senere (normalt ikke mellem 23:00 og 02:00)", - "unknown": "Ukendt fejl: Pr\u00f8v igen senere (normalt ikke mellem 23:00 og 02:00)", - "wrong_login": "Loginfejl: Kontroller din e-mail og adgangskode" - }, - "step": { - "user": { - "data": { - "password": "Adgangskode", - "username": "E-mail" - }, - "description": "Indtast dine legitimationsoplysninger", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/de.json b/homeassistant/components/linky/translations/de.json deleted file mode 100644 index c915ddf0881..00000000000 --- a/homeassistant/components/linky/translations/de.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Konto bereits konfiguriert" - }, - "error": { - "access": "Konnte nicht auf Enedis.fr zugreifen, \u00fcberpr\u00fcfe bitte die Internetverbindung", - "enedis": "Enedis.fr antwortete mit einem Fehler: wiederhole den Vorgang sp\u00e4ter (in der Regel nicht zwischen 23 Uhr und 2 Uhr morgens)", - "unknown": "Unbekannter Fehler: Wiederhole den Vorgang sp\u00e4ter (in der Regel nicht zwischen 23 Uhr und 2 Uhr morgens)", - "wrong_login": "Login-Fehler: Pr\u00fcfe bitte E-Mail & Passwort" - }, - "step": { - "user": { - "data": { - "password": "Passwort", - "username": "E-Mail-Adresse" - }, - "description": "Gib deine Zugangsdaten ein", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/en.json b/homeassistant/components/linky/translations/en.json deleted file mode 100644 index 512c0567444..00000000000 --- a/homeassistant/components/linky/translations/en.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Account already configured" - }, - "error": { - "access": "Could not access to Enedis.fr, please check your internet connection", - "enedis": "Enedis.fr answered with an error: please retry later (usually not between 11PM and 2AM)", - "unknown": "Unknown error: please retry later (usually not between 11PM and 2AM)", - "wrong_login": "Login error: please check your email & password" - }, - "step": { - "user": { - "data": { - "password": "Password", - "username": "Email" - }, - "description": "Enter your credentials", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/es-419.json b/homeassistant/components/linky/translations/es-419.json deleted file mode 100644 index 58e44695fc8..00000000000 --- a/homeassistant/components/linky/translations/es-419.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "La cuenta ya ha sido configurada" - }, - "error": { - "access": "No se pudo acceder a Enedis.fr, compruebe su conexi\u00f3n a Internet.", - "enedis": "Enedis.fr respondi\u00f3 con un error: vuelva a intentarlo m\u00e1s tarde (normalmente no entre las 11 p.m. y las 2 a.m.)", - "unknown": "Error desconocido: por favor, vuelva a intentarlo m\u00e1s tarde (normalmente no entre las 11 p.m. y las 2 a.m.)", - "wrong_login": "Error de inicio de sesi\u00f3n: por favor revise su direcci\u00f3n de correo electr\u00f3nico y contrase\u00f1a" - }, - "step": { - "user": { - "data": { - "password": "Contrase\u00f1a", - "username": "Correo electr\u00f3nico" - }, - "description": "Ingrese sus credenciales", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/es.json b/homeassistant/components/linky/translations/es.json deleted file mode 100644 index ef07dc2ca75..00000000000 --- a/homeassistant/components/linky/translations/es.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "La cuenta ya est\u00e1 configurada" - }, - "error": { - "access": "No se pudo acceder a Enedis.fr, comprueba tu conexi\u00f3n a Internet", - "enedis": "Enedis.fr respondi\u00f3 con un error: vuelva a intentarlo m\u00e1s tarde (normalmente no entre las 11:00 y las 2 de la ma\u00f1ana)", - "unknown": "Error desconocido: por favor, vuelva a intentarlo m\u00e1s tarde (normalmente no entre las 23:00 y las 02:00 horas).", - "wrong_login": "Error de inicio de sesi\u00f3n: comprueba tu direcci\u00f3n de correo electr\u00f3nico y contrase\u00f1a" - }, - "step": { - "user": { - "data": { - "password": "Contrase\u00f1a", - "username": "Correo electr\u00f3nico" - }, - "description": "Introduzca sus credenciales", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/fr.json b/homeassistant/components/linky/translations/fr.json deleted file mode 100644 index 71dba36dbe8..00000000000 --- a/homeassistant/components/linky/translations/fr.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9" - }, - "error": { - "access": "Impossible d'acc\u00e9der \u00e0 Enedis.fr, merci de v\u00e9rifier votre connexion internet", - "enedis": "Erreur d'Enedis.fr: merci de r\u00e9essayer plus tard (pas entre 23h et 2h)", - "unknown": "Erreur inconnue: merci de r\u00e9essayer plus tard (pas entre 23h et 2h)", - "wrong_login": "Erreur de connexion: veuillez v\u00e9rifier votre e-mail et votre mot de passe" - }, - "step": { - "user": { - "data": { - "password": "Mot de passe", - "username": "Email" - }, - "description": "Entrez vos identifiants", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/hu.json b/homeassistant/components/linky/translations/hu.json deleted file mode 100644 index 9b450985375..00000000000 --- a/homeassistant/components/linky/translations/hu.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" - }, - "error": { - "access": "Nem siker\u00fclt el\u00e9rni az Enedis.fr webhelyet, ellen\u0151rizze internet-kapcsolat\u00e1t", - "enedis": "Az Enedis.fr hib\u00e1val v\u00e1laszolt: k\u00e9rj\u00fck, pr\u00f3b\u00e1lkozzon k\u00e9s\u0151bb \u00fajra (\u00e1ltal\u00e1ban nem 23:00 \u00e9s 2:00 k\u00f6z\u00f6tt)", - "unknown": "Ismeretlen hiba: pr\u00f3b\u00e1lkozzon k\u00e9s\u0151bb (\u00e1ltal\u00e1ban nem 23:00 \u00e9s 2:00 \u00f3ra k\u00f6z\u00f6tt)" - }, - "step": { - "user": { - "data": { - "password": "Jelsz\u00f3", - "username": "E-mail" - }, - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/it.json b/homeassistant/components/linky/translations/it.json deleted file mode 100644 index ff5e226dcbe..00000000000 --- a/homeassistant/components/linky/translations/it.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Account gi\u00e0 configurato" - }, - "error": { - "access": "Impossibile accedere a Enedis.fr, si prega di controllare la connessione internet", - "enedis": "Enedis.fr ha risposto con un errore: si prega di riprovare pi\u00f9 tardi (di solito non tra le 23:00 e le 02:00).", - "unknown": "Errore sconosciuto: riprova pi\u00f9 tardi (in genere non tra le 23:00 e le 02:00)", - "wrong_login": "Errore di accesso: si prega di controllare la tua E-mail e la password" - }, - "step": { - "user": { - "data": { - "password": "Password", - "username": "E-mail" - }, - "description": "Inserisci le tue credenziali", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/ko.json b/homeassistant/components/linky/translations/ko.json deleted file mode 100644 index cd83aad724f..00000000000 --- a/homeassistant/components/linky/translations/ko.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." - }, - "error": { - "access": "Enedis.fr \uc5d0 \uc811\uc18d\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc778\ud130\ub137 \uc5f0\uacb0\uc744 \ud655\uc778\ud574\ubcf4\uc138\uc694", - "enedis": "Enedis.fr \uc774 \uc624\ub958\ub85c \uc751\ub2f5\ud588\uc2b5\ub2c8\ub2e4: \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694 (\uc800\ub141 11\uc2dc \ubd80\ud130 \uc0c8\ubcbd 2\uc2dc\ub294 \ud53c\ud574\uc8fc\uc138\uc694)", - "unknown": "\uc54c \uc218\uc5c6\ub294 \uc624\ub958: \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694 (\uc800\ub141 11\uc2dc \ubd80\ud130 \uc0c8\ubcbd 2\uc2dc\ub294 \ud53c\ud574\uc8fc\uc138\uc694)", - "wrong_login": "\ub85c\uadf8\uc778 \uc624\ub958: \uc774\uba54\uc77c \ubc0f \ube44\ubc00\ubc88\ud638\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" - }, - "step": { - "user": { - "data": { - "password": "\ube44\ubc00\ubc88\ud638", - "username": "\uc774\uba54\uc77c" - }, - "description": "\uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/lb.json b/homeassistant/components/linky/translations/lb.json deleted file mode 100644 index 091a3b8d699..00000000000 --- a/homeassistant/components/linky/translations/lb.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Kont ass scho konfigur\u00e9iert" - }, - "error": { - "access": "Keng Verbindung zu Enedis.fr, iwwerpr\u00e9ift d'Internet Verbindung", - "enedis": "Enedis.fr huet mat engem Feeler ge\u00e4ntwert: prob\u00e9iert sp\u00e9ider nach emol (normalerweis net t\u00ebscht 23h00 an 2h00)", - "unknown": "Onbekannte Feeler: prob\u00e9iert sp\u00e9ider nach emol (normalerweis net t\u00ebscht 23h00 an 2h00)", - "wrong_login": "Feeler beim Login: iwwerpr\u00e9ift \u00e4r E-Mail & Passwuert" - }, - "step": { - "user": { - "data": { - "password": "Passwuert", - "username": "E-Mail" - }, - "description": "F\u00ebllt \u00e4r Login Informatiounen aus", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/lv.json b/homeassistant/components/linky/translations/lv.json deleted file mode 100644 index 973833a5470..00000000000 --- a/homeassistant/components/linky/translations/lv.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "username": "E-pasts" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/nl.json b/homeassistant/components/linky/translations/nl.json deleted file mode 100644 index 2c05353be3f..00000000000 --- a/homeassistant/components/linky/translations/nl.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Account al geconfigureerd" - }, - "error": { - "access": "Geen toegang tot Enedis.fr, controleer uw internetverbinding", - "enedis": "Enedis.fr antwoordde met een fout: probeer het later opnieuw (meestal niet tussen 23.00 en 02.00 uur)", - "unknown": "Onbekende fout: probeer het later opnieuw (meestal niet tussen 23.00 en 02.00 uur)", - "wrong_login": "Aanmeldingsfout: controleer uw e-mailadres en wachtwoord" - }, - "step": { - "user": { - "data": { - "password": "Wachtwoord", - "username": "E-mail" - }, - "description": "Voer uw gegevens in", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/nn.json b/homeassistant/components/linky/translations/nn.json deleted file mode 100644 index 6cdaaf837a4..00000000000 --- a/homeassistant/components/linky/translations/nn.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/no.json b/homeassistant/components/linky/translations/no.json deleted file mode 100644 index 5cf8ea2da34..00000000000 --- a/homeassistant/components/linky/translations/no.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Kontoen er allerede konfigurert" - }, - "error": { - "access": "Kunne ikke f\u00e5 tilgang til Enedis.fr, vennligst sjekk internettforbindelsen din", - "enedis": "Enedis.fr svarte med en feil: vennligst pr\u00f8v p\u00e5 nytt senere (vanligvis ikke mellom 23:00 og 02:00)", - "unknown": "Ukjent feil: pr\u00f8v p\u00e5 nytt senere (vanligvis ikke mellom 23:00 og 02:00)", - "wrong_login": "Innloggingsfeil: vennligst sjekk e-postadressen og passordet ditt" - }, - "step": { - "user": { - "data": { - "password": "Passord", - "username": "E-post" - }, - "description": "Fyll inn legitimasjonen din", - "title": "" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/pl.json b/homeassistant/components/linky/translations/pl.json deleted file mode 100644 index 1fc09298fd7..00000000000 --- a/homeassistant/components/linky/translations/pl.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane." - }, - "error": { - "access": "Nie mo\u017cna uzyska\u0107 dost\u0119pu do Enedis.fr, sprawd\u017a po\u0142\u0105czenie internetowe", - "enedis": "Enedis.fr odpowiedzia\u0142 b\u0142\u0119dem: spr\u00f3buj ponownie p\u00f3\u017aniej (zwykle nie mi\u0119dzy 23:00, a 2:00)", - "unknown": "Nieznany b\u0142\u0105d: spr\u00f3buj ponownie p\u00f3\u017aniej (zwykle nie mi\u0119dzy godzin\u0105 23:00, a 2:00)", - "wrong_login": "B\u0142\u0105d logowania: sprawd\u017a adres e-mail i has\u0142o" - }, - "step": { - "user": { - "data": { - "password": "Has\u0142o", - "username": "Adres e-mail" - }, - "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/pt-BR.json b/homeassistant/components/linky/translations/pt-BR.json deleted file mode 100644 index bf2bc7070ae..00000000000 --- a/homeassistant/components/linky/translations/pt-BR.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "error": { - "wrong_login": "Erro de Login: por favor, verifique seu e-mail e senha" - }, - "step": { - "user": { - "data": { - "password": "Senha", - "username": "E-mail" - }, - "description": "Insira suas credenciais", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/pt.json b/homeassistant/components/linky/translations/pt.json deleted file mode 100644 index 54619af958e..00000000000 --- a/homeassistant/components/linky/translations/pt.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "password": "Palavra-passe", - "username": "O email" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/ru.json b/homeassistant/components/linky/translations/ru.json deleted file mode 100644 index 65e0269967a..00000000000 --- a/homeassistant/components/linky/translations/ru.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u0423\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430." - }, - "error": { - "access": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a Enedis.fr, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443.", - "enedis": "Enedis.fr \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b \u043e\u0442\u0432\u0435\u0442 \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439: \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435 (\u043d\u0435 \u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043a\u0435 \u0441 23:00 \u043f\u043e 2:00).", - "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430: \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435 (\u043d\u0435 \u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043a\u0435 \u0441 23:00 \u043f\u043e 2:00).", - "wrong_login": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0445\u043e\u0434\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c." - }, - "step": { - "user": { - "data": { - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b" - }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/sl.json b/homeassistant/components/linky/translations/sl.json deleted file mode 100644 index 3df56ac5bbb..00000000000 --- a/homeassistant/components/linky/translations/sl.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Ra\u010dun \u017ee nastavljen" - }, - "error": { - "access": "Do Enedis.fr ni bilo mogo\u010de dostopati, preverite internetno povezavo", - "enedis": "Enedis.fr je odgovoril z napako: poskusite pozneje (ponavadi med 23. in 2. uro)", - "unknown": "Neznana napaka: Prosimo, poskusite pozneje (obi\u010dajno ne med 23. in 2. uro)", - "wrong_login": "Napaka pri prijavi: preverite svoj e-po\u0161tni naslov in geslo" - }, - "step": { - "user": { - "data": { - "password": "Geslo", - "username": "E-po\u0161tni naslov" - }, - "description": "Vnesite svoje poverilnice", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/sv.json b/homeassistant/components/linky/translations/sv.json deleted file mode 100644 index 2d8c2b7177a..00000000000 --- a/homeassistant/components/linky/translations/sv.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Kontot har redan konfigurerats." - }, - "error": { - "access": "Det gick inte att komma \u00e5t Enedis.fr, kontrollera din internetanslutning", - "enedis": "Enedis.fr svarade med ett fel: f\u00f6rs\u00f6k igen senare (vanligtvis inte mellan 23:00 och 02:00)", - "unknown": "Ok\u00e4nt fel: f\u00f6rs\u00f6k igen senare (vanligtvis inte mellan 23:00 och 02:00)", - "wrong_login": "Inloggningsfel: v\u00e4nligen kontrollera din e-post och l\u00f6senord" - }, - "step": { - "user": { - "data": { - "password": "L\u00f6senord", - "username": "E-post" - }, - "description": "Ange dina autentiseringsuppgifter", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/zh-Hans.json b/homeassistant/components/linky/translations/zh-Hans.json deleted file mode 100644 index 62138856078..00000000000 --- a/homeassistant/components/linky/translations/zh-Hans.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "config": { - "error": { - "wrong_login": "\u767b\u5f55\u51fa\u9519\uff1a\u8bf7\u68c0\u67e5\u60a8\u7684\u7535\u5b50\u90ae\u7bb1\u548c\u5bc6\u7801" - }, - "step": { - "user": { - "data": { - "password": "\u5bc6\u7801", - "username": "\u7535\u5b50\u90ae\u7bb1" - }, - "description": "\u8f93\u5165\u60a8\u7684\u8eab\u4efd\u8ba4\u8bc1" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/linky/translations/zh-Hant.json b/homeassistant/components/linky/translations/zh-Hant.json deleted file mode 100644 index 7a28dd692f6..00000000000 --- a/homeassistant/components/linky/translations/zh-Hant.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" - }, - "error": { - "access": "\u7121\u6cd5\u8a2a\u554f Enedis.fr\uff0c\u8acb\u6aa2\u67e5\u60a8\u7684\u7db2\u969b\u7db2\u8def\u9023\u7dda", - "enedis": "Endis.fr \u56de\u5831\u932f\u8aa4\uff1a\u8acb\u7a0d\u5f8c\u518d\u8a66\uff08\u901a\u5e38\u907f\u958b\u591c\u9593 11 - \u51cc\u6668 2 \u9ede\u4e4b\u9593\uff09", - "unknown": "\u672a\u77e5\u932f\u8aa4\uff1a\u8acb\u7a0d\u5f8c\u518d\u8a66\uff08\u901a\u5e38\u907f\u958b\u591c\u9593 11 - \u51cc\u6668 2 \u9ede\u4e4b\u9593\uff09", - "wrong_login": "\u767b\u5165\u932f\u8aa4\uff1a\u8acb\u78ba\u8a8d\u96fb\u5b50\u90f5\u4ef6\u8207\u5bc6\u78bc" - }, - "step": { - "user": { - "data": { - "password": "\u5bc6\u78bc", - "username": "\u96fb\u5b50\u90f5\u4ef6" - }, - "description": "\u8f38\u5165\u6191\u8b49", - "title": "Linky" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 7aa9eac6a86..3b4216377e5 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -94,7 +94,6 @@ FLOWS = [ "konnected", "life360", "lifx", - "linky", "local_ip", "locative", "logi_circle", diff --git a/requirements_all.txt b/requirements_all.txt index c144f8207fc..a18e41e096b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1439,9 +1439,6 @@ pylgnetcast-homeassistant==0.2.0.dev0 # homeassistant.components.forked_daapd pylibrespot-java==0.1.0 -# homeassistant.components.linky -pylinky==0.4.0 - # homeassistant.components.litejet pylitejet==0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 722fbe0bcc5..814b422ee7d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -676,9 +676,6 @@ pylast==3.2.1 # homeassistant.components.forked_daapd pylibrespot-java==0.1.0 -# homeassistant.components.linky -pylinky==0.4.0 - # homeassistant.components.litejet pylitejet==0.1 diff --git a/tests/components/linky/__init__.py b/tests/components/linky/__init__.py deleted file mode 100644 index f461885e384..00000000000 --- a/tests/components/linky/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the Linky component.""" diff --git a/tests/components/linky/conftest.py b/tests/components/linky/conftest.py deleted file mode 100644 index 93e3ff78d2b..00000000000 --- a/tests/components/linky/conftest.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Linky generic test utils.""" -import pytest - -from tests.async_mock import patch - - -@pytest.fixture(autouse=True) -def patch_fakeuseragent(): - """Stub out fake useragent dep that makes requests.""" - with patch("pylinky.client.UserAgent", return_value="Test Browser"): - yield diff --git a/tests/components/linky/test_config_flow.py b/tests/components/linky/test_config_flow.py deleted file mode 100644 index f39f0da7d99..00000000000 --- a/tests/components/linky/test_config_flow.py +++ /dev/null @@ -1,182 +0,0 @@ -"""Tests for the Linky config flow.""" -from pylinky.exceptions import ( - PyLinkyAccessException, - PyLinkyEnedisException, - PyLinkyException, - PyLinkyWrongLoginException, -) -import pytest - -from homeassistant import data_entry_flow -from homeassistant.components.linky.const import DEFAULT_TIMEOUT, DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER -from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME -from homeassistant.helpers.typing import HomeAssistantType - -from tests.async_mock import Mock, patch -from tests.common import MockConfigEntry - -USERNAME = "username@hotmail.fr" -USERNAME_2 = "username@free.fr" -PASSWORD = "password" -TIMEOUT = 20 - - -@pytest.fixture(name="login") -def mock_controller_login(): - """Mock a successful login.""" - with patch( - "homeassistant.components.linky.config_flow.LinkyClient" - ) as service_mock: - service_mock.return_value.login = Mock(return_value=True) - service_mock.return_value.close_session = Mock(return_value=None) - yield service_mock - - -@pytest.fixture(name="fetch_data") -def mock_controller_fetch_data(): - """Mock a successful get data.""" - with patch( - "homeassistant.components.linky.config_flow.LinkyClient" - ) as service_mock: - service_mock.return_value.fetch_data = Mock(return_value={}) - service_mock.return_value.close_session = Mock(return_value=None) - yield service_mock - - -async def test_user(hass: HomeAssistantType, login, fetch_data): - """Test user config.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=None - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - # test with all provided - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == USERNAME - assert result["title"] == USERNAME - assert result["data"][CONF_USERNAME] == USERNAME - assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_TIMEOUT] == DEFAULT_TIMEOUT - - -async def test_import(hass: HomeAssistantType, login, fetch_data): - """Test import step.""" - # import with username and password - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == USERNAME - assert result["title"] == USERNAME - assert result["data"][CONF_USERNAME] == USERNAME - assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_TIMEOUT] == DEFAULT_TIMEOUT - - # import with all - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - CONF_USERNAME: USERNAME_2, - CONF_PASSWORD: PASSWORD, - CONF_TIMEOUT: TIMEOUT, - }, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == USERNAME_2 - assert result["title"] == USERNAME_2 - assert result["data"][CONF_USERNAME] == USERNAME_2 - assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_TIMEOUT] == TIMEOUT - - -async def test_abort_if_already_setup(hass: HomeAssistantType, login, fetch_data): - """Test we abort if Linky is already setup.""" - MockConfigEntry( - domain=DOMAIN, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - unique_id=USERNAME, - ).add_to_hass(hass) - - # Should fail, same USERNAME (import) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - # Should fail, same USERNAME (flow) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - -async def test_login_failed(hass: HomeAssistantType, login): - """Test when we have errors during login.""" - login.return_value.login.side_effect = PyLinkyAccessException() - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "access"} - hass.config_entries.flow.async_abort(result["flow_id"]) - - login.return_value.login.side_effect = PyLinkyWrongLoginException() - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "wrong_login"} - hass.config_entries.flow.async_abort(result["flow_id"]) - - -async def test_fetch_failed(hass: HomeAssistantType, login): - """Test when we have errors during fetch.""" - login.return_value.fetch_data.side_effect = PyLinkyAccessException() - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "access"} - hass.config_entries.flow.async_abort(result["flow_id"]) - - login.return_value.fetch_data.side_effect = PyLinkyEnedisException() - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "enedis"} - hass.config_entries.flow.async_abort(result["flow_id"]) - - login.return_value.fetch_data.side_effect = PyLinkyException() - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "unknown"} - hass.config_entries.flow.async_abort(result["flow_id"]) From 5bdeb46c125d42c9a57b4980de69c8a32a5140f5 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 6 Aug 2020 10:43:47 +0200 Subject: [PATCH 04/77] Suppress MQTT discovery updates without changes (#38568) --- homeassistant/components/mqtt/__init__.py | 12 +++++++--- .../components/mqtt/binary_sensor.py | 3 ++- .../components/mqtt/light/schema_template.py | 4 ++-- .../mqtt/test_alarm_control_panel.py | 16 +++++++++++++ tests/components/mqtt/test_binary_sensor.py | 15 ++++++++++++ tests/components/mqtt/test_camera.py | 19 ++++++++++++--- tests/components/mqtt/test_climate.py | 18 ++++++++++++--- tests/components/mqtt/test_common.py | 23 +++++++++++++++++++ tests/components/mqtt/test_cover.py | 23 +++++++++++++++---- tests/components/mqtt/test_fan.py | 21 +++++++++++++---- tests/components/mqtt/test_legacy_vacuum.py | 13 +++++++++++ tests/components/mqtt/test_light.py | 16 +++++++++++++ tests/components/mqtt/test_light_json.py | 17 ++++++++++++++ tests/components/mqtt/test_light_template.py | 19 +++++++++++++++ tests/components/mqtt/test_lock.py | 17 ++++++++++++++ tests/components/mqtt/test_sensor.py | 22 ++++++++++++++---- tests/components/mqtt/test_state_vacuum.py | 23 +++++++++++++++---- tests/components/mqtt/test_switch.py | 16 +++++++++++++ 18 files changed, 266 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index a0527cfe427..81c44ac8aea 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -44,6 +44,7 @@ from . import config_flow # noqa: F401 pylint: disable=unused-import from . import debug_info, discovery from .const import ( ATTR_DISCOVERY_HASH, + ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC, ATTR_PAYLOAD, ATTR_QOS, @@ -1169,6 +1170,7 @@ class MqttDiscoveryUpdate(Entity): _LOGGER.info( "Got update for entity with hash: %s '%s'", discovery_hash, payload, ) + old_payload = self._discovery_data[ATTR_DISCOVERY_PAYLOAD] debug_info.update_entity_discovery_data(self.hass, payload, self.entity_id) if not payload: # Empty payload: Remove component @@ -1176,9 +1178,13 @@ class MqttDiscoveryUpdate(Entity): self._cleanup_discovery_on_remove() await _async_remove_state_and_registry_entry(self) elif self._discovery_update: - # Non-empty payload: Notify component - _LOGGER.info("Updating component: %s", self.entity_id) - await self._discovery_update(payload) + if old_payload != self._discovery_data[ATTR_DISCOVERY_PAYLOAD]: + # Non-empty, changed payload: Notify component + _LOGGER.info("Updating component: %s", self.entity_id) + await self._discovery_update(payload) + else: + # Non-empty, unchanged payload: Ignore to avoid changing states + _LOGGER.info("Ignoring unchanged update for: %s", self.entity_id) if discovery_hash: debug_info.add_entity_discovery_data( diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index cd69967e6a7..5d69bfde4f6 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -169,7 +169,8 @@ class MqttBinarySensor( if expire_after is not None and expire_after > 0: - # When expire_after is set, and we receive a message, assume device is not expired since it has to be to receive the message + # When expire_after is set, and we receive a message, assume device is + # not expired since it has to be to receive the message self._expired = False # Reset old trigger diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index a9f18e7039b..d14cda70bb6 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -101,10 +101,10 @@ async def async_setup_entity_template( config, async_add_entities, config_entry, discovery_data ): """Set up a MQTT Template light.""" - async_add_entities([MqttTemplate(config, config_entry, discovery_data)]) + async_add_entities([MqttLightTemplate(config, config_entry, discovery_data)]) -class MqttTemplate( +class MqttLightTemplate( MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index aa6452fd9c8..734e1fd552f 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -28,6 +28,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -42,6 +43,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import assert_setup_component, async_fire_mqtt_message from tests.components.alarm_control_panel import common @@ -575,6 +577,20 @@ async def test_discovery_update_alarm(hass, mqtt_mock, caplog): ) +async def test_discovery_update_unchanged_alarm(hass, mqtt_mock, caplog): + """Test update of discovered alarm_control_panel.""" + config1 = copy.deepcopy(DEFAULT_CONFIG[alarm_control_panel.DOMAIN]) + config1["name"] = "Beer" + + data1 = json.dumps(config1) + with patch( + "homeassistant.components.mqtt.alarm_control_panel.MqttAlarm.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, alarm_control_panel.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index b909a0592e0..c739f4378d1 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -26,6 +26,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -593,6 +594,20 @@ async def test_discovery_update_binary_sensor(hass, mqtt_mock, caplog): ) +async def test_discovery_update_unchanged_binary_sensor(hass, mqtt_mock, caplog): + """Test update of discovered binary_sensor.""" + config1 = copy.deepcopy(DEFAULT_CONFIG[binary_sensor.DOMAIN]) + config1["name"] = "Beer" + + data1 = json.dumps(config1) + with patch( + "homeassistant.components.mqtt.binary_sensor.MqttBinarySensor.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, binary_sensor.DOMAIN, data1, discovery_update + ) + + async def test_expiration_on_discovery_and_discovery_update_of_binary_sensor( hass, mqtt_mock, legacy_patchable_time, caplog ): diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index 6869b530668..22f714fdcf7 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -16,6 +16,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -30,6 +31,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import async_fire_mqtt_message DEFAULT_CONFIG = { @@ -153,14 +155,25 @@ async def test_discovery_update_camera(hass, mqtt_mock, caplog): entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] await async_start(hass, "homeassistant", entry) - data1 = '{ "name": "Beer",' ' "topic": "test_topic"}' - data2 = '{ "name": "Milk",' ' "topic": "test_topic"}' + data1 = '{ "name": "Beer", "topic": "test_topic"}' + data2 = '{ "name": "Milk", "topic": "test_topic"}' await help_test_discovery_update( hass, mqtt_mock, caplog, camera.DOMAIN, data1, data2 ) +async def test_discovery_update_unchanged_camera(hass, mqtt_mock, caplog): + """Test update of discovered camera.""" + data1 = '{ "name": "Beer", "topic": "test_topic"}' + with patch( + "homeassistant.components.mqtt.camera.MqttCamera.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, camera.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" @@ -168,7 +181,7 @@ async def test_discovery_broken(hass, mqtt_mock, caplog): await async_start(hass, "homeassistant", entry) data1 = '{ "name": "Beer" }' - data2 = '{ "name": "Milk",' ' "topic": "test_topic"}' + data2 = '{ "name": "Milk", "topic": "test_topic"}' await help_test_discovery_broken( hass, mqtt_mock, caplog, camera.DOMAIN, data1, data2 diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 6a7bdf0b7e6..d60af211d71 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -34,6 +34,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -48,7 +49,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) -from tests.async_mock import call +from tests.async_mock import call, patch from tests.common import async_fire_mqtt_message from tests.components.climate import common @@ -909,11 +910,22 @@ async def test_discovery_update_climate(hass, mqtt_mock, caplog): ) +async def test_discovery_update_unchanged_climate(hass, mqtt_mock, caplog): + """Test update of discovered climate.""" + data1 = '{ "name": "Beer" }' + with patch( + "homeassistant.components.mqtt.climate.MqttClimate.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, CLIMATE_DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" - data1 = '{ "name": "Beer",' ' "power_command_topic": "test_topic#" }' - data2 = '{ "name": "Milk", ' ' "power_command_topic": "test_topic" }' + data1 = '{ "name": "Beer", "power_command_topic": "test_topic#" }' + data2 = '{ "name": "Milk", "power_command_topic": "test_topic" }' await help_test_discovery_broken( hass, mqtt_mock, caplog, CLIMATE_DOMAIN, data1, data2 ) diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 31566885a37..89bfde22d87 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -497,6 +497,29 @@ async def help_test_discovery_update(hass, mqtt_mock, caplog, domain, data1, dat assert state is None +async def help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, domain, data1, discovery_update +): + """Test update of discovered component without changes. + + This is a test helper for the MqttDiscoveryUpdate mixin. + """ + entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] + await async_start(hass, "homeassistant", entry) + + async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data1) + await hass.async_block_till_done() + + state = hass.states.get(f"{domain}.beer") + assert state is not None + assert state.name == "Beer" + + async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data1) + await hass.async_block_till_done() + + assert not discovery_update.called + + async def help_test_discovery_broken(hass, mqtt_mock, caplog, domain, data1, data2): """Test handling of bad discovery message.""" entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index c3f00badef8..f9036bcfa0f 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -38,6 +38,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -52,6 +53,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import async_fire_mqtt_message DEFAULT_CONFIG = { @@ -1862,24 +1864,35 @@ async def test_unique_id(hass, mqtt_mock): async def test_discovery_removal_cover(hass, mqtt_mock, caplog): """Test removal of discovered cover.""" - data = '{ "name": "test",' ' "command_topic": "test_topic" }' + data = '{ "name": "test", "command_topic": "test_topic" }' await help_test_discovery_removal(hass, mqtt_mock, caplog, cover.DOMAIN, data) async def test_discovery_update_cover(hass, mqtt_mock, caplog): """Test update of discovered cover.""" - data1 = '{ "name": "Beer",' ' "command_topic": "test_topic" }' - data2 = '{ "name": "Milk",' ' "command_topic": "test_topic" }' + data1 = '{ "name": "Beer", "command_topic": "test_topic" }' + data2 = '{ "name": "Milk", "command_topic": "test_topic" }' await help_test_discovery_update( hass, mqtt_mock, caplog, cover.DOMAIN, data1, data2 ) +async def test_discovery_update_unchanged_cover(hass, mqtt_mock, caplog): + """Test update of discovered cover.""" + data1 = '{ "name": "Beer", "command_topic": "test_topic" }' + with patch( + "homeassistant.components.mqtt.cover.MqttCover.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, cover.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" - data1 = '{ "name": "Beer",' ' "command_topic": "test_topic#" }' - data2 = '{ "name": "Milk",' ' "command_topic": "test_topic" }' + data1 = '{ "name": "Beer", "command_topic": "test_topic#" }' + data2 = '{ "name": "Milk", "command_topic": "test_topic" }' await help_test_discovery_broken( hass, mqtt_mock, caplog, cover.DOMAIN, data1, data2 ) diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 6114fe48ff4..e1801c5c15a 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -19,6 +19,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -33,6 +34,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import async_fire_mqtt_message from tests.components.fan import common @@ -689,22 +691,33 @@ async def test_unique_id(hass, mqtt_mock): async def test_discovery_removal_fan(hass, mqtt_mock, caplog): """Test removal of discovered fan.""" - data = '{ "name": "test",' ' "command_topic": "test_topic" }' + data = '{ "name": "test", "command_topic": "test_topic" }' await help_test_discovery_removal(hass, mqtt_mock, caplog, fan.DOMAIN, data) async def test_discovery_update_fan(hass, mqtt_mock, caplog): """Test update of discovered fan.""" - data1 = '{ "name": "Beer",' ' "command_topic": "test_topic" }' - data2 = '{ "name": "Milk",' ' "command_topic": "test_topic" }' + data1 = '{ "name": "Beer", "command_topic": "test_topic" }' + data2 = '{ "name": "Milk", "command_topic": "test_topic" }' await help_test_discovery_update(hass, mqtt_mock, caplog, fan.DOMAIN, data1, data2) +async def test_discovery_update_unchanged_fan(hass, mqtt_mock, caplog): + """Test update of discovered fan.""" + data1 = '{ "name": "Beer", "command_topic": "test_topic" }' + with patch( + "homeassistant.components.mqtt.fan.MqttFan.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, fan.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" data1 = '{ "name": "Beer" }' - data2 = '{ "name": "Milk",' ' "command_topic": "test_topic" }' + data2 = '{ "name": "Milk", "command_topic": "test_topic" }' await help_test_discovery_broken(hass, mqtt_mock, caplog, fan.DOMAIN, data1, data2) diff --git a/tests/components/mqtt/test_legacy_vacuum.py b/tests/components/mqtt/test_legacy_vacuum.py index 893c1b78f1e..aacea4e345e 100644 --- a/tests/components/mqtt/test_legacy_vacuum.py +++ b/tests/components/mqtt/test_legacy_vacuum.py @@ -31,6 +31,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -45,6 +46,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import async_fire_mqtt_message from tests.components.vacuum import common @@ -643,6 +645,17 @@ async def test_discovery_update_vacuum(hass, mqtt_mock, caplog): ) +async def test_discovery_update_unchanged_vacuum(hass, mqtt_mock, caplog): + """Test update of discovered vacuum.""" + data1 = '{ "name": "Beer", "command_topic": "test_topic" }' + with patch( + "homeassistant.components.mqtt.vacuum.schema_legacy.MqttVacuum.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, vacuum.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 5fa8fa181e5..75d3e694838 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -170,6 +170,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -1450,6 +1451,21 @@ async def test_discovery_update_light(hass, mqtt_mock, caplog): ) +async def test_discovery_update_unchanged_light(hass, mqtt_mock, caplog): + """Test update of discovered light.""" + data1 = ( + '{ "name": "Beer",' + ' "state_topic": "test_topic",' + ' "command_topic": "test_topic" }' + ) + with patch( + "homeassistant.components.mqtt.light.schema_basic.MqttLight.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, light.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 7bb3763654e..54292aeeb7b 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -110,6 +110,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -1179,6 +1180,22 @@ async def test_discovery_update_light(hass, mqtt_mock, caplog): ) +async def test_discovery_update_unchanged_light(hass, mqtt_mock, caplog): + """Test update of discovered light.""" + data1 = ( + '{ "name": "Beer",' + ' "schema": "json",' + ' "state_topic": "test_topic",' + ' "command_topic": "test_topic" }' + ) + with patch( + "homeassistant.components.mqtt.light.schema_json.MqttLightJson.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, light.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index f0e226d2095..17b3332da40 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -47,6 +47,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -923,6 +924,24 @@ async def test_discovery_update_light(hass, mqtt_mock, caplog): ) +async def test_discovery_update_unchanged_light(hass, mqtt_mock, caplog): + """Test update of discovered light.""" + data1 = ( + '{ "name": "Beer",' + ' "schema": "template",' + ' "state_topic": "test_topic",' + ' "command_topic": "test_topic",' + ' "command_on_template": "on",' + ' "command_off_template": "off"}' + ) + with patch( + "homeassistant.components.mqtt.light.schema_template.MqttLightTemplate.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, light.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index ff130077a95..cd37543d94e 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -20,6 +20,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -34,6 +35,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import async_fire_mqtt_message DEFAULT_CONFIG = { @@ -382,6 +384,21 @@ async def test_discovery_update_lock(hass, mqtt_mock, caplog): await help_test_discovery_update(hass, mqtt_mock, caplog, LOCK_DOMAIN, data1, data2) +async def test_discovery_update_unchanged_lock(hass, mqtt_mock, caplog): + """Test update of discovered lock.""" + data1 = ( + '{ "name": "Beer",' + ' "state_topic": "test_topic",' + ' "command_topic": "command_topic" }' + ) + with patch( + "homeassistant.components.mqtt.lock.MqttLock.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, LOCK_DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index 5ec5fccbe28..0d31b9f33f2 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -24,6 +24,7 @@ from .test_common import ( help_test_discovery_update, help_test_discovery_update_attr, help_test_discovery_update_availability, + help_test_discovery_update_unchanged, help_test_entity_debug_info, help_test_entity_debug_info_max_messages, help_test_entity_debug_info_message, @@ -425,24 +426,35 @@ async def test_unique_id(hass, mqtt_mock): async def test_discovery_removal_sensor(hass, mqtt_mock, caplog): """Test removal of discovered sensor.""" - data = '{ "name": "test",' ' "state_topic": "test_topic" }' + data = '{ "name": "test", "state_topic": "test_topic" }' await help_test_discovery_removal(hass, mqtt_mock, caplog, sensor.DOMAIN, data) async def test_discovery_update_sensor(hass, mqtt_mock, caplog): """Test update of discovered sensor.""" - data1 = '{ "name": "Beer",' ' "state_topic": "test_topic" }' - data2 = '{ "name": "Milk",' ' "state_topic": "test_topic" }' + data1 = '{ "name": "Beer", "state_topic": "test_topic" }' + data2 = '{ "name": "Milk", "state_topic": "test_topic" }' await help_test_discovery_update( hass, mqtt_mock, caplog, sensor.DOMAIN, data1, data2 ) +async def test_discovery_update_unchanged_sensor(hass, mqtt_mock, caplog): + """Test update of discovered sensor.""" + data1 = '{ "name": "Beer", "state_topic": "test_topic" }' + with patch( + "homeassistant.components.mqtt.sensor.MqttSensor.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, sensor.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" - data1 = '{ "name": "Beer",' ' "state_topic": "test_topic#" }' - data2 = '{ "name": "Milk",' ' "state_topic": "test_topic" }' + data1 = '{ "name": "Beer", "state_topic": "test_topic#" }' + data2 = '{ "name": "Milk", "state_topic": "test_topic" }' await help_test_discovery_broken( hass, mqtt_mock, caplog, sensor.DOMAIN, data1, data2 ) diff --git a/tests/components/mqtt/test_state_vacuum.py b/tests/components/mqtt/test_state_vacuum.py index c8ca7d3691b..fe410821395 100644 --- a/tests/components/mqtt/test_state_vacuum.py +++ b/tests/components/mqtt/test_state_vacuum.py @@ -41,6 +41,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -55,6 +56,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import async_fire_mqtt_message from tests.components.vacuum import common @@ -410,24 +412,35 @@ async def test_unique_id(hass, mqtt_mock): async def test_discovery_removal_vacuum(hass, mqtt_mock, caplog): """Test removal of discovered vacuum.""" - data = '{ "schema": "state", "name": "test",' ' "command_topic": "test_topic"}' + data = '{ "schema": "state", "name": "test", "command_topic": "test_topic"}' await help_test_discovery_removal(hass, mqtt_mock, caplog, vacuum.DOMAIN, data) async def test_discovery_update_vacuum(hass, mqtt_mock, caplog): """Test update of discovered vacuum.""" - data1 = '{ "schema": "state", "name": "Beer",' ' "command_topic": "test_topic"}' - data2 = '{ "schema": "state", "name": "Milk",' ' "command_topic": "test_topic"}' + data1 = '{ "schema": "state", "name": "Beer", "command_topic": "test_topic"}' + data2 = '{ "schema": "state", "name": "Milk", "command_topic": "test_topic"}' await help_test_discovery_update( hass, mqtt_mock, caplog, vacuum.DOMAIN, data1, data2 ) +async def test_discovery_update_unchanged_vacuum(hass, mqtt_mock, caplog): + """Test update of discovered vacuum.""" + data1 = '{ "schema": "state", "name": "Beer", "command_topic": "test_topic"}' + with patch( + "homeassistant.components.mqtt.vacuum.schema_state.MqttStateVacuum.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, vacuum.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" - data1 = '{ "schema": "state", "name": "Beer",' ' "command_topic": "test_topic#"}' - data2 = '{ "schema": "state", "name": "Milk",' ' "command_topic": "test_topic"}' + data1 = '{ "schema": "state", "name": "Beer", "command_topic": "test_topic#"}' + data2 = '{ "schema": "state", "name": "Milk", "command_topic": "test_topic"}' await help_test_discovery_broken( hass, mqtt_mock, caplog, vacuum.DOMAIN, data1, data2 ) diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index 869a413eb6b..a6edb8d6f14 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -15,6 +15,7 @@ from .test_common import ( help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_unchanged, help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, @@ -320,6 +321,21 @@ async def test_discovery_update_switch(hass, mqtt_mock, caplog): ) +async def test_discovery_update_unchanged_switch(hass, mqtt_mock, caplog): + """Test update of discovered switch.""" + data1 = ( + '{ "name": "Beer",' + ' "state_topic": "test_topic",' + ' "command_topic": "test_topic" }' + ) + with patch( + "homeassistant.components.mqtt.switch.MqttSwitch.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, switch.DOMAIN, data1, discovery_update + ) + + @pytest.mark.no_fail_on_log_exception async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" From a25f1cc6c354a32ec7bf1e5fbc084ca53f7645e7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 6 Aug 2020 08:32:53 +0200 Subject: [PATCH 05/77] Fix missing rfxtrx strings (#38570) * Fix missing rfxtrx strings * Clean --- homeassistant/components/rfxtrx/strings.json | 9 ++++++++- homeassistant/components/rfxtrx/translations/en.json | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/rfxtrx/translations/en.json diff --git a/homeassistant/components/rfxtrx/strings.json b/homeassistant/components/rfxtrx/strings.json index 7a73a41bfdf..e19265dec32 100644 --- a/homeassistant/components/rfxtrx/strings.json +++ b/homeassistant/components/rfxtrx/strings.json @@ -1,2 +1,9 @@ { -} \ No newline at end of file + "config": { + "step": {}, + "error": {}, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/homeassistant/components/rfxtrx/translations/en.json b/homeassistant/components/rfxtrx/translations/en.json new file mode 100644 index 00000000000..263b2a9467b --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/en.json @@ -0,0 +1,9 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + }, + "error": {}, + "step": {} + } +} From e287c9cf0833d33963b5830ce8eba9e5d2347bcd Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 6 Aug 2020 09:32:42 +0200 Subject: [PATCH 06/77] Revert "Add a timeout for async_add_entities (#38474)" (#38584) This reverts commit 7590af393077fbee840a7e91921717a4b699a553. --- homeassistant/helpers/entity_platform.py | 28 ++---------------- tests/helpers/test_entity_platform.py | 36 ------------------------ 2 files changed, 3 insertions(+), 61 deletions(-) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 5f6d2349ec7..7a581dbd19e 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -1,6 +1,5 @@ """Class to manage the entities for a single platform.""" import asyncio -from contextlib import suppress from contextvars import ContextVar from datetime import datetime, timedelta from logging import Logger @@ -24,8 +23,6 @@ if TYPE_CHECKING: SLOW_SETUP_WARNING = 10 SLOW_SETUP_MAX_WAIT = 60 -SLOW_ADD_ENTITIES_MAX_WAIT = 60 - PLATFORM_NOT_READY_RETRIES = 10 DATA_ENTITY_PLATFORM = "entity_platform" PLATFORM_NOT_READY_BASE_WAIT_TIME = 30 # seconds @@ -285,10 +282,8 @@ class EntityPlatform: device_registry = await hass.helpers.device_registry.async_get_registry() entity_registry = await hass.helpers.entity_registry.async_get_registry() tasks = [ - asyncio.create_task( - self._async_add_entity( # type: ignore - entity, update_before_add, entity_registry, device_registry - ) + self._async_add_entity( # type: ignore + entity, update_before_add, entity_registry, device_registry ) for entity in new_entities ] @@ -297,24 +292,7 @@ class EntityPlatform: if not tasks: return - await asyncio.wait(tasks, timeout=SLOW_ADD_ENTITIES_MAX_WAIT) - - for idx, entity in enumerate(new_entities): - task = tasks[idx] - if task.done(): - await task - continue - - self.logger.warning( - "Timed out adding entity %s for domain %s with platform %s after %ds.", - entity.entity_id, - self.domain, - self.platform_name, - SLOW_ADD_ENTITIES_MAX_WAIT, - ) - task.cancel() - with suppress(asyncio.CancelledError): - await task + await asyncio.gather(*tasks) if self._async_unsub_polling is not None or not any( entity.should_poll for entity in self.entities.values() diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 3de68dca4c2..5912eb42b03 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -931,39 +931,3 @@ async def test_invalid_entity_id(hass): await platform.async_add_entities([entity]) assert entity.hass is None assert entity.platform is None - - -class MockBlockingEntity(MockEntity): - """Class to mock an entity that will block adding entities.""" - - async def async_added_to_hass(self): - """Block for a long time.""" - await asyncio.sleep(1000) - - -async def test_setup_entry_with_entities_that_block_forever(hass, caplog): - """Test we cancel adding entities when we reach the timeout.""" - registry = mock_registry(hass) - - async def async_setup_entry(hass, config_entry, async_add_entities): - """Mock setup entry method.""" - async_add_entities([MockBlockingEntity(name="test1", unique_id="unique")]) - return True - - platform = MockPlatform(async_setup_entry=async_setup_entry) - config_entry = MockConfigEntry(entry_id="super-mock-id") - mock_entity_platform = MockEntityPlatform( - hass, platform_name=config_entry.domain, platform=platform - ) - - with patch.object(entity_platform, "SLOW_ADD_ENTITIES_MAX_WAIT", 0.01): - assert await mock_entity_platform.async_setup_entry(config_entry) - await hass.async_block_till_done() - full_name = f"{mock_entity_platform.domain}.{config_entry.domain}" - assert full_name in hass.config.components - assert len(hass.states.async_entity_ids()) == 0 - assert len(registry.entities) == 1 - assert "Timed out adding entity" in caplog.text - assert "test_domain.test1" in caplog.text - assert "test_domain" in caplog.text - assert "test" in caplog.text From 9713b5806f4bd2a2f1b9822c8065c4fafdd269cc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 6 Aug 2020 12:36:59 +0200 Subject: [PATCH 07/77] Do not print warning when command line switch queries off (#38591) --- homeassistant/components/command_line/__init__.py | 11 ++++++++--- homeassistant/components/command_line/switch.py | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/command_line/__init__.py b/homeassistant/components/command_line/__init__.py index 92f219a13ea..4f98818d9b3 100644 --- a/homeassistant/components/command_line/__init__.py +++ b/homeassistant/components/command_line/__init__.py @@ -6,8 +6,12 @@ import subprocess _LOGGER = logging.getLogger(__name__) -def call_shell_with_timeout(command, timeout): - """Run a shell command with a timeout.""" +def call_shell_with_timeout(command, timeout, *, log_return_code=True): + """Run a shell command with a timeout. + + If log_return_code is set to False, it will not print an error if a non-zero + return code is returned. + """ try: _LOGGER.debug("Running command: %s", command) subprocess.check_output( @@ -15,7 +19,8 @@ def call_shell_with_timeout(command, timeout): ) return 0 except subprocess.CalledProcessError as proc_exception: - _LOGGER.error("Command failed: %s", command) + if log_return_code: + _LOGGER.error("Command failed: %s", command) return proc_exception.returncode except subprocess.TimeoutExpired: _LOGGER.error("Timeout for command: %s", command) diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index 50cda31a537..804e3c6a4d5 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -114,7 +114,9 @@ class CommandSwitch(SwitchEntity): def _query_state_code(self, command): """Execute state command for return code.""" _LOGGER.info("Running state code command: %s", command) - return call_shell_with_timeout(command, self._timeout) == 0 + return ( + call_shell_with_timeout(command, self._timeout, log_return_code=False) == 0 + ) @property def should_poll(self): From ec14bf215ac46621ca563a5200ad1091b6446ec2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 6 Aug 2020 10:58:06 +0000 Subject: [PATCH 08/77] Bumped version to 0.114.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index f6b54465239..2b893eb84fe 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "0b0" +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From f0f112ff4219d1ac18137568252170c80f23fce3 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Fri, 7 Aug 2020 02:56:28 -0400 Subject: [PATCH 09/77] Upgrade to TensorFlow 2 (#38384) Co-authored-by: Paulus Schoutsen Co-authored-by: Martin Hjelmare Co-authored-by: Franck Nijhof --- azure-pipelines-wheels.yml | 1 + .../components/tensorflow/image_processing.py | 139 ++++++++++++------ .../components/tensorflow/manifest.json | 7 +- pylintrc | 2 +- requirements_all.txt | 13 +- script/gen_requirements_all.py | 1 + 6 files changed, 115 insertions(+), 48 deletions(-) diff --git a/azure-pipelines-wheels.yml b/azure-pipelines-wheels.yml index c8943595429..ebe704f12e2 100644 --- a/azure-pipelines-wheels.yml +++ b/azure-pipelines-wheels.yml @@ -89,5 +89,6 @@ jobs: sed -i "s|# py_noaa|py_noaa|g" ${requirement_file} sed -i "s|# bme680|bme680|g" ${requirement_file} sed -i "s|# python-gammu|python-gammu|g" ${requirement_file} + sed -i "s|# tf-models-official|tf-models-official|g" ${requirement_file} done displayName: 'Prepare requirements files for Home Assistant wheels' diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index f4eb5342c46..d6d20c63f56 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -3,9 +3,11 @@ import io import logging import os import sys +import time from PIL import Image, ImageDraw, UnidentifiedImageError import numpy as np +import tensorflow as tf import voluptuous as vol from homeassistant.components.image_processing import ( @@ -16,16 +18,21 @@ from homeassistant.components.image_processing import ( PLATFORM_SCHEMA, ImageProcessingEntity, ) +from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.core import split_entity_id from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv from homeassistant.util.pil import draw_box +os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" + +DOMAIN = "tensorflow" _LOGGER = logging.getLogger(__name__) ATTR_MATCHES = "matches" ATTR_SUMMARY = "summary" ATTR_TOTAL_MATCHES = "total_matches" +ATTR_PROCESS_TIME = "process_time" CONF_AREA = "area" CONF_BOTTOM = "bottom" @@ -34,6 +41,7 @@ CONF_CATEGORY = "category" CONF_FILE_OUT = "file_out" CONF_GRAPH = "graph" CONF_LABELS = "labels" +CONF_LABEL_OFFSET = "label_offset" CONF_LEFT = "left" CONF_MODEL = "model" CONF_MODEL_DIR = "model_dir" @@ -58,12 +66,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_FILE_OUT, default=[]): vol.All(cv.ensure_list, [cv.template]), vol.Required(CONF_MODEL): vol.Schema( { - vol.Required(CONF_GRAPH): cv.isfile, + vol.Required(CONF_GRAPH): cv.isdir, vol.Optional(CONF_AREA): AREA_SCHEMA, vol.Optional(CONF_CATEGORIES, default=[]): vol.All( cv.ensure_list, [vol.Any(cv.string, CATEGORY_SCHEMA)] ), vol.Optional(CONF_LABELS): cv.isfile, + vol.Optional(CONF_LABEL_OFFSET, default=1): int, vol.Optional(CONF_MODEL_DIR): cv.isdir, } ), @@ -71,17 +80,40 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) +def get_model_detection_function(model): + """Get a tf.function for detection.""" + + @tf.function + def detect_fn(image): + """Detect objects in image.""" + + image, shapes = model.preprocess(image) + prediction_dict = model.predict(image, shapes) + detections = model.postprocess(prediction_dict, shapes) + + return detections + + return detect_fn + + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the TensorFlow image processing platform.""" - model_config = config.get(CONF_MODEL) + model_config = config[CONF_MODEL] model_dir = model_config.get(CONF_MODEL_DIR) or hass.config.path("tensorflow") labels = model_config.get(CONF_LABELS) or hass.config.path( "tensorflow", "object_detection", "data", "mscoco_label_map.pbtxt" ) + checkpoint = os.path.join(model_config[CONF_GRAPH], "checkpoint") + pipeline_config = os.path.join(model_config[CONF_GRAPH], "pipeline.config") # Make sure locations exist - if not os.path.isdir(model_dir) or not os.path.exists(labels): - _LOGGER.error("Unable to locate tensorflow models or label map") + if ( + not os.path.isdir(model_dir) + or not os.path.isdir(checkpoint) + or not os.path.exists(pipeline_config) + or not os.path.exists(labels) + ): + _LOGGER.error("Unable to locate tensorflow model or label map") return # append custom model path to sys.path @@ -89,18 +121,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: # Verify that the TensorFlow Object Detection API is pre-installed - os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" # These imports shouldn't be moved to the top, because they depend on code from the model_dir. # (The model_dir is created during the manual setup process. See integration docs.) - import tensorflow as tf # pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel - from object_detection.utils import label_map_util + from object_detection.utils import config_util, label_map_util + from object_detection.builders import model_builder except ImportError: _LOGGER.error( "No TensorFlow Object Detection library found! Install or compile " "for your system following instructions here: " - "https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/installation.md" + "https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2.md#installation" ) return @@ -113,22 +144,45 @@ def setup_platform(hass, config, add_entities, discovery_info=None): "PIL at reduced resolution" ) - # Set up Tensorflow graph, session, and label map to pass to processor - # pylint: disable=no-member - detection_graph = tf.Graph() - with detection_graph.as_default(): - od_graph_def = tf.GraphDef() - with tf.gfile.GFile(model_config.get(CONF_GRAPH), "rb") as fid: - serialized_graph = fid.read() - od_graph_def.ParseFromString(serialized_graph) - tf.import_graph_def(od_graph_def, name="") + hass.data[DOMAIN] = {CONF_MODEL: None} - session = tf.Session(graph=detection_graph) - label_map = label_map_util.load_labelmap(labels) - categories = label_map_util.convert_label_map_to_categories( - label_map, max_num_classes=90, use_display_name=True + def tensorflow_hass_start(_event): + """Set up TensorFlow model on hass start.""" + start = time.perf_counter() + + # Load pipeline config and build a detection model + pipeline_configs = config_util.get_configs_from_pipeline_file(pipeline_config) + detection_model = model_builder.build( + model_config=pipeline_configs["model"], is_training=False + ) + + # Restore checkpoint + ckpt = tf.compat.v2.train.Checkpoint(model=detection_model) + ckpt.restore(os.path.join(checkpoint, "ckpt-0")).expect_partial() + + _LOGGER.debug( + "Model checkpoint restore took %d seconds", time.perf_counter() - start + ) + + model = get_model_detection_function(detection_model) + + # Preload model cache with empty image tensor + inp = np.zeros([2160, 3840, 3], dtype=np.uint8) + # The input needs to be a tensor, convert it using `tf.convert_to_tensor`. + input_tensor = tf.convert_to_tensor(inp, dtype=tf.float32) + # The model expects a batch of images, so add an axis with `tf.newaxis`. + input_tensor = input_tensor[tf.newaxis, ...] + # Run inference + model(input_tensor) + + _LOGGER.debug("Model load took %d seconds", time.perf_counter() - start) + hass.data[DOMAIN][CONF_MODEL] = model + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, tensorflow_hass_start) + + category_index = label_map_util.create_category_index_from_labelmap( + labels, use_display_name=True ) - category_index = label_map_util.create_category_index(categories) entities = [] @@ -138,8 +192,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass, camera[CONF_ENTITY_ID], camera.get(CONF_NAME), - session, - detection_graph, category_index, config, ) @@ -152,14 +204,7 @@ class TensorFlowImageProcessor(ImageProcessingEntity): """Representation of an TensorFlow image processor.""" def __init__( - self, - hass, - camera_entity, - name, - session, - detection_graph, - category_index, - config, + self, hass, camera_entity, name, category_index, config, ): """Initialize the TensorFlow entity.""" model_config = config.get(CONF_MODEL) @@ -169,13 +214,12 @@ class TensorFlowImageProcessor(ImageProcessingEntity): self._name = name else: self._name = "TensorFlow {}".format(split_entity_id(camera_entity)[1]) - self._session = session - self._graph = detection_graph self._category_index = category_index self._min_confidence = config.get(CONF_CONFIDENCE) self._file_out = config.get(CONF_FILE_OUT) # handle categories and specific detection areas + self._label_id_offset = model_config.get(CONF_LABEL_OFFSET) categories = model_config.get(CONF_CATEGORIES) self._include_categories = [] self._category_areas = {} @@ -212,6 +256,7 @@ class TensorFlowImageProcessor(ImageProcessingEntity): self._matches = {} self._total_matches = 0 self._last_image = None + self._process_time = 0 @property def camera_entity(self): @@ -237,6 +282,7 @@ class TensorFlowImageProcessor(ImageProcessingEntity): category: len(values) for category, values in self._matches.items() }, ATTR_TOTAL_MATCHES: self._total_matches, + ATTR_PROCESS_TIME: self._process_time, } def _save_image(self, image, matches, paths): @@ -281,10 +327,16 @@ class TensorFlowImageProcessor(ImageProcessingEntity): def process_image(self, image): """Process the image.""" + model = self.hass.data[DOMAIN][CONF_MODEL] + if not model: + _LOGGER.debug("Model not yet ready.") + return + start = time.perf_counter() try: import cv2 # pylint: disable=import-error, import-outside-toplevel + # pylint: disable=no-member img = cv2.imdecode(np.asarray(bytearray(image)), cv2.IMREAD_UNCHANGED) inp = img[:, :, [2, 1, 0]] # BGR->RGB inp_expanded = inp.reshape(1, inp.shape[0], inp.shape[1], 3) @@ -303,15 +355,15 @@ class TensorFlowImageProcessor(ImageProcessingEntity): ) inp_expanded = np.expand_dims(inp, axis=0) - image_tensor = self._graph.get_tensor_by_name("image_tensor:0") - boxes = self._graph.get_tensor_by_name("detection_boxes:0") - scores = self._graph.get_tensor_by_name("detection_scores:0") - classes = self._graph.get_tensor_by_name("detection_classes:0") - boxes, scores, classes = self._session.run( - [boxes, scores, classes], feed_dict={image_tensor: inp_expanded} - ) - boxes, scores, classes = map(np.squeeze, [boxes, scores, classes]) - classes = classes.astype(int) + # The input needs to be a tensor, convert it using `tf.convert_to_tensor`. + input_tensor = tf.convert_to_tensor(inp_expanded, dtype=tf.float32) + + detections = model(input_tensor) + boxes = detections["detection_boxes"][0].numpy() + scores = detections["detection_scores"][0].numpy() + classes = ( + detections["detection_classes"][0].numpy() + self._label_id_offset + ).astype(int) matches = {} total_matches = 0 @@ -367,3 +419,4 @@ class TensorFlowImageProcessor(ImageProcessingEntity): self._matches = matches self._total_matches = total_matches + self._process_time = time.perf_counter() - start diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index b74633d36d4..f9ee39ec7be 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -3,9 +3,12 @@ "name": "TensorFlow", "documentation": "https://www.home-assistant.io/integrations/tensorflow", "requirements": [ - "tensorflow==1.13.2", + "tensorflow==2.2.0", + "tf-slim==1.1.0", + "tf-models-official==2.2.1", + "pycocotools==2.0.1", "numpy==1.19.0", - "protobuf==3.6.1", + "protobuf==3.12.2", "pillow==7.1.2" ], "codeowners": [] diff --git a/pylintrc b/pylintrc index df53c2f67a2..f2860026cd8 100644 --- a/pylintrc +++ b/pylintrc @@ -5,7 +5,7 @@ ignore=tests jobs=2 load-plugins=pylint_strict_informational persistent=no -extension-pkg-whitelist=ciso8601 +extension-pkg-whitelist=ciso8601,cv2 [BASIC] good-names=id,i,j,k,ex,Run,_,fp,T,ev diff --git a/requirements_all.txt b/requirements_all.txt index a18e41e096b..0b25ccc63c6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1120,7 +1120,7 @@ proliphix==0.4.1 prometheus_client==0.7.1 # homeassistant.components.tensorflow -protobuf==3.6.1 +protobuf==3.12.2 # homeassistant.components.proxmoxve proxmoxer==1.1.1 @@ -1261,6 +1261,9 @@ pychromecast==7.2.0 # homeassistant.components.cmus pycmus==0.1.1 +# homeassistant.components.tensorflow +pycocotools==2.0.1 + # homeassistant.components.comfoconnect pycomfoconnect==0.3 @@ -2098,7 +2101,7 @@ temescal==0.1 temperusb==1.5.3 # homeassistant.components.tensorflow -# tensorflow==1.13.2 +# tensorflow==2.2.0 # homeassistant.components.powerwall tesla-powerwall==0.2.12 @@ -2106,6 +2109,12 @@ tesla-powerwall==0.2.12 # homeassistant.components.tesla teslajsonpy==0.10.1 +# homeassistant.components.tensorflow +# tf-models-official==2.2.1 + +# homeassistant.components.tensorflow +tf-slim==1.1.0 + # homeassistant.components.thermoworks_smoke thermoworks_smoke==0.1.8 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 4625924da29..772b9af5034 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -41,6 +41,7 @@ COMMENT_REQUIREMENTS = ( "RPi.GPIO", "smbus-cffi", "tensorflow", + "tf-models-official", "VL53L1X2", ) From 93cdd4dbf3724013cf2990e9f8a446644155a1af Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 6 Aug 2020 19:43:42 +0100 Subject: [PATCH 10/77] Improve the OVO Energy integration (#38598) Co-authored-by: Martin Hjelmare --- .../components/ovo_energy/config_flow.py | 70 ++++++++----------- .../components/ovo_energy/manifest.json | 1 - .../components/ovo_energy/strings.json | 29 ++++---- .../ovo_energy/translations/en.json | 29 ++++---- 4 files changed, 60 insertions(+), 69 deletions(-) diff --git a/homeassistant/components/ovo_energy/config_flow.py b/homeassistant/components/ovo_energy/config_flow.py index e4d33865f57..ac3e8371123 100644 --- a/homeassistant/components/ovo_energy/config_flow.py +++ b/homeassistant/components/ovo_energy/config_flow.py @@ -9,58 +9,48 @@ from homeassistant import config_entries from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from .const import CONF_ACCOUNT_ID, DOMAIN +from .const import CONF_ACCOUNT_ID, DOMAIN # pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) +USER_SCHEMA = vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} +) -@config_entries.HANDLERS.register(DOMAIN) -class OVOEnergyFlowHandler(ConfigFlow): + +class OVOEnergyFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a OVO Energy config flow.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - def __init__(self): - """Initialize OVO Energy flow.""" - - async def _show_setup_form(self, errors=None): - """Show the setup form to the user.""" - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} - ), - errors=errors or {}, - ) - async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" - if user_input is None: - return await self._show_setup_form() - errors = {} - - client = OVOEnergy() - - try: - if ( - await client.authenticate( - user_input.get(CONF_USERNAME), user_input.get(CONF_PASSWORD) + if user_input is not None: + client = OVOEnergy() + try: + authenticated = await client.authenticate( + user_input[CONF_USERNAME], user_input[CONF_PASSWORD] ) - is not True - ): - errors["base"] = "authorization_error" - return await self._show_setup_form(errors) - except aiohttp.ClientError: - errors["base"] = "connection_error" - return await self._show_setup_form(errors) + except aiohttp.ClientError: + errors["base"] = "connection_error" + else: + if authenticated: + await self.async_set_unique_id(user_input[CONF_USERNAME]) + self._abort_if_unique_id_configured() - return self.async_create_entry( - title=client.account_id, - data={ - CONF_USERNAME: user_input.get(CONF_USERNAME), - CONF_PASSWORD: user_input.get(CONF_PASSWORD), - CONF_ACCOUNT_ID: client.account_id, - }, + return self.async_create_entry( + title=client.account_id, + data={ + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_ACCOUNT_ID: client.account_id, + }, + ) + + errors["base"] = "authorization_error" + + return self.async_show_form( + step_id="user", data_schema=USER_SCHEMA, errors=errors ) diff --git a/homeassistant/components/ovo_energy/manifest.json b/homeassistant/components/ovo_energy/manifest.json index 27a28863405..2da08d3339b 100644 --- a/homeassistant/components/ovo_energy/manifest.json +++ b/homeassistant/components/ovo_energy/manifest.json @@ -4,6 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ovo_energy", "requirements": ["ovoenergy==1.1.6"], - "dependencies": [], "codeowners": ["@timmo001"] } diff --git a/homeassistant/components/ovo_energy/strings.json b/homeassistant/components/ovo_energy/strings.json index a98b0223644..0132f3582b6 100644 --- a/homeassistant/components/ovo_energy/strings.json +++ b/homeassistant/components/ovo_energy/strings.json @@ -1,18 +1,19 @@ { - "config": { - "error": { - "authorization_error": "Authorization error. Check your credentials.", - "connection_error": "Could not connect to OVO Energy." - }, - "step": { - "user": { - "data": { - "username": "[%key:common::config_flow::data::username%]", - "password": "[%key:common::config_flow::data::password%]" + "config": { + "error": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "authorization_error": "Authorization error. Check your credentials.", + "connection_error": "[%key:common::config_flow::error::cannot_connect%]" }, - "description": "Set up an OVO Energy instance to access your energy usage.", - "title": "Add OVO Energy" - } + "step": { + "user": { + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + }, + "description": "Set up an OVO Energy instance to access your energy usage.", + "title": "Add OVO Energy Account" + } + } } - } } diff --git a/homeassistant/components/ovo_energy/translations/en.json b/homeassistant/components/ovo_energy/translations/en.json index afe1bb6e301..0132f3582b6 100644 --- a/homeassistant/components/ovo_energy/translations/en.json +++ b/homeassistant/components/ovo_energy/translations/en.json @@ -1,18 +1,19 @@ { - "config": { - "error": { - "authorization_error": "Authorization error. Check your credentials.", - "connection_error": "Could not connect to OVO Energy." - }, - "step": { - "user": { - "data": { - "username": "Username", - "password": "Password" + "config": { + "error": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "authorization_error": "Authorization error. Check your credentials.", + "connection_error": "[%key:common::config_flow::error::cannot_connect%]" }, - "description": "Set up an OVO Energy instance to access your energy usage.", - "title": "Add OVO Energy" - } + "step": { + "user": { + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + }, + "description": "Set up an OVO Energy instance to access your energy usage.", + "title": "Add OVO Energy Account" + } + } } - } } From 4fc56cec1cd1eb23f745e35c3857a0c509b65013 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 7 Aug 2020 08:36:38 +0200 Subject: [PATCH 11/77] V2 timeout for async_add_entities (#38601) Co-authored-by: Martin Hjelmare Co-authored-by: J. Nick Koston Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/entity_platform.py | 15 +++++++++- tests/helpers/test_entity_platform.py | 38 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 7a581dbd19e..6d9a1275b06 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -23,6 +23,9 @@ if TYPE_CHECKING: SLOW_SETUP_WARNING = 10 SLOW_SETUP_MAX_WAIT = 60 +SLOW_ADD_ENTITY_MAX_WAIT = 10 # Per Entity +SLOW_ADD_MIN_TIMEOUT = 60 + PLATFORM_NOT_READY_RETRIES = 10 DATA_ENTITY_PLATFORM = "entity_platform" PLATFORM_NOT_READY_BASE_WAIT_TIME = 30 # seconds @@ -292,7 +295,17 @@ class EntityPlatform: if not tasks: return - await asyncio.gather(*tasks) + timeout = max(SLOW_ADD_ENTITY_MAX_WAIT * len(tasks), SLOW_ADD_MIN_TIMEOUT) + try: + async with self.hass.timeout.async_timeout(timeout, self.domain): + await asyncio.gather(*tasks) + except asyncio.TimeoutError: + self.logger.warning( + "Timed out adding entities for domain %s with platform %s after %ds", + self.domain, + self.platform_name, + timeout, + ) if self._async_unsub_polling is not None or not any( entity.should_poll for entity in self.entities.values() diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 5912eb42b03..6d03b087151 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -931,3 +931,41 @@ async def test_invalid_entity_id(hass): await platform.async_add_entities([entity]) assert entity.hass is None assert entity.platform is None + + +class MockBlockingEntity(MockEntity): + """Class to mock an entity that will block adding entities.""" + + async def async_added_to_hass(self): + """Block for a long time.""" + await asyncio.sleep(1000) + + +async def test_setup_entry_with_entities_that_block_forever(hass, caplog): + """Test we cancel adding entities when we reach the timeout.""" + registry = mock_registry(hass) + + async def async_setup_entry(hass, config_entry, async_add_entities): + """Mock setup entry method.""" + async_add_entities([MockBlockingEntity(name="test1", unique_id="unique")]) + return True + + platform = MockPlatform(async_setup_entry=async_setup_entry) + config_entry = MockConfigEntry(entry_id="super-mock-id") + mock_entity_platform = MockEntityPlatform( + hass, platform_name=config_entry.domain, platform=platform + ) + + with patch.object(entity_platform, "SLOW_ADD_ENTITY_MAX_WAIT", 0.01), patch.object( + entity_platform, "SLOW_ADD_MIN_TIMEOUT", 0.01 + ): + assert await mock_entity_platform.async_setup_entry(config_entry) + await hass.async_block_till_done() + full_name = f"{mock_entity_platform.domain}.{config_entry.domain}" + assert full_name in hass.config.components + assert len(hass.states.async_entity_ids()) == 0 + assert len(registry.entities) == 1 + assert "Timed out adding entities" in caplog.text + assert "test_domain.test1" in caplog.text + assert "test_domain" in caplog.text + assert "test" in caplog.text From 790a136c0a82cb1d0d392cb2dbc63c52789a6f58 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Aug 2020 22:26:43 -0500 Subject: [PATCH 12/77] Ensure homekit pairing barcode is usable on dark themes (#38609) --- homeassistant/components/homekit/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 201a0529f82..2199371c00d 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -354,7 +354,7 @@ def show_setup_message(hass, entry_id, bridge_name, pincode, uri): buffer = io.BytesIO() url = pyqrcode.create(uri) - url.svg(buffer, scale=5) + url.svg(buffer, scale=5, module_color="#000", background="#FFF") pairing_secret = secrets.token_hex(32) hass.data[DOMAIN][entry_id][HOMEKIT_PAIRING_QR] = buffer.getvalue() From 6baded622b5cb945c5331cba3d60982c3e4f3287 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 7 Aug 2020 08:17:00 +0200 Subject: [PATCH 13/77] Handle unavailable input_select in Google Assistant (#38611) --- .../components/google_assistant/trait.py | 77 ++++++++++--------- .../google_assistant/test_smart_home.py | 2 - .../components/google_assistant/test_trait.py | 5 ++ 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 90b5016260d..36863fd86c8 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1267,46 +1267,49 @@ class ModesTrait(_Trait): return features & media_player.SUPPORT_SELECT_SOUND_MODE + def _generate(self, name, settings): + """Generate a list of modes.""" + mode = { + "name": name, + "name_values": [ + {"name_synonym": self.SYNONYMS.get(name, [name]), "lang": "en"} + ], + "settings": [], + "ordered": False, + } + for setting in settings: + mode["settings"].append( + { + "setting_name": setting, + "setting_values": [ + { + "setting_synonym": self.SYNONYMS.get(setting, [setting]), + "lang": "en", + } + ], + } + ) + return mode + def sync_attributes(self): """Return mode attributes for a sync request.""" - - def _generate(name, settings): - mode = { - "name": name, - "name_values": [ - {"name_synonym": self.SYNONYMS.get(name, [name]), "lang": "en"} - ], - "settings": [], - "ordered": False, - } - for setting in settings: - mode["settings"].append( - { - "setting_name": setting, - "setting_values": [ - { - "setting_synonym": self.SYNONYMS.get( - setting, [setting] - ), - "lang": "en", - } - ], - } - ) - return mode - - attrs = self.state.attributes modes = [] - if self.state.domain == media_player.DOMAIN: - if media_player.ATTR_SOUND_MODE_LIST in attrs: - modes.append( - _generate("sound mode", attrs[media_player.ATTR_SOUND_MODE_LIST]) - ) - elif self.state.domain == input_select.DOMAIN: - modes.append(_generate("option", attrs[input_select.ATTR_OPTIONS])) - elif self.state.domain == humidifier.DOMAIN: - if humidifier.ATTR_AVAILABLE_MODES in attrs: - modes.append(_generate("mode", attrs[humidifier.ATTR_AVAILABLE_MODES])) + + for domain, attr, name in ( + (media_player.DOMAIN, media_player.ATTR_SOUND_MODE_LIST, "sound mode"), + (input_select.DOMAIN, input_select.ATTR_OPTIONS, "option"), + (humidifier.DOMAIN, humidifier.ATTR_AVAILABLE_MODES, "mode"), + ): + if self.state.domain != domain: + continue + + items = self.state.attributes.get(attr) + + if items is not None: + modes.append(self._generate(name, items)) + + # Shortcut since all domains are currently unique + break payload = {"availableModes": modes} diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index e9795a9320f..6cd99d1fdd1 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -337,8 +337,6 @@ async def test_execute(hass): const.SOURCE_CLOUD, ) - print(result) - assert result == { "requestId": REQ_ID, "payload": { diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index faad53fbc66..854e040119d 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1433,6 +1433,11 @@ async def test_modes_input_select(hass): assert helpers.get_google_type(input_select.DOMAIN, None) is not None assert trait.ModesTrait.supported(input_select.DOMAIN, None, None) + trt = trait.ModesTrait( + hass, State("input_select.bla", "unavailable"), BASIC_CONFIG, + ) + assert trt.sync_attributes() == {"availableModes": []} + trt = trait.ModesTrait( hass, State( From 64749a0f8597f16b6b0a3ad2573ff86edb409ee7 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 7 Aug 2020 08:37:10 +0200 Subject: [PATCH 14/77] Bump OpenCV 4.3.0 and Numpy 1.19.1 (#38616) --- homeassistant/components/iqvia/manifest.json | 2 +- homeassistant/components/opencv/manifest.json | 2 +- homeassistant/components/tensorflow/manifest.json | 2 +- homeassistant/components/trend/manifest.json | 2 +- requirements_all.txt | 4 ++-- requirements_test_all.txt | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/iqvia/manifest.json b/homeassistant/components/iqvia/manifest.json index 5d880888ef5..1f862bb1bbf 100644 --- a/homeassistant/components/iqvia/manifest.json +++ b/homeassistant/components/iqvia/manifest.json @@ -3,6 +3,6 @@ "name": "IQVIA", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/iqvia", - "requirements": ["numpy==1.19.0", "pyiqvia==0.2.1"], + "requirements": ["numpy==1.19.1", "pyiqvia==0.2.1"], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/opencv/manifest.json b/homeassistant/components/opencv/manifest.json index ed8fd9c662c..1fb7096d5fa 100644 --- a/homeassistant/components/opencv/manifest.json +++ b/homeassistant/components/opencv/manifest.json @@ -2,6 +2,6 @@ "domain": "opencv", "name": "OpenCV", "documentation": "https://www.home-assistant.io/integrations/opencv", - "requirements": ["numpy==1.19.0", "opencv-python-headless==4.2.0.32"], + "requirements": ["numpy==1.19.1", "opencv-python-headless==4.3.0.36"], "codeowners": [] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index f9ee39ec7be..fc87b5cdbff 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-slim==1.1.0", "tf-models-official==2.2.1", "pycocotools==2.0.1", - "numpy==1.19.0", + "numpy==1.19.1", "protobuf==3.12.2", "pillow==7.1.2" ], diff --git a/homeassistant/components/trend/manifest.json b/homeassistant/components/trend/manifest.json index dabeabd2757..a43c2bb0cce 100644 --- a/homeassistant/components/trend/manifest.json +++ b/homeassistant/components/trend/manifest.json @@ -2,7 +2,7 @@ "domain": "trend", "name": "Trend", "documentation": "https://www.home-assistant.io/integrations/trend", - "requirements": ["numpy==1.19.0"], + "requirements": ["numpy==1.19.1"], "codeowners": [], "quality_scale": "internal" } diff --git a/requirements_all.txt b/requirements_all.txt index 0b25ccc63c6..b15a653f05f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -981,7 +981,7 @@ numato-gpio==0.8.0 # homeassistant.components.opencv # homeassistant.components.tensorflow # homeassistant.components.trend -numpy==1.19.0 +numpy==1.19.1 # homeassistant.components.oasa_telematics oasatelematics==0.3 @@ -1002,7 +1002,7 @@ onvif-zeep-async==0.4.0 open-garage==0.1.4 # homeassistant.components.opencv -# opencv-python-headless==4.2.0.32 +# opencv-python-headless==4.3.0.36 # homeassistant.components.openerz openerz-api==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 814b422ee7d..c38f78a2877 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -461,7 +461,7 @@ numato-gpio==0.8.0 # homeassistant.components.opencv # homeassistant.components.tensorflow # homeassistant.components.trend -numpy==1.19.0 +numpy==1.19.1 # homeassistant.components.google oauth2client==4.0.0 From 6e31a2e67d9bc2bb59bd4ef8b1a1ffd4a6e9021a Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Thu, 6 Aug 2020 18:47:39 -0400 Subject: [PATCH 15/77] Expose video doorbell button state to HomeKit (#38617) --- homeassistant/components/homekit/const.py | 1 + homeassistant/components/homekit/type_cameras.py | 11 +++++++++++ tests/components/homekit/test_type_cameras.py | 13 +++++++++++++ 3 files changed, 25 insertions(+) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index e38b86a7032..d8eec057191 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -129,6 +129,7 @@ SERV_OUTLET = "Outlet" SERV_SECURITY_SYSTEM = "SecuritySystem" SERV_SMOKE_SENSOR = "SmokeSensor" SERV_SPEAKER = "Speaker" +SERV_STATELESS_PROGRAMMABLE_SWITCH = "StatelessProgrammableSwitch" SERV_SWITCH = "Switch" SERV_TELEVISION = "Television" SERV_TELEVISION_SPEAKER = "TelevisionSpeaker" diff --git a/homeassistant/components/homekit/type_cameras.py b/homeassistant/components/homekit/type_cameras.py index 93b822f9e7a..91b13a93eca 100644 --- a/homeassistant/components/homekit/type_cameras.py +++ b/homeassistant/components/homekit/type_cameras.py @@ -54,6 +54,7 @@ from .const import ( SERV_DOORBELL, SERV_MOTION_SENSOR, SERV_SPEAKER, + SERV_STATELESS_PROGRAMMABLE_SWITCH, ) from .img_util import scale_jpeg_camera_image from .util import pid_is_alive @@ -211,6 +212,7 @@ class Camera(HomeAccessory, PyhapCamera): self._async_update_motion_state(state) self._char_doorbell_detected = None + self._char_doorbell_detected_switch = None self.linked_doorbell_sensor = self.config.get(CONF_LINKED_DOORBELL_SENSOR) if self.linked_doorbell_sensor: state = self.hass.states.get(self.linked_doorbell_sensor) @@ -220,6 +222,14 @@ class Camera(HomeAccessory, PyhapCamera): self._char_doorbell_detected = serv_doorbell.configure_char( CHAR_PROGRAMMABLE_SWITCH_EVENT, value=0, ) + serv_stateless_switch = self.add_preload_service( + SERV_STATELESS_PROGRAMMABLE_SWITCH + ) + self._char_doorbell_detected_switch = serv_stateless_switch.configure_char( + CHAR_PROGRAMMABLE_SWITCH_EVENT, + value=0, + valid_values={"SinglePress": DOORBELL_SINGLE_PRESS}, + ) serv_speaker = self.add_preload_service(SERV_SPEAKER) serv_speaker.configure_char(CHAR_MUTE, value=0) @@ -282,6 +292,7 @@ class Camera(HomeAccessory, PyhapCamera): if new_state.state == STATE_ON: self._char_doorbell_detected.set_value(DOORBELL_SINGLE_PRESS) + self._char_doorbell_detected_switch.set_value(DOORBELL_SINGLE_PRESS) _LOGGER.debug( "%s: Set linked doorbell %s sensor to %d", self.entity_id, diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index 9e8faa34d38..118ce2d9934 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -21,6 +21,7 @@ from homeassistant.components.homekit.const import ( DEVICE_CLASS_OCCUPANCY, SERV_DOORBELL, SERV_MOTION_SENSOR, + SERV_STATELESS_PROGRAMMABLE_SWITCH, VIDEO_CODEC_COPY, VIDEO_CODEC_H264_OMX, ) @@ -653,18 +654,28 @@ async def test_camera_with_linked_doorbell_sensor(hass, run_driver, events): assert char.value == 0 + service2 = acc.get_service(SERV_STATELESS_PROGRAMMABLE_SWITCH) + assert service2 + char2 = service.get_characteristic(CHAR_PROGRAMMABLE_SWITCH_EVENT) + assert char2 + + assert char2.value == 0 + hass.states.async_set( doorbell_entity_id, STATE_OFF, {ATTR_DEVICE_CLASS: DEVICE_CLASS_OCCUPANCY} ) await hass.async_block_till_done() assert char.value == 0 + assert char2.value == 0 char.set_value(True) + char2.set_value(True) hass.states.async_set( doorbell_entity_id, STATE_ON, {ATTR_DEVICE_CLASS: DEVICE_CLASS_OCCUPANCY} ) await hass.async_block_till_done() assert char.value == 0 + assert char2.value == 0 # Ensure we do not throw when the linked # doorbell sensor is removed @@ -673,6 +684,7 @@ async def test_camera_with_linked_doorbell_sensor(hass, run_driver, events): await acc.run_handler() await hass.async_block_till_done() assert char.value == 0 + assert char2.value == 0 async def test_camera_with_a_missing_linked_doorbell_sensor(hass, run_driver, events): @@ -703,3 +715,4 @@ async def test_camera_with_a_missing_linked_doorbell_sensor(hass, run_driver, ev assert acc.category == 17 # Camera assert not acc.get_service(SERV_DOORBELL) + assert not acc.get_service(SERV_STATELESS_PROGRAMMABLE_SWITCH) From 30e1ff83b9babcc9f96f8f2890d45674da6189e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Aug 2020 01:40:28 -0500 Subject: [PATCH 16/77] Ensure doorbird does not block startup (#38619) --- homeassistant/components/doorbird/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/doorbird/manifest.json b/homeassistant/components/doorbird/manifest.json index 58311fa65e4..23495a22bf8 100644 --- a/homeassistant/components/doorbird/manifest.json +++ b/homeassistant/components/doorbird/manifest.json @@ -2,7 +2,7 @@ "domain": "doorbird", "name": "DoorBird", "documentation": "https://www.home-assistant.io/integrations/doorbird", - "requirements": ["doorbirdpy==2.0.8"], + "requirements": ["doorbirdpy==2.1.0"], "dependencies": ["http"], "zeroconf": ["_axis-video._tcp.local."], "codeowners": ["@oblogic7", "@bdraco"], diff --git a/requirements_all.txt b/requirements_all.txt index b15a653f05f..34cdaf215db 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -491,7 +491,7 @@ distro==1.5.0 dlipower==0.7.165 # homeassistant.components.doorbird -doorbirdpy==2.0.8 +doorbirdpy==2.1.0 # homeassistant.components.dovado dovado==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c38f78a2877..e13dcd5b584 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -252,7 +252,7 @@ directv==0.3.0 distro==1.5.0 # homeassistant.components.doorbird -doorbirdpy==2.0.8 +doorbirdpy==2.1.0 # homeassistant.components.dsmr dsmr_parser==0.18 From b626368b6a65ad9aab4dd4cc0abb554a7db3fd97 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 7 Aug 2020 08:45:05 +0000 Subject: [PATCH 17/77] Bumped version to 0.114.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2b893eb84fe..6158e5c67e2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 214bc81d023cce47f02db5d426fa9330bd520a43 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 7 Aug 2020 08:40:50 +0000 Subject: [PATCH 18/77] Fix lint --- homeassistant/components/tensorflow/image_processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index d6d20c63f56..420c3403a11 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -7,7 +7,7 @@ import time from PIL import Image, ImageDraw, UnidentifiedImageError import numpy as np -import tensorflow as tf +import tensorflow as tf # pylint: disable=import-error import voluptuous as vol from homeassistant.components.image_processing import ( From a92bc562d326cefc9b403497e1b293a75857cd5f Mon Sep 17 00:00:00 2001 From: Thomas Hollstegge Date: Sun, 9 Aug 2020 04:34:14 +0200 Subject: [PATCH 19/77] Make sure groups are initialized before template sensors (#37766) * Make sure groups are initialized before template sensors This way users may use the `expand` function in templates to expand groups and have HA listen for changes to group members. Fixes #35872 * Patch async_setup_platform instead of async_setup * Cleanup * Use an event to avoid sleep * Update tests/components/template/test_sensor.py Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../components/template/manifest.json | 3 +- tests/components/template/test_sensor.py | 46 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/template/manifest.json b/homeassistant/components/template/manifest.json index 4ad03db22bb..dd2f8d1e0c6 100644 --- a/homeassistant/components/template/manifest.json +++ b/homeassistant/components/template/manifest.json @@ -3,5 +3,6 @@ "name": "Template", "documentation": "https://www.home-assistant.io/integrations/template", "codeowners": ["@PhracturedBlue", "@tetienne"], - "quality_scale": "internal" + "quality_scale": "internal", + "after_dependencies": ["group"] } diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 8a3a731f953..3899a7b3afe 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -1,11 +1,16 @@ """The test for the Template sensor platform.""" +from asyncio import Event +from unittest.mock import patch + +from homeassistant.bootstrap import async_from_config_dict from homeassistant.const import ( + EVENT_COMPONENT_LOADED, EVENT_HOMEASSISTANT_START, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.setup import async_setup_component, setup_component +from homeassistant.setup import ATTR_COMPONENT, async_setup_component, setup_component from tests.common import assert_setup_component, get_test_home_assistant @@ -438,6 +443,45 @@ class TestTemplateSensor: ) +async def test_creating_sensor_loads_group(hass): + """Test setting up template sensor loads group component first.""" + order = [] + after_dep_event = Event() + + async def async_setup_group(hass, config): + # Make sure group takes longer to load, so that it won't + # be loaded first by chance + await after_dep_event.wait() + + order.append("group") + return True + + async def async_setup_template( + hass, config, async_add_entities, discovery_info=None + ): + order.append("sensor.template") + return True + + async def set_after_dep_event(event): + if event.data[ATTR_COMPONENT] == "sensor": + after_dep_event.set() + + hass.bus.async_listen(EVENT_COMPONENT_LOADED, set_after_dep_event) + + with patch( + "homeassistant.components.group.async_setup", new=async_setup_group, + ), patch( + "homeassistant.components.template.sensor.async_setup_platform", + new=async_setup_template, + ): + await async_from_config_dict( + {"sensor": {"platform": "template", "sensors": {}}, "group": {}}, hass + ) + await hass.async_block_till_done() + + assert order == ["group", "sensor.template"] + + async def test_available_template_with_entities(hass): """Test availability tempalates with values from other entities.""" hass.states.async_set("sensor.availability_sensor", STATE_OFF) From a58a67923b3246c44a07bd6c753826140434ede8 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 8 Aug 2020 13:59:53 +0200 Subject: [PATCH 20/77] Fix xiaomi_aqara discovery (#38622) --- homeassistant/components/xiaomi_aqara/config_flow.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_aqara/config_flow.py b/homeassistant/components/xiaomi_aqara/config_flow.py index fb66be76635..c42598c2665 100644 --- a/homeassistant/components/xiaomi_aqara/config_flow.py +++ b/homeassistant/components/xiaomi_aqara/config_flow.py @@ -75,8 +75,9 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.interface = user_input[CONF_INTERFACE] # allow optional manual setting of host and mac - if self.host is None and self.sid is None: + if self.host is None: self.host = user_input.get(CONF_HOST) + if self.sid is None: mac_address = user_input.get(CONF_MAC) # format sid from mac_address @@ -173,7 +174,9 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): unique_id = mac_address await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured({CONF_HOST: self.host}) + self._abort_if_unique_id_configured( + {CONF_HOST: self.host, CONF_MAC: mac_address} + ) # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": {"name": self.host}}) From ab9df350fd1c9555e6147807b064c86eec435af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 8 Aug 2020 20:00:56 +0200 Subject: [PATCH 21/77] Update frontend to 20200807.1 (#38626) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index c4d11297e9b..aaab3ac570c 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200805.0"], + "requirements": ["home-assistant-frontend==20200807.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0339c779291..6b4deee3a3e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.35.0 -home-assistant-frontend==20200805.0 +home-assistant-frontend==20200807.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 netdisco==2.8.1 diff --git a/requirements_all.txt b/requirements_all.txt index 34cdaf215db..bb00e05c200 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -733,7 +733,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20200805.0 +home-assistant-frontend==20200807.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e13dcd5b584..a9838bef8c9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -362,7 +362,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20200805.0 +home-assistant-frontend==20200807.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From eac5619001b65394253689cd0780a38ca2857551 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 7 Aug 2020 17:37:31 +0200 Subject: [PATCH 22/77] Remove tf-models-official from wheels builder (#38637) --- azure-pipelines-wheels.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/azure-pipelines-wheels.yml b/azure-pipelines-wheels.yml index ebe704f12e2..c8943595429 100644 --- a/azure-pipelines-wheels.yml +++ b/azure-pipelines-wheels.yml @@ -89,6 +89,5 @@ jobs: sed -i "s|# py_noaa|py_noaa|g" ${requirement_file} sed -i "s|# bme680|bme680|g" ${requirement_file} sed -i "s|# python-gammu|python-gammu|g" ${requirement_file} - sed -i "s|# tf-models-official|tf-models-official|g" ${requirement_file} done displayName: 'Prepare requirements files for Home Assistant wheels' From 95ffe122645194f313dda7d2c7f81c0cbe378ba6 Mon Sep 17 00:00:00 2001 From: Ole-Martin Heggen Date: Fri, 7 Aug 2020 22:14:42 +0200 Subject: [PATCH 23/77] Fix url in seventeentrack delivered notification (#38646) --- homeassistant/components/seventeentrack/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 53b16944cb2..42b198d48d9 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -259,7 +259,7 @@ class SeventeenTrackPackageSensor(Entity): self._friendly_name if self._friendly_name else self._tracking_number ) message = NOTIFICATION_DELIVERED_MESSAGE.format( - self._tracking_number, identification + identification, self._tracking_number ) title = NOTIFICATION_DELIVERED_TITLE.format(identification) notification_id = NOTIFICATION_DELIVERED_TITLE.format(self._tracking_number) From 24c3cbfff9c5106b50af39af74db3774c15d17c4 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 7 Aug 2020 18:01:55 -0600 Subject: [PATCH 24/77] Bump regenmaschine to 2.1.0 (#38649) --- homeassistant/components/rainmachine/__init__.py | 2 +- homeassistant/components/rainmachine/config_flow.py | 6 +++--- homeassistant/components/rainmachine/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/rainmachine/test_config_flow.py | 7 +++---- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index 2e32d0ed43d..239878d0219 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -133,7 +133,7 @@ async def async_setup_entry(hass, config_entry): _verify_domain_control = verify_domain_control(hass, DOMAIN) websession = aiohttp_client.async_get_clientsession(hass) - client = Client(websession) + client = Client(session=websession) try: await client.load_local( diff --git a/homeassistant/components/rainmachine/config_flow.py b/homeassistant/components/rainmachine/config_flow.py index dc1ee16d05f..d0513ac89fb 100644 --- a/homeassistant/components/rainmachine/config_flow.py +++ b/homeassistant/components/rainmachine/config_flow.py @@ -1,5 +1,5 @@ """Config flow to configure the RainMachine component.""" -from regenmaschine import login +from regenmaschine import Client from regenmaschine.errors import RainMachineError import voluptuous as vol @@ -59,12 +59,12 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured() websession = aiohttp_client.async_get_clientsession(self.hass) + client = Client(session=websession) try: - await login( + await client.load_local( user_input[CONF_IP_ADDRESS], user_input[CONF_PASSWORD], - websession, port=user_input[CONF_PORT], ssl=user_input.get(CONF_SSL, True), ) diff --git a/homeassistant/components/rainmachine/manifest.json b/homeassistant/components/rainmachine/manifest.json index aed0f030c25..07321801381 100644 --- a/homeassistant/components/rainmachine/manifest.json +++ b/homeassistant/components/rainmachine/manifest.json @@ -3,6 +3,6 @@ "name": "RainMachine", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/rainmachine", - "requirements": ["regenmaschine==1.5.1"], + "requirements": ["regenmaschine==2.1.0"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index bb00e05c200..59788525fbf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1876,7 +1876,7 @@ raspyrfm-client==1.2.8 recollect-waste==1.0.1 # homeassistant.components.rainmachine -regenmaschine==1.5.1 +regenmaschine==2.1.0 # homeassistant.components.python_script restrictedpython==5.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a9838bef8c9..4b123504a40 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -842,7 +842,7 @@ pyzerproc==0.2.5 rachiopy==0.1.3 # homeassistant.components.rainmachine -regenmaschine==1.5.1 +regenmaschine==2.1.0 # homeassistant.components.python_script restrictedpython==5.0 diff --git a/tests/components/rainmachine/test_config_flow.py b/tests/components/rainmachine/test_config_flow.py index 04dc67bdbe8..7b27bdf2f39 100644 --- a/tests/components/rainmachine/test_config_flow.py +++ b/tests/components/rainmachine/test_config_flow.py @@ -50,8 +50,7 @@ async def test_invalid_password(hass): flow.context = {"source": SOURCE_USER} with patch( - "homeassistant.components.rainmachine.config_flow.login", - side_effect=RainMachineError, + "regenmaschine.client.Client.load_local", side_effect=RainMachineError, ): result = await flow.async_step_user(user_input=conf) assert result["errors"] == {CONF_PASSWORD: "invalid_credentials"} @@ -84,7 +83,7 @@ async def test_step_import(hass): flow.context = {"source": SOURCE_USER} with patch( - "homeassistant.components.rainmachine.config_flow.login", return_value=True, + "regenmaschine.client.Client.load_local", return_value=True, ): result = await flow.async_step_import(import_config=conf) @@ -115,7 +114,7 @@ async def test_step_user(hass): flow.context = {"source": SOURCE_USER} with patch( - "homeassistant.components.rainmachine.config_flow.login", return_value=True, + "regenmaschine.client.Client.load_local", return_value=True, ): result = await flow.async_step_user(user_input=conf) From 43961dc36b5a5877289c60cdadc851b599bd583c Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Fri, 7 Aug 2020 23:50:08 -0500 Subject: [PATCH 25/77] Fix AccuWeather async timeout (#38654) --- homeassistant/components/accuweather/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/accuweather/__init__.py b/homeassistant/components/accuweather/__init__.py index 3dbb713ab2b..1e1a434a036 100644 --- a/homeassistant/components/accuweather/__init__.py +++ b/homeassistant/components/accuweather/__init__.py @@ -114,7 +114,7 @@ class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator): async def _async_update_data(self): """Update data via library.""" try: - with timeout(10): + async with timeout(10): current = await self.accuweather.async_get_current_conditions() forecast = ( await self.accuweather.async_get_forecast(metric=self.is_metric) From aa4e879e1a526cf23c3c0108e08308693b8c3a81 Mon Sep 17 00:00:00 2001 From: Alejandro Rivera Date: Sat, 8 Aug 2020 01:56:39 -0700 Subject: [PATCH 26/77] Fix rest_command UnboundLocalError in exception handling (#38656) ``` 2020-08-07 22:38:10 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection.3903193064] local variable 'response' referenced before assignment Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/components/rest_command/__init__.py", line 115, in async_service_handler async with getattr(websession, method)( File "/usr/local/lib/python3.8/site-packages/aiohttp/client.py", line 1012, in __aenter__ self._resp = await self._coro File "/usr/local/lib/python3.8/site-packages/aiohttp/client.py", line 582, in _request break File "/usr/local/lib/python3.8/site-packages/aiohttp/helpers.py", line 586, in __exit__ raise asyncio.TimeoutError from None asyncio.exceptions.TimeoutError During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 125, in handle_call_service await hass.services.async_call( File "/usr/src/homeassistant/homeassistant/core.py", line 1281, in async_call task.result() File "/usr/src/homeassistant/homeassistant/core.py", line 1316, in _execute_service await handler.func(service_call) File "/usr/src/homeassistant/homeassistant/components/rest_command/__init__.py", line 137, in async_service_handler _LOGGER.warning("Timeout call %s", response.url, exc_info=1) UnboundLocalError: local variable 'response' referenced before assignment ``` --- homeassistant/components/rest_command/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/rest_command/__init__.py b/homeassistant/components/rest_command/__init__.py index f8b99c48a44..1290912897d 100644 --- a/homeassistant/components/rest_command/__init__.py +++ b/homeassistant/components/rest_command/__init__.py @@ -134,7 +134,7 @@ async def async_setup(hass, config): ) except asyncio.TimeoutError: - _LOGGER.warning("Timeout call %s", response.url, exc_info=1) + _LOGGER.warning("Timeout call %s", request_url, exc_info=1) except aiohttp.ClientError: _LOGGER.error("Client error %s", request_url, exc_info=1) From f1e3023d44f58e2a37144e2994382593588bd9d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Aug 2020 15:37:05 -0500 Subject: [PATCH 27/77] Ensure shared zeroconf is passed to homekit controller devices (#38678) --- homeassistant/components/homekit_controller/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 47f3cf20571..4a8730b2e9e 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -11,6 +11,7 @@ from aiohomekit.model.characteristics import ( ) from aiohomekit.model.services import Service, ServicesTypes +from homeassistant.components import zeroconf from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import Entity @@ -212,7 +213,8 @@ async def async_setup(hass, config): map_storage = hass.data[ENTITY_MAP] = EntityMapStorage(hass) await map_storage.async_initialize() - hass.data[CONTROLLER] = aiohomekit.Controller() + zeroconf_instance = await zeroconf.async_get_instance(hass) + hass.data[CONTROLLER] = aiohomekit.Controller(zeroconf_instance=zeroconf_instance) hass.data[KNOWN_DEVICES] = {} return True From 65450d85186e54f50a3bcad9704ecf8b0b2ce479 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Aug 2020 06:29:46 -0500 Subject: [PATCH 28/77] Update aiohomekit to handle homekit devices that do not send format (#38679) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 9bbaf959012..4d37a38e417 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit[IP]==0.2.45"], + "requirements": ["aiohomekit[IP]==0.2.46"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k"] diff --git a/requirements_all.txt b/requirements_all.txt index 59788525fbf..2fb6a9ffa08 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -173,7 +173,7 @@ aioguardian==1.0.1 aioharmony==0.2.6 # homeassistant.components.homekit_controller -aiohomekit[IP]==0.2.45 +aiohomekit[IP]==0.2.46 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4b123504a40..764e573b8b2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -98,7 +98,7 @@ aioguardian==1.0.1 aioharmony==0.2.6 # homeassistant.components.homekit_controller -aiohomekit[IP]==0.2.45 +aiohomekit[IP]==0.2.46 # homeassistant.components.emulated_hue # homeassistant.components.http From 6f5884805e61dce364b0fe9314a17e732a29aced Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 8 Aug 2020 21:00:55 -0600 Subject: [PATCH 29/77] Fix missing data for Guardian "AP enabled" binary sensor (#38681) --- homeassistant/components/guardian/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index 495f325eb7f..c63d80163bc 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -90,7 +90,7 @@ class GuardianBinarySensor(GuardianEntity, BinarySensorEntity): def _async_update_from_latest_data(self) -> None: """Update the entity.""" if self._kind == SENSOR_KIND_AP_INFO: - self._is_on = self._coordinators[API_WIFI_STATUS].data["ap_enabled"] + self._is_on = self._coordinators[API_WIFI_STATUS].data["station_connected"] self._attrs.update( { ATTR_CONNECTED_CLIENTS: self._coordinators[API_WIFI_STATUS].data[ From cb51a00c378d1c5c61e5cda50c2eb914d33c427e Mon Sep 17 00:00:00 2001 From: On Freund Date: Sun, 9 Aug 2020 08:02:21 +0300 Subject: [PATCH 30/77] Bump pyvolumio to 0.1.1 (#38685) --- homeassistant/components/volumio/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/volumio/manifest.json b/homeassistant/components/volumio/manifest.json index 95d84fd7ee6..c5d14859f05 100644 --- a/homeassistant/components/volumio/manifest.json +++ b/homeassistant/components/volumio/manifest.json @@ -5,5 +5,5 @@ "codeowners": ["@OnFreund"], "config_flow": true, "zeroconf": ["_Volumio._tcp.local."], - "requirements": ["pyvolumio==0.1"] + "requirements": ["pyvolumio==0.1.1"] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 2fb6a9ffa08..ba9f91df2cd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1831,7 +1831,7 @@ pyvizio==0.1.49 pyvlx==0.2.16 # homeassistant.components.volumio -pyvolumio==0.1 +pyvolumio==0.1.1 # homeassistant.components.html5 pywebpush==1.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 764e573b8b2..73f86625d5b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -830,7 +830,7 @@ pyvesync==1.1.0 pyvizio==0.1.49 # homeassistant.components.volumio -pyvolumio==0.1 +pyvolumio==0.1.1 # homeassistant.components.html5 pywebpush==1.9.2 From c0be9aca48cce485c979fcd7e80f6fb3d272818d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 9 Aug 2020 11:30:45 +0000 Subject: [PATCH 31/77] Bumped version to 0.114.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6158e5c67e2..a79d95c0b5f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 7f0c97fad857148f8b21bd36e6336fbfce0c9dc7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 9 Aug 2020 14:10:27 +0200 Subject: [PATCH 32/77] Bump updater timeout (#38690) --- homeassistant/components/updater/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 59f858f7cf4..d90efe132f6 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -134,7 +134,7 @@ async def get_newest_version(hass, huuid, include_components): session = async_get_clientsession(hass) - with async_timeout.timeout(15): + with async_timeout.timeout(30): req = await session.post(UPDATER_URL, json=info_object) _LOGGER.info( From e8587408aef0b2d0488f6b85a9fb9af172d36584 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 10 Aug 2020 13:12:23 +0200 Subject: [PATCH 33/77] Update base image 8.2.1 (#38716) --- build.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.json b/build.json index 279a58f4c5a..a1db6ac2a54 100644 --- a/build.json +++ b/build.json @@ -1,11 +1,11 @@ { "image": "homeassistant/{arch}-homeassistant", "build_from": { - "aarch64": "homeassistant/aarch64-homeassistant-base:8.1.0", - "armhf": "homeassistant/armhf-homeassistant-base:8.1.0", - "armv7": "homeassistant/armv7-homeassistant-base:8.1.0", - "amd64": "homeassistant/amd64-homeassistant-base:8.1.0", - "i386": "homeassistant/i386-homeassistant-base:8.1.0" + "aarch64": "homeassistant/aarch64-homeassistant-base:8.2.1", + "armhf": "homeassistant/armhf-homeassistant-base:8.2.1", + "armv7": "homeassistant/armv7-homeassistant-base:8.2.1", + "amd64": "homeassistant/amd64-homeassistant-base:8.2.1", + "i386": "homeassistant/i386-homeassistant-base:8.2.1" }, "labels": { "io.hass.type": "core" From e3335eea007d8a67628d2be1b9a62ed110bda327 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Aug 2020 12:49:12 +0000 Subject: [PATCH 34/77] Bumped version to 0.114.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a79d95c0b5f..6a398512265 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From db646141c6d314a9fc583ae3dd38cbfe242364d7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Aug 2020 17:54:46 +0200 Subject: [PATCH 35/77] Add scan_tag webhook to mobile app (#38721) --- .../components/mobile_app/webhook.py | 12 +++++++++ tests/components/mobile_app/test_webhook.py | 25 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index ca9c31011ed..d03505b0cb9 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -538,3 +538,15 @@ async def webhook_get_config(hass, config_entry, data): pass return webhook_response(resp, registration=config_entry.data) + + +@WEBHOOK_COMMANDS.register("scan_tag") +@validate_schema({vol.Required("tag_id"): cv.string}) +async def webhook_scan_tag(hass, config_entry, data): + """Handle a fire event webhook.""" + hass.bus.async_fire( + "tag_scanned", + {"tag_id": data["tag_id"], "device_id": config_entry.data[ATTR_DEVICE_ID]}, + context=registration_context(config_entry.data), + ) + return empty_okay_response() diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index 195c60d830c..bd38bca535b 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -406,3 +406,28 @@ async def test_webhook_camera_stream_stream_available_but_errors( webhook_json = await resp.json() assert webhook_json["hls_path"] is None assert webhook_json["mjpeg_path"] == "/api/camera_proxy_stream/camera.stream_camera" + + +async def test_webhook_handle_scan_tag(hass, create_registrations, webhook_client): + """Test that we can scan tags.""" + events = [] + + @callback + def store_event(event): + """Helepr to store events.""" + events.append(event) + + hass.bus.async_listen("tag_scanned", store_event) + + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[1]["webhook_id"]), + json={"type": "scan_tag", "data": {"tag_id": "mock-tag-id"}}, + ) + + assert resp.status == 200 + json = await resp.json() + assert json == {} + + assert len(events) == 1 + assert events[0].data["tag_id"] == "mock-tag-id" + assert events[0].data["device_id"] == "mock-device-id" From 57e99af9ecdcc0782b07ce373846a7d44d4c18c8 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 10 Aug 2020 16:34:52 +0200 Subject: [PATCH 36/77] Add scikit-build to installed env (#38726) --- azure-pipelines-wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-wheels.yml b/azure-pipelines-wheels.yml index c8943595429..3e7821d77af 100644 --- a/azure-pipelines-wheels.yml +++ b/azure-pipelines-wheels.yml @@ -48,7 +48,7 @@ jobs: parameters: builderVersion: '$(versionWheels)' builderApk: 'build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev' - builderPip: 'Cython;numpy' + builderPip: 'Cython;numpy;scikit-build' skipBinary: 'aiohttp' wheelsRequirement: 'requirements_wheels.txt' wheelsRequirementDiff: 'requirements_diff.txt' From 6717e67352df7417124023401c3c69a7f1c21573 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Mon, 10 Aug 2020 15:32:00 -0500 Subject: [PATCH 37/77] Bump pysmartthings 0.7.3 (#38732) --- homeassistant/components/smartthings/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json index bf137ae398d..58ea833cb7d 100644 --- a/homeassistant/components/smartthings/manifest.json +++ b/homeassistant/components/smartthings/manifest.json @@ -3,7 +3,7 @@ "name": "SmartThings", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/smartthings", - "requirements": ["pysmartapp==0.3.2", "pysmartthings==0.7.2"], + "requirements": ["pysmartapp==0.3.2", "pysmartthings==0.7.3"], "dependencies": ["webhook"], "after_dependencies": ["cloud"], "codeowners": ["@andrewsayre"] diff --git a/requirements_all.txt b/requirements_all.txt index ba9f91df2cd..a7a3da1dd0e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1626,7 +1626,7 @@ pysmappee==0.1.5 pysmartapp==0.3.2 # homeassistant.components.smartthings -pysmartthings==0.7.2 +pysmartthings==0.7.3 # homeassistant.components.smarty pysmarty==0.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 73f86625d5b..80ff12c16ae 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -755,7 +755,7 @@ pysmappee==0.1.5 pysmartapp==0.3.2 # homeassistant.components.smartthings -pysmartthings==0.7.2 +pysmartthings==0.7.3 # homeassistant.components.soma pysoma==0.0.10 From 48617534e956c11c5c6df046ab428c7107896315 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 10 Aug 2020 17:40:07 -0400 Subject: [PATCH 38/77] Make default duration 1/10th of a second for ZHA light calls (#38739) * default duration to 1/10th of a second * update test --- homeassistant/components/zha/light.py | 2 +- tests/components/zha/test_light.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 51b0633ecf2..ff080562190 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -192,7 +192,7 @@ class BaseLight(LogMixin, light.LightEntity): async def async_turn_on(self, **kwargs): """Turn the entity on.""" transition = kwargs.get(light.ATTR_TRANSITION) - duration = transition * 10 if transition else 0 + duration = transition * 10 if transition else 1 brightness = kwargs.get(light.ATTR_BRIGHTNESS) effect = kwargs.get(light.ATTR_EFFECT) flash = kwargs.get(light.ATTR_FLASH) diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 6b94354ed59..bd340445527 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -403,7 +403,7 @@ async def async_test_level_on_off_from_hass( 4, (zigpy.types.uint8_t, zigpy.types.uint16_t), 10, - 0, + 1, expect_reply=True, manufacturer=None, tsn=None, From eada72e2e119e2fd4a272886567388c7437a1078 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Aug 2020 02:45:36 -0500 Subject: [PATCH 39/77] Install a threading.excepthook on python 3.8 and later (#38741) Exceptions in threads were being silently discarded and never logged as the new in python 3.8 threading.excepthook was not being set. --- homeassistant/bootstrap.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 4cf95d68f05..a7953cbec6c 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -6,6 +6,7 @@ import logging import logging.handlers import os import sys +import threading from time import monotonic from typing import TYPE_CHECKING, Any, Dict, Optional, Set @@ -308,6 +309,12 @@ def async_enable_logging( "Uncaught exception", exc_info=args # type: ignore ) + if sys.version_info[:2] >= (3, 8): + threading.excepthook = lambda args: logging.getLogger(None).exception( + "Uncaught thread exception", + exc_info=(args.exc_type, args.exc_value, args.exc_traceback), + ) + # Log errors to a file if we have write access to file or config dir if log_file is None: err_log_path = hass.config.path(ERROR_LOG_FILENAME) From d1e6fce6528e6cebfe3c22fc683b7ce34f1e5ce4 Mon Sep 17 00:00:00 2001 From: etheralm Date: Tue, 11 Aug 2020 14:19:10 +0200 Subject: [PATCH 40/77] Bump dyson upstream library version (#38756) --- homeassistant/components/dyson/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/dyson/manifest.json b/homeassistant/components/dyson/manifest.json index 35a76180e2e..94a29d1615d 100644 --- a/homeassistant/components/dyson/manifest.json +++ b/homeassistant/components/dyson/manifest.json @@ -2,7 +2,7 @@ "domain": "dyson", "name": "Dyson", "documentation": "https://www.home-assistant.io/integrations/dyson", - "requirements": ["libpurecool==0.6.1"], + "requirements": ["libpurecool==0.6.3"], "after_dependencies": ["zeroconf"], "codeowners": ["@etheralm"] } diff --git a/requirements_all.txt b/requirements_all.txt index a7a3da1dd0e..5ad4de4c823 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -824,7 +824,7 @@ konnected==1.1.0 lakeside==0.12 # homeassistant.components.dyson -libpurecool==0.6.1 +libpurecool==0.6.3 # homeassistant.components.foscam libpyfoscam==1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 80ff12c16ae..584874736ee 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -403,7 +403,7 @@ keyrings.alt==3.4.0 konnected==1.1.0 # homeassistant.components.dyson -libpurecool==0.6.1 +libpurecool==0.6.3 # homeassistant.components.mikrotik librouteros==3.0.0 From 7722bb05bcfebafdffbddf5278bda85d2a9a124b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 11 Aug 2020 15:45:07 +0200 Subject: [PATCH 41/77] Bump frontend to 20200811.0 (#38760) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index aaab3ac570c..da11f574ade 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200807.1"], + "requirements": ["home-assistant-frontend==20200811.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 6b4deee3a3e..e709bf68aa4 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.35.0 -home-assistant-frontend==20200807.1 +home-assistant-frontend==20200811.0 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 netdisco==2.8.1 diff --git a/requirements_all.txt b/requirements_all.txt index 5ad4de4c823..d58700527f9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -733,7 +733,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20200807.1 +home-assistant-frontend==20200811.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 584874736ee..f7840e59b4d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -362,7 +362,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20200807.1 +home-assistant-frontend==20200811.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From e60e82c42428a0a9c3eef6ca1011773d2a6bcc3e Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 11 Aug 2020 11:14:02 -0400 Subject: [PATCH 42/77] Bump ZHA quirks lib to 0.0.43 (#38762) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 974cac32c33..3b123c53598 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -6,7 +6,7 @@ "requirements": [ "bellows==0.18.0", "pyserial==3.4", - "zha-quirks==0.0.42", + "zha-quirks==0.0.43", "zigpy-cc==0.4.4", "zigpy-deconz==0.9.2", "zigpy==0.22.2", diff --git a/requirements_all.txt b/requirements_all.txt index d58700527f9..35e7f615694 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2272,7 +2272,7 @@ zengge==0.2 zeroconf==0.28.0 # homeassistant.components.zha -zha-quirks==0.0.42 +zha-quirks==0.0.43 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f7840e59b4d..1b39ec6a51b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1008,7 +1008,7 @@ yeelight==0.5.2 zeroconf==0.28.0 # homeassistant.components.zha -zha-quirks==0.0.42 +zha-quirks==0.0.43 # homeassistant.components.zha zigpy-cc==0.4.4 From c0730a519af67d9ff5ba7adce00eed4539fef265 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 12 Aug 2020 08:00:38 +0200 Subject: [PATCH 43/77] Fix lastest version in updater for Supervisor enabled installs (#38773) * Fix lastest version in update for Supervisor enabled installs * Fix updater tests --- homeassistant/components/hassio/__init__.py | 24 ++++++++++---------- homeassistant/components/hassio/handler.py | 8 +++++++ homeassistant/components/updater/__init__.py | 5 ++-- tests/components/hassio/test_handler.py | 24 ++++++++++++++++++++ tests/components/hassio/test_init.py | 20 +++++++++------- tests/components/updater/test_init.py | 7 +----- 6 files changed, 60 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index f64461f70d3..69c53225d49 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -44,6 +44,7 @@ CONFIG_SCHEMA = vol.Schema( DATA_INFO = "hassio_info" DATA_HOST_INFO = "hassio_host_info" +DATA_CORE_INFO = "hassio_core_info" HASSIO_UPDATE_INTERVAL = timedelta(minutes=55) SERVICE_ADDON_START = "addon_start" @@ -140,18 +141,6 @@ async def async_get_addon_info(hass: HomeAssistantType, addon_id: str) -> dict: return result["data"] -@callback -@bind_hass -def get_homeassistant_version(hass): - """Return latest available Home Assistant version. - - Async friendly. - """ - if DATA_INFO not in hass.data: - return None - return hass.data[DATA_INFO].get("homeassistant") - - @callback @bind_hass def get_info(hass): @@ -172,6 +161,16 @@ def get_host_info(hass): return hass.data.get(DATA_HOST_INFO) +@callback +@bind_hass +def get_core_info(hass): + """Return Home Assistant Core information from Supervisor. + + Async friendly. + """ + return hass.data.get(DATA_CORE_INFO) + + @callback @bind_hass def is_hassio(hass): @@ -301,6 +300,7 @@ async def async_setup(hass, config): try: hass.data[DATA_INFO] = await hassio.get_info() hass.data[DATA_HOST_INFO] = await hassio.get_host_info() + hass.data[DATA_CORE_INFO] = await hassio.get_core_info() except HassioAPIError as err: _LOGGER.warning("Can't read last version: %s", err) diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 861056a46e4..e96ed613324 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -82,6 +82,14 @@ class HassIO: """ return self.send_command("/host/info", method="get") + @_api_data + def get_core_info(self): + """Return data for Home Asssistant Core. + + This method returns a coroutine. + """ + return self.send_command("/core/info", method="get") + @_api_data def get_addon_info(self, addon): """Return data for a Add-on. diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index d90efe132f6..f3c9483e4a8 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -76,9 +76,10 @@ async def async_setup(hass, config): if "dev" in current_version: return Updater(False, "", "") - # Load data from supervisor on Hass.io + # Load data from Supervisor if hass.components.hassio.is_hassio(): - newest = hass.components.hassio.get_homeassistant_version() + core_info = hass.components.hassio.get_core_info() + newest = core_info["version_latest"] # Validate version update_available = False diff --git a/tests/components/hassio/test_handler.py b/tests/components/hassio/test_handler.py index 67fcfb75d5f..311fc6c7e8c 100644 --- a/tests/components/hassio/test_handler.py +++ b/tests/components/hassio/test_handler.py @@ -92,6 +92,30 @@ async def test_api_host_info_error(hassio_handler, aioclient_mock): assert aioclient_mock.call_count == 1 +async def test_api_core_info(hassio_handler, aioclient_mock): + """Test setup with API Home Assistant Core info.""" + aioclient_mock.get( + "http://127.0.0.1/core/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + + data = await hassio_handler.get_core_info() + assert aioclient_mock.call_count == 1 + assert data["version_latest"] == "1.0.0" + + +async def test_api_core_info_error(hassio_handler, aioclient_mock): + """Test setup with API Home Assistant Core info error.""" + aioclient_mock.get( + "http://127.0.0.1/core/info", json={"result": "error", "message": None} + ) + + with pytest.raises(HassioAPIError): + await hassio_handler.get_core_info() + + assert aioclient_mock.call_count == 1 + + async def test_api_homeassistant_stop(hassio_handler, aioclient_mock): """Test setup with API Home Assistant stop.""" aioclient_mock.post("http://127.0.0.1/homeassistant/stop", json={"result": "ok"}) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index d0043747835..34ba638410a 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -40,6 +40,10 @@ def mock_all(aioclient_mock): }, }, ) + aioclient_mock.get( + "http://127.0.0.1/core/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) aioclient_mock.get( "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} ) @@ -51,8 +55,8 @@ async def test_setup_api_ping(hass, aioclient_mock): result = await async_setup_component(hass, "hassio", {}) assert result - assert aioclient_mock.call_count == 6 - assert hass.components.hassio.get_homeassistant_version() == "0.110.0" + assert aioclient_mock.call_count == 7 + assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0" assert hass.components.hassio.is_hassio() @@ -90,7 +94,7 @@ async def test_setup_api_push_api_data(hass, aioclient_mock): ) assert result - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 7 assert not aioclient_mock.mock_calls[1][2]["ssl"] assert aioclient_mock.mock_calls[1][2]["port"] == 9999 assert aioclient_mock.mock_calls[1][2]["watchdog"] @@ -106,7 +110,7 @@ async def test_setup_api_push_api_data_server_host(hass, aioclient_mock): ) assert result - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 7 assert not aioclient_mock.mock_calls[1][2]["ssl"] assert aioclient_mock.mock_calls[1][2]["port"] == 9999 assert not aioclient_mock.mock_calls[1][2]["watchdog"] @@ -118,7 +122,7 @@ async def test_setup_api_push_api_data_default(hass, aioclient_mock, hass_storag result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}}) assert result - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 7 assert not aioclient_mock.mock_calls[1][2]["ssl"] assert aioclient_mock.mock_calls[1][2]["port"] == 8123 refresh_token = aioclient_mock.mock_calls[1][2]["refresh_token"] @@ -165,7 +169,7 @@ async def test_setup_api_existing_hassio_user(hass, aioclient_mock, hass_storage result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}}) assert result - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 7 assert not aioclient_mock.mock_calls[1][2]["ssl"] assert aioclient_mock.mock_calls[1][2]["port"] == 8123 assert aioclient_mock.mock_calls[1][2]["refresh_token"] == token.token @@ -179,7 +183,7 @@ async def test_setup_core_push_timezone(hass, aioclient_mock): result = await async_setup_component(hass, "hassio", {"hassio": {}}) assert result - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 7 assert aioclient_mock.mock_calls[2][2]["timezone"] == "testzone" await hass.config.async_update(time_zone="America/New_York") @@ -195,7 +199,7 @@ async def test_setup_hassio_no_additional_data(hass, aioclient_mock): result = await async_setup_component(hass, "hassio", {"hassio": {}}) assert result - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 7 assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456" diff --git a/tests/components/updater/test_init.py b/tests/components/updater/test_init.py index 203a4df8355..89ebf9e1bbb 100644 --- a/tests/components/updater/test_init.py +++ b/tests/components/updater/test_init.py @@ -154,12 +154,7 @@ async def test_new_version_shows_entity_after_hour_hassio( """Test if binary sensor gets updated if new version is available / Hass.io.""" mock_get_uuid.return_value = MOCK_HUUID mock_component(hass, "hassio") - hass.data["hassio_info"] = {"hassos": None, "homeassistant": "999.0"} - hass.data["hassio_host"] = { - "supervisor": "222", - "chassis": "vm", - "operating_system": "HassOS 4.6", - } + hass.data["hassio_core_info"] = {"version_latest": "999.0"} assert await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}}) From f33d120ebf5d804f3b33daea255282c9fc244515 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 11 Aug 2020 20:41:49 -0400 Subject: [PATCH 44/77] Bump up ZHA dependencies (#38775) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 3b123c53598..b9d2caf0137 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.18.0", + "bellows==0.18.1", "pyserial==3.4", "zha-quirks==0.0.43", "zigpy-cc==0.4.4", diff --git a/requirements_all.txt b/requirements_all.txt index 35e7f615694..45acef0fd07 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -328,7 +328,7 @@ beautifulsoup4==4.9.0 beewi_smartclim==0.0.7 # homeassistant.components.zha -bellows==0.18.0 +bellows==0.18.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1b39ec6a51b..c1b4f560dc9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -175,7 +175,7 @@ azure-eventhub==5.1.0 base36==0.1.1 # homeassistant.components.zha -bellows==0.18.0 +bellows==0.18.1 # homeassistant.components.blebox blebox_uniapi==1.3.2 From e3f10f977bde69535960bd22cf4a498dd3331ab0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 12 Aug 2020 10:58:46 +0200 Subject: [PATCH 45/77] Bumped version to 0.114.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6a398512265..2726181612a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From caf5020bac10f1bd108c3b7db01a02407e0a3f56 Mon Sep 17 00:00:00 2001 From: Quentame Date: Fri, 14 Aug 2020 15:10:13 +0200 Subject: [PATCH 46/77] Update meteo_france based on code review (#38789) * Review: if not to pop * Review: async_add_job --> async_add_executor_job * Review: const * Review: start logging messages with capital letter * Review : UTC isoformated time --> fix "Invalid date"" * Fix hail forecast condition * Review: _show_setup_form is a callback * Fix update option * Review: no icon for next_rain * Review: inline cities form * Review: store places as an instance attribute * UNDO_UPDATE_LISTENER() --- .../components/meteo_france/__init__.py | 20 +++++-- .../components/meteo_france/config_flow.py | 58 ++++++++++--------- .../components/meteo_france/const.py | 36 +++++++----- .../components/meteo_france/sensor.py | 20 ++++--- .../components/meteo_france/weather.py | 5 +- 5 files changed, 82 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 469c66ad79f..276aac188d9 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -20,6 +20,7 @@ from .const import ( COORDINATOR_RAIN, DOMAIN, PLATFORMS, + UNDO_UPDATE_LISTENER, ) _LOGGER = logging.getLogger(__name__) @@ -77,15 +78,17 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool async def _async_update_data_forecast_forecast(): """Fetch data from API endpoint.""" - return await hass.async_add_job(client.get_forecast, latitude, longitude) + return await hass.async_add_executor_job( + client.get_forecast, latitude, longitude + ) async def _async_update_data_rain(): """Fetch data from API endpoint.""" - return await hass.async_add_job(client.get_rain, latitude, longitude) + return await hass.async_add_executor_job(client.get_rain, latitude, longitude) async def _async_update_data_alert(): """Fetch data from API endpoint.""" - return await hass.async_add_job( + return await hass.async_add_executor_job( client.get_warning_current_phenomenoms, department, 0, True ) @@ -156,10 +159,13 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool entry.title, ) + undo_listener = entry.add_update_listener(_async_update_listener) + hass.data[DOMAIN][entry.entry_id] = { COORDINATOR_FORECAST: coordinator_forecast, COORDINATOR_RAIN: coordinator_rain, COORDINATOR_ALERT: coordinator_alert, + UNDO_UPDATE_LISTENER: undo_listener, } for platform in PLATFORMS: @@ -192,8 +198,14 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): ) ) if unload_ok: + hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() hass.data[DOMAIN].pop(entry.entry_id) - if len(hass.data[DOMAIN]) == 0: + if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) return unload_ok + + +async def _async_update_listener(hass: HomeAssistantType, entry: ConfigEntry): + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index 73b1ea41089..0854c280c16 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -21,13 +21,18 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + def __init__(self): + """Init MeteoFranceFlowHandler.""" + self.places = [] + @staticmethod @callback def async_get_options_flow(config_entry): """Get the options flow for this handler.""" return MeteoFranceOptionsFlowHandler(config_entry) - async def _show_setup_form(self, user_input=None, errors=None): + @callback + def _show_setup_form(self, user_input=None, errors=None): """Show the setup form to the user.""" if user_input is None: @@ -46,7 +51,7 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors = {} if user_input is None: - return await self._show_setup_form(user_input, errors) + return self._show_setup_form(user_input, errors) city = user_input[CONF_CITY] # Might be a city name or a postal code latitude = user_input.get(CONF_LATITUDE) @@ -54,13 +59,15 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if not latitude: client = MeteoFranceClient() - places = await self.hass.async_add_executor_job(client.search_places, city) - _LOGGER.debug("places search result: %s", places) - if not places: + self.places = await self.hass.async_add_executor_job( + client.search_places, city + ) + _LOGGER.debug("Places search result: %s", self.places) + if not self.places: errors[CONF_CITY] = "empty" - return await self._show_setup_form(user_input, errors) + return self._show_setup_form(user_input, errors) - return await self.async_step_cities(places=places) + return await self.async_step_cities() # Check if already configured await self.async_set_unique_id(f"{latitude}, {longitude}") @@ -74,19 +81,27 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Import a config entry.""" return await self.async_step_user(user_input) - async def async_step_cities(self, user_input=None, places=None): + async def async_step_cities(self, user_input=None): """Step where the user choose the city from the API search results.""" - if places and len(places) > 1 and self.source != SOURCE_IMPORT: - places_for_form = {} - for place in places: - places_for_form[_build_place_key(place)] = f"{place}" + if not user_input: + if len(self.places) > 1 and self.source != SOURCE_IMPORT: + places_for_form = {} + for place in self.places: + places_for_form[_build_place_key(place)] = f"{place}" - return await self._show_cities_form(places_for_form) - # for import and only 1 city in the search result - if places and not user_input: - user_input = {CONF_CITY: _build_place_key(places[0])} + return self.async_show_form( + step_id="cities", + data_schema=vol.Schema( + { + vol.Required(CONF_CITY): vol.All( + vol.Coerce(str), vol.In(places_for_form) + ) + } + ), + ) + user_input = {CONF_CITY: _build_place_key(self.places[0])} - city_infos = user_input.get(CONF_CITY).split(";") + city_infos = user_input[CONF_CITY].split(";") return await self.async_step_user( { CONF_CITY: city_infos[0], @@ -95,15 +110,6 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } ) - async def _show_cities_form(self, cities): - """Show the form to choose the city.""" - return self.async_show_form( - step_id="cities", - data_schema=vol.Schema( - {vol.Required(CONF_CITY): vol.All(vol.Coerce(str), vol.In(cities))} - ), - ) - class MeteoFranceOptionsFlowHandler(config_entries.OptionsFlow): """Handle a option flow.""" diff --git a/homeassistant/components/meteo_france/const.py b/homeassistant/components/meteo_france/const.py index d1decb54078..8e6c625e331 100644 --- a/homeassistant/components/meteo_france/const.py +++ b/homeassistant/components/meteo_france/const.py @@ -1,6 +1,9 @@ """Meteo-France component constants.""" from homeassistant.const import ( + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, PRESSURE_HPA, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, @@ -12,6 +15,7 @@ PLATFORMS = ["sensor", "weather"] COORDINATOR_FORECAST = "coordinator_forecast" COORDINATOR_RAIN = "coordinator_rain" COORDINATOR_ALERT = "coordinator_alert" +UNDO_UPDATE_LISTENER = "undo_update_listener" ATTRIBUTION = "Data provided by Météo-France" CONF_CITY = "city" @@ -24,7 +28,7 @@ ATTR_NEXT_RAIN_1_HOUR_FORECAST = "1_hour_forecast" ENTITY_NAME = "name" ENTITY_UNIT = "unit" ENTITY_ICON = "icon" -ENTITY_CLASS = "device_class" +ENTITY_DEVICE_CLASS = "device_class" ENTITY_ENABLE = "enable" ENTITY_API_DATA_PATH = "data_path" @@ -32,8 +36,8 @@ SENSOR_TYPES = { "pressure": { ENTITY_NAME: "Pressure", ENTITY_UNIT: PRESSURE_HPA, - ENTITY_ICON: "mdi:gauge", - ENTITY_CLASS: "pressure", + ENTITY_ICON: None, + ENTITY_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, ENTITY_ENABLE: False, ENTITY_API_DATA_PATH: "current_forecast:sea_level", }, @@ -41,7 +45,7 @@ SENSOR_TYPES = { ENTITY_NAME: "Rain chance", ENTITY_UNIT: UNIT_PERCENTAGE, ENTITY_ICON: "mdi:weather-rainy", - ENTITY_CLASS: None, + ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: True, ENTITY_API_DATA_PATH: "probability_forecast:rain:3h", }, @@ -49,7 +53,7 @@ SENSOR_TYPES = { ENTITY_NAME: "Snow chance", ENTITY_UNIT: UNIT_PERCENTAGE, ENTITY_ICON: "mdi:weather-snowy", - ENTITY_CLASS: None, + ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: True, ENTITY_API_DATA_PATH: "probability_forecast:snow:3h", }, @@ -57,7 +61,7 @@ SENSOR_TYPES = { ENTITY_NAME: "Freeze chance", ENTITY_UNIT: UNIT_PERCENTAGE, ENTITY_ICON: "mdi:snowflake", - ENTITY_CLASS: None, + ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: True, ENTITY_API_DATA_PATH: "probability_forecast:freezing", }, @@ -65,23 +69,23 @@ SENSOR_TYPES = { ENTITY_NAME: "Wind speed", ENTITY_UNIT: SPEED_KILOMETERS_PER_HOUR, ENTITY_ICON: "mdi:weather-windy", - ENTITY_CLASS: None, + ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: False, ENTITY_API_DATA_PATH: "current_forecast:wind:speed", }, "next_rain": { ENTITY_NAME: "Next rain", ENTITY_UNIT: None, - ENTITY_ICON: "mdi:weather-pouring", - ENTITY_CLASS: "timestamp", + ENTITY_ICON: None, + ENTITY_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, ENTITY_ENABLE: True, ENTITY_API_DATA_PATH: None, }, "temperature": { ENTITY_NAME: "Temperature", ENTITY_UNIT: TEMP_CELSIUS, - ENTITY_ICON: "mdi:thermometer", - ENTITY_CLASS: "temperature", + ENTITY_ICON: None, + ENTITY_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ENTITY_ENABLE: False, ENTITY_API_DATA_PATH: "current_forecast:T:value", }, @@ -89,7 +93,7 @@ SENSOR_TYPES = { ENTITY_NAME: "UV", ENTITY_UNIT: None, ENTITY_ICON: "mdi:sunglasses", - ENTITY_CLASS: None, + ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: True, ENTITY_API_DATA_PATH: "today_forecast:uv", }, @@ -97,7 +101,7 @@ SENSOR_TYPES = { ENTITY_NAME: "Weather alert", ENTITY_UNIT: None, ENTITY_ICON: "mdi:weather-cloudy-alert", - ENTITY_CLASS: None, + ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: True, ENTITY_API_DATA_PATH: None, }, @@ -105,7 +109,7 @@ SENSOR_TYPES = { ENTITY_NAME: "Daily precipitation", ENTITY_UNIT: "mm", ENTITY_ICON: "mdi:cup-water", - ENTITY_CLASS: None, + ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: True, ENTITY_API_DATA_PATH: "today_forecast:precipitation:24h", }, @@ -113,7 +117,7 @@ SENSOR_TYPES = { ENTITY_NAME: "Cloud cover", ENTITY_UNIT: UNIT_PERCENTAGE, ENTITY_ICON: "mdi:weather-partly-cloudy", - ENTITY_CLASS: None, + ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: True, ENTITY_API_DATA_PATH: "current_forecast:clouds", }, @@ -128,7 +132,7 @@ CONDITION_CLASSES = { "Brouillard", "Brouillard givrant", ], - "hail": ["Risque de grêle"], + "hail": ["Risque de grêle", "Risque de grèle"], "lightning": ["Risque d'orages", "Orages"], "lightning-rainy": ["Pluie orageuses", "Pluies orageuses", "Averses orageuses"], "partlycloudy": [ diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 39e33dafd65..927250c6af6 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -21,7 +21,7 @@ from .const import ( COORDINATOR_RAIN, DOMAIN, ENTITY_API_DATA_PATH, - ENTITY_CLASS, + ENTITY_DEVICE_CLASS, ENTITY_ENABLE, ENTITY_ICON, ENTITY_NAME, @@ -128,7 +128,7 @@ class MeteoFranceSensor(Entity): @property def device_class(self): """Return the device class.""" - return SENSOR_TYPES[self._type][ENTITY_CLASS] + return SENSOR_TYPES[self._type][ENTITY_DEVICE_CLASS] @property def entity_registry_enabled_default(self) -> bool: @@ -170,9 +170,15 @@ class MeteoFranceRainSensor(MeteoFranceSensor): @property def state(self): """Return the state.""" - next_rain_date_locale = self.coordinator.data.next_rain_date_locale() + # search first cadran with rain + next_rain = next( + (cadran for cadran in self.coordinator.data.forecast if cadran["rain"] > 1), + None, + ) return ( - dt_util.as_local(next_rain_date_locale) if next_rain_date_locale else None + dt_util.utc_from_timestamp(next_rain["dt"]).isoformat() + if next_rain + else None ) @property @@ -180,11 +186,7 @@ class MeteoFranceRainSensor(MeteoFranceSensor): """Return the state attributes.""" return { ATTR_NEXT_RAIN_1_HOUR_FORECAST: [ - { - dt_util.as_local( - self.coordinator.data.timestamp_to_locale_time(item["dt"]) - ).strftime("%H:%M"): item["desc"] - } + {dt_util.utc_from_timestamp(item["dt"]).isoformat(): item["desc"]} for item in self.coordinator.data.forecast ], ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index a9c4840901b..e8afcea9702 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -16,6 +16,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MODE, TEMP_CELSIUS from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util import dt as dt_util from .const import ( ATTRIBUTION, @@ -134,9 +135,9 @@ class MeteoFranceWeather(WeatherEntity): continue forecast_data.append( { - ATTR_FORECAST_TIME: self.coordinator.data.timestamp_to_locale_time( + ATTR_FORECAST_TIME: dt_util.utc_from_timestamp( forecast["dt"] - ), + ).isoformat(), ATTR_FORECAST_CONDITION: format_condition( forecast["weather"]["desc"] ), From 18eeda0e03dd23f3808ed218676213e872a709aa Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 13 Aug 2020 12:11:58 +0200 Subject: [PATCH 47/77] Catch upnp timeout error (#38794) --- homeassistant/components/upnp/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 98bf3e6f4dd..3a34cb26604 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -1,4 +1,5 @@ """Open ports in your router for Home Assistant and provide statistics.""" +import asyncio from ipaddress import ip_address from operator import itemgetter @@ -106,7 +107,11 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) # discover and construct udn = config_entry.data.get(CONFIG_ENTRY_UDN) st = config_entry.data.get(CONFIG_ENTRY_ST) # pylint: disable=invalid-name - device = await async_discover_and_construct(hass, udn, st) + try: + device = await async_discover_and_construct(hass, udn, st) + except asyncio.TimeoutError: + raise ConfigEntryNotReady + if not device: _LOGGER.info("Unable to create UPnP/IGD, aborting") raise ConfigEntryNotReady From 8feb382ae57349f30510a936b2348d41745f7412 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 12 Aug 2020 15:21:17 -0600 Subject: [PATCH 48/77] Handle unhandled exceptions related to unavailable SimpliSafe features (#38812) --- homeassistant/components/simplisafe/__init__.py | 9 ++++++++- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 327549eeb62..07b4942ad34 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -3,7 +3,7 @@ import asyncio from uuid import UUID from simplipy import API -from simplipy.errors import InvalidCredentialsError, SimplipyError +from simplipy.errors import EndpointUnavailable, InvalidCredentialsError, SimplipyError from simplipy.websocket import ( EVENT_CAMERA_MOTION_DETECTED, EVENT_CONNECTION_LOST, @@ -555,6 +555,13 @@ class SimpliSafe: LOGGER.error("Error while using stored refresh token: %s", err) return + if isinstance(result, EndpointUnavailable): + # In case the user attempt an action not allowed in their current plan, + # we merely log that message at INFO level (so the user is aware, + # but not spammed with ERROR messages that they cannot change): + LOGGER.info(result) + return + if isinstance(result, SimplipyError): LOGGER.error("SimpliSafe error while updating: %s", result) return diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 0ec77d13b9a..dd9ab53cb98 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.2.2"], + "requirements": ["simplisafe-python==9.3.0"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 45acef0fd07..f23f363c731 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1963,7 +1963,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.2.2 +simplisafe-python==9.3.0 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c1b4f560dc9..23f0c94909b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -878,7 +878,7 @@ sentry-sdk==0.13.5 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.2.2 +simplisafe-python==9.3.0 # homeassistant.components.sleepiq sleepyq==0.7 From eb16ca847a997a56bfa9240405cbb22b09baa356 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 13 Aug 2020 03:12:18 -0500 Subject: [PATCH 49/77] Make executor max_workers consistent between python versions (#38821) The default on python 3.8 is for max_workers is significantly lower than the default on python 3.7 which means we can get starved for workers. To determine a reasonable maximum, the maximum was increased to large number on 5 production instances. The number of worker threads created during startup that were needed to avoid waiting for a thread: HOU 1 - 71 HOU 2 - 48 OGG 1 - 60 OGG 2 - 68 OGG 3 - 64 This lead to a selection of 64 as it was reliable in all cases and did not have a significant memory impact --- homeassistant/runner.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 26e7bab7616..b397f9438f2 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -11,6 +11,18 @@ from homeassistant import bootstrap from homeassistant.core import callback from homeassistant.helpers.frame import warn_use +# +# Python 3.8 has significantly less workers by default +# than Python 3.7. In order to be consistent between +# supported versions, we need to set max_workers. +# +# In most cases the workers are not I/O bound, as they +# are sleeping/blocking waiting for data from integrations +# updating so this number should be higher than the default +# use case. +# +MAX_EXECUTOR_WORKERS = 64 + @dataclasses.dataclass class RuntimeConfig: @@ -57,7 +69,9 @@ class HassEventLoopPolicy(PolicyBase): # type: ignore if self.debug: loop.set_debug(True) - executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker") + executor = ThreadPoolExecutor( + thread_name_prefix="SyncWorker", max_workers=MAX_EXECUTOR_WORKERS + ) loop.set_default_executor(executor) loop.set_default_executor = warn_use( # type: ignore loop.set_default_executor, "sets default executor on the event loop" From 473d0af85d96cd7be4f772e3b78dbbb2c0a7debe Mon Sep 17 00:00:00 2001 From: RogerSelwyn Date: Thu, 13 Aug 2020 10:47:32 +0100 Subject: [PATCH 50/77] Fix creation of unrequired sensors in OVO energy (#38835) --- homeassistant/components/ovo_energy/sensor.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 5fe1bb056e7..4b9e2e70806 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -27,18 +27,20 @@ async def async_setup_entry( ] client: OVOEnergy = hass.data[DOMAIN][entry.entry_id][DATA_CLIENT] - currency = coordinator.data.electricity[ - len(coordinator.data.electricity) - 1 - ].cost.currency_unit + entities = [] + + if coordinator.data.electricity: + currency = coordinator.data.electricity[ + len(coordinator.data.electricity) - 1 + ].cost.currency_unit + entities.append(OVOEnergyLastElectricityReading(coordinator, client)) + entities.append(OVOEnergyLastElectricityCost(coordinator, client, currency)) + if coordinator.data.gas: + entities.append(OVOEnergyLastGasReading(coordinator, client)) + entities.append(OVOEnergyLastGasCost(coordinator, client, currency)) async_add_entities( - [ - OVOEnergyLastElectricityReading(coordinator, client), - OVOEnergyLastGasReading(coordinator, client), - OVOEnergyLastElectricityCost(coordinator, client, currency), - OVOEnergyLastGasCost(coordinator, client, currency), - ], - True, + entities, True, ) From d4a7cefa8d53bda62ccf493f5ec3cac866d37f82 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Thu, 13 Aug 2020 19:00:55 -0700 Subject: [PATCH 51/77] Bump pywemo to 0.4.46 (#38845) --- homeassistant/components/wemo/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index 7bb3371c153..6200c3b46ed 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -3,7 +3,7 @@ "name": "Belkin WeMo", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wemo", - "requirements": ["pywemo==0.4.45"], + "requirements": ["pywemo==0.4.46"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/requirements_all.txt b/requirements_all.txt index f23f363c731..61971f3e8c5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1837,7 +1837,7 @@ pyvolumio==0.1.1 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.4.45 +pywemo==0.4.46 # homeassistant.components.xeoma pyxeoma==1.4.1 From ca5368f01ff486ab0923a765e504cc30d86e7b45 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 14 Aug 2020 13:06:31 +0100 Subject: [PATCH 52/77] Fix OVO Energy Sensors (#38849) --- .../components/ovo_energy/__init__.py | 9 +++++- homeassistant/components/ovo_energy/sensor.py | 32 +++++++++++++------ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/ovo_energy/__init__.py b/homeassistant/components/ovo_energy/__init__.py index 3aff51fa044..e98e81ba1c2 100644 --- a/homeassistant/components/ovo_energy/__init__.py +++ b/homeassistant/components/ovo_energy/__init__.py @@ -40,7 +40,14 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool """Fetch data from OVO Energy.""" now = datetime.utcnow() async with async_timeout.timeout(10): - return await client.get_daily_usage(now.strftime("%Y-%m")) + try: + await client.authenticate( + entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD] + ) + return await client.get_daily_usage(now.strftime("%Y-%m")) + except aiohttp.ClientError as exception: + _LOGGER.warning(exception) + return None coordinator = DataUpdateCoordinator( hass, diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 4b9e2e70806..a0781836d6c 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -29,15 +29,29 @@ async def async_setup_entry( entities = [] - if coordinator.data.electricity: - currency = coordinator.data.electricity[ - len(coordinator.data.electricity) - 1 - ].cost.currency_unit - entities.append(OVOEnergyLastElectricityReading(coordinator, client)) - entities.append(OVOEnergyLastElectricityCost(coordinator, client, currency)) - if coordinator.data.gas: - entities.append(OVOEnergyLastGasReading(coordinator, client)) - entities.append(OVOEnergyLastGasCost(coordinator, client, currency)) + if coordinator.data: + if coordinator.data.electricity: + entities.append(OVOEnergyLastElectricityReading(coordinator, client)) + entities.append( + OVOEnergyLastElectricityCost( + coordinator, + client, + coordinator.data.electricity[ + len(coordinator.data.electricity) - 1 + ].cost.currency_unit, + ) + ) + if coordinator.data.gas: + entities.append(OVOEnergyLastGasReading(coordinator, client)) + entities.append( + OVOEnergyLastGasCost( + coordinator, + client, + coordinator.data.gas[ + len(coordinator.data.gas) - 1 + ].cost.currency_unit, + ) + ) async_add_entities( entities, True, From 276874c4144e9110ec969636c3669f14ec09a013 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 14 Aug 2020 05:35:42 -0700 Subject: [PATCH 53/77] Fix ozw dimming transition (#38850) * Handle float from light component * Test with float Co-authored-by: Paulus Schoutsen --- homeassistant/components/ozw/light.py | 2 +- tests/components/ozw/test_light.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ozw/light.py b/homeassistant/components/ozw/light.py index 2c7461976c4..7464023868d 100644 --- a/homeassistant/components/ozw/light.py +++ b/homeassistant/components/ozw/light.py @@ -172,7 +172,7 @@ class ZwaveLight(ZWaveDeviceEntity, LightEntity): else: # transition specified by user - new_value = max(0, min(7620, kwargs[ATTR_TRANSITION])) + new_value = int(max(0, min(7620, kwargs[ATTR_TRANSITION]))) if ozw_version < (1, 6, 1205): transition = kwargs[ATTR_TRANSITION] if transition <= 127: diff --git a/tests/components/ozw/test_light.py b/tests/components/ozw/test_light.py index 8f9892e37cd..e35a7214858 100644 --- a/tests/components/ozw/test_light.py +++ b/tests/components/ozw/test_light.py @@ -85,7 +85,7 @@ async def test_light(hass, light_data, light_msg, light_rgb_msg, sent_messages): assert state.state == "off" # Test turn on without brightness - new_transition = 127 + new_transition = 127.0 await hass.services.async_call( "light", "turn_on", From 0d4331829c28d3522b9f26c2b94ac8aeaa8077c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 13 Aug 2020 22:00:39 -0500 Subject: [PATCH 54/77] Ensure service browser does not collapse on bad dns names (#38851) If a device on the network presented a bad name, zeroconf would throw zeroconf.BadTypeInNameException and the service browser would die off. We now trap the exception and continue. --- homeassistant/components/zeroconf/__init__.py | 8 ++++++- tests/components/zeroconf/test_init.py | 22 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 71e2f67bad7..f4f2494666b 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -8,6 +8,7 @@ import voluptuous as vol from zeroconf import ( DNSPointer, DNSRecord, + Error as ZeroconfError, InterfaceChoice, IPVersion, NonUniqueNameException, @@ -208,7 +209,12 @@ def setup(hass, config): if state_change != ServiceStateChange.Added: return - service_info = zeroconf.get_service_info(service_type, name) + try: + service_info = zeroconf.get_service_info(service_type, name) + except ZeroconfError: + _LOGGER.exception("Failed to get info for device %s", name) + return + if not service_info: # Prevent the browser thread from collapsing as # service_info can be None diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index e8315b5dc75..dee8ca97433 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -1,6 +1,12 @@ """Test Zeroconf component setup process.""" import pytest -from zeroconf import InterfaceChoice, IPVersion, ServiceInfo, ServiceStateChange +from zeroconf import ( + BadTypeInNameException, + InterfaceChoice, + IPVersion, + ServiceInfo, + ServiceStateChange, +) from homeassistant.components import zeroconf from homeassistant.components.zeroconf import CONF_DEFAULT_INTERFACE, CONF_IPV6 @@ -175,6 +181,20 @@ async def test_setup_with_ipv6_default(hass, mock_zeroconf): assert mock_zeroconf.called_with() +async def test_service_with_invalid_name(hass, mock_zeroconf, caplog): + """Test we do not crash on service with an invalid name.""" + with patch.object( + zeroconf, "HaServiceBrowser", side_effect=service_update_mock + ) as mock_service_browser: + mock_zeroconf.get_service_info.side_effect = BadTypeInNameException + assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert len(mock_service_browser.mock_calls) == 1 + assert "Failed to get info for device name" in caplog.text + + async def test_homekit_match_partial_space(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry.""" with patch.dict( From 0c83156ba46bac807a60d98c043ef21b3af234f9 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 14 Aug 2020 17:55:56 +0100 Subject: [PATCH 55/77] Update ovoenergy package to v1.1.7 (#38875) --- homeassistant/components/ovo_energy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ovo_energy/manifest.json b/homeassistant/components/ovo_energy/manifest.json index 2da08d3339b..ba9579279a9 100644 --- a/homeassistant/components/ovo_energy/manifest.json +++ b/homeassistant/components/ovo_energy/manifest.json @@ -3,6 +3,6 @@ "name": "OVO Energy", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ovo_energy", - "requirements": ["ovoenergy==1.1.6"], + "requirements": ["ovoenergy==1.1.7"], "codeowners": ["@timmo001"] } diff --git a/requirements_all.txt b/requirements_all.txt index 61971f3e8c5..1b55cde4269 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1029,7 +1029,7 @@ oru==0.1.11 orvibo==1.1.1 # homeassistant.components.ovo_energy -ovoenergy==1.1.6 +ovoenergy==1.1.7 # homeassistant.components.mqtt # homeassistant.components.shiftr diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 23f0c94909b..5a0a2c11485 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -473,7 +473,7 @@ onvif-zeep-async==0.4.0 openerz-api==0.1.0 # homeassistant.components.ovo_energy -ovoenergy==1.1.6 +ovoenergy==1.1.7 # homeassistant.components.mqtt # homeassistant.components.shiftr From 10525c7aa79ef0ae24528cc80b8e3032660bc2b6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Aug 2020 00:14:02 -0500 Subject: [PATCH 56/77] Adjust slow add entities timeouts to handle slowest known case (#38876) With this change, we should still be able to startup in under 10 minutes if something really goes wrong. The testing done in #38661 was used to determine these values. --- homeassistant/helpers/entity_platform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 6d9a1275b06..7d6b6b52a9b 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -23,8 +23,8 @@ if TYPE_CHECKING: SLOW_SETUP_WARNING = 10 SLOW_SETUP_MAX_WAIT = 60 -SLOW_ADD_ENTITY_MAX_WAIT = 10 # Per Entity -SLOW_ADD_MIN_TIMEOUT = 60 +SLOW_ADD_ENTITY_MAX_WAIT = 15 # Per Entity +SLOW_ADD_MIN_TIMEOUT = 500 PLATFORM_NOT_READY_RETRIES = 10 DATA_ENTITY_PLATFORM = "entity_platform" From 2485aa772c0bbbd1002faf6bda73a2b6ea28f21c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 15 Aug 2020 05:17:31 +0000 Subject: [PATCH 57/77] Bumped version to 0.114.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2726181612a..908f849d3aa 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 431fe5950c83c7ab82955913090e746e325ad771 Mon Sep 17 00:00:00 2001 From: tizzen33 <53906250+tizzen33@users.noreply.github.com> Date: Mon, 17 Aug 2020 00:06:22 +0200 Subject: [PATCH 58/77] Fix 'Not Available' message for Onkyo integration (#38554) --- homeassistant/components/onkyo/media_player.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 2b67f06ac3c..283c1468385 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -239,6 +239,7 @@ class OnkyoDevice(MediaPlayerEntity): self._source_mapping = sources self._reverse_mapping = {value: key for key, value in sources.items()} self._attributes = {} + self._hdmi_out_supported = True def command(self, command): """Run an eiscp command and catch connection errors.""" @@ -251,6 +252,7 @@ class OnkyoDevice(MediaPlayerEntity): else: _LOGGER.info("%s is disconnected. Attempting to reconnect", self._name) return False + _LOGGER.debug("Result for %s: %s", command, result) return result def update(self): @@ -268,7 +270,13 @@ class OnkyoDevice(MediaPlayerEntity): volume_raw = self.command("volume query") mute_raw = self.command("audio-muting query") current_source_raw = self.command("input-selector query") - hdmi_out_raw = self.command("hdmi-output-selector query") + # If the following command is sent to a device with only one HDMI out, + # the display shows 'Not Available'. + # We avoid this by checking if HDMI out is supported + if self._hdmi_out_supported: + hdmi_out_raw = self.command("hdmi-output-selector query") + else: + hdmi_out_raw = [] preset_raw = self.command("preset query") if not (volume_raw and mute_raw and current_source_raw): return @@ -298,6 +306,8 @@ class OnkyoDevice(MediaPlayerEntity): if not hdmi_out_raw: return self._attributes["video_out"] = ",".join(hdmi_out_raw[1]) + if hdmi_out_raw[1] == "N/A": + self._hdmi_out_supported = False @property def name(self): From 9ee6ae8f946159ea5e66f83534c750abea1e2324 Mon Sep 17 00:00:00 2001 From: escoand Date: Sat, 15 Aug 2020 20:25:37 +0200 Subject: [PATCH 59/77] Better timeout handling in samsungtv integration (#38759) * handle PlatformNotReady * set timeout in bridge * set timeout in test * Revert "handle PlatformNotReady" This reverts commit 118ee06ba016f88abdf767efdf4ef1eacb4e5caa. --- homeassistant/components/samsungtv/bridge.py | 2 +- tests/components/samsungtv/test_media_player.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index 83b8ea3d138..ee761696cc0 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -250,7 +250,7 @@ class SamsungTVWSBridge(SamsungTVBridge): host=self.host, port=self.port, token=self.token, - timeout=10, + timeout=8, name=VALUE_CONF_NAME, ) self._remote.open() diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 5d6c36153c2..840d5ac3f4e 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -97,7 +97,7 @@ MOCK_CALLS_ENTRY_WS = { "host": "fake", "name": "HomeAssistant", "port": 8001, - "timeout": 10, + "timeout": 8, "token": "abcde", } From 48267c2705436c9b58f9c1f784cb48c79e20d93e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 15 Aug 2020 20:41:30 +0200 Subject: [PATCH 60/77] Fix ozw pure rgb dimmer light (#38877) * Fix ozw pure rgb light * Add test --- homeassistant/components/ozw/light.py | 42 +++--- tests/components/ozw/conftest.py | 17 +++ tests/components/ozw/test_light.py | 42 ++++++ tests/fixtures/ozw/light_pure_rgb.json | 25 ++++ .../ozw/light_pure_rgb_dimmer_dump.csv | 131 ++++++++++++++++++ 5 files changed, 238 insertions(+), 19 deletions(-) create mode 100644 tests/fixtures/ozw/light_pure_rgb.json create mode 100644 tests/fixtures/ozw/light_pure_rgb_dimmer_dump.csv diff --git a/homeassistant/components/ozw/light.py b/homeassistant/components/ozw/light.py index 7464023868d..209bd035fcd 100644 --- a/homeassistant/components/ozw/light.py +++ b/homeassistant/components/ozw/light.py @@ -80,24 +80,24 @@ class ZwaveLight(ZWaveDeviceEntity, LightEntity): if self.values.dimming_duration is not None: self._supported_features |= SUPPORT_TRANSITION - if self.values.color is None or self.values.color_channels is None: - return + if self.values.color is not None: + self._supported_features |= SUPPORT_COLOR - self._supported_features |= SUPPORT_COLOR + if self.values.color_channels is not None: + # Support Color Temp if both white channels + if (self.values.color_channels.value & COLOR_CHANNEL_WARM_WHITE) and ( + self.values.color_channels.value & COLOR_CHANNEL_COLD_WHITE + ): + self._supported_features |= SUPPORT_COLOR_TEMP - # Support Color Temp if both white channels - if (self.values.color_channels.value & COLOR_CHANNEL_WARM_WHITE) and ( - self.values.color_channels.value & COLOR_CHANNEL_COLD_WHITE - ): - self._supported_features |= SUPPORT_COLOR_TEMP + # Support White value if only a single white channel + if ((self.values.color_channels.value & COLOR_CHANNEL_WARM_WHITE) != 0) ^ ( + (self.values.color_channels.value & COLOR_CHANNEL_COLD_WHITE) != 0 + ): + self._supported_features |= SUPPORT_WHITE_VALUE - # Support White value if only a single white channel - if ((self.values.color_channels.value & COLOR_CHANNEL_WARM_WHITE) != 0) ^ ( - (self.values.color_channels.value & COLOR_CHANNEL_COLD_WHITE) != 0 - ): - self._supported_features |= SUPPORT_WHITE_VALUE - - self._calculate_rgb_values() + if self.values.color is not None: + self._calculate_color_values() @property def brightness(self): @@ -248,10 +248,8 @@ class ZwaveLight(ZWaveDeviceEntity, LightEntity): self.values.primary.send_value(0) - def _calculate_rgb_values(self): - # Color Channels - self._color_channels = self.values.color_channels.data[ATTR_VALUE] - + def _calculate_color_values(self): + """Parse color rgb and color temperature data.""" # Color Data String data = self.values.color.data[ATTR_VALUE] @@ -259,6 +257,12 @@ class ZwaveLight(ZWaveDeviceEntity, LightEntity): rgb = [int(data[1:3], 16), int(data[3:5], 16), int(data[5:7], 16)] self._hs = color_util.color_RGB_to_hs(*rgb) + if self.values.color_channels is None: + return + + # Color Channels + self._color_channels = self.values.color_channels.data[ATTR_VALUE] + # Parse remaining color channels. OpenZWave appends white channels # that are present. index = 7 diff --git a/tests/components/ozw/conftest.py b/tests/components/ozw/conftest.py index 42947064c1e..8ad19aca46c 100644 --- a/tests/components/ozw/conftest.py +++ b/tests/components/ozw/conftest.py @@ -33,6 +33,12 @@ def light_new_ozw_data_fixture(): return load_fixture("ozw/light_new_ozw_network_dump.csv") +@pytest.fixture(name="light_pure_rgb_dimmer_data", scope="session") +def light_pure_rgb_dimmer_data_fixture(): + """Load light rgb and dimmer MQTT data and return it.""" + return load_fixture("ozw/light_pure_rgb_dimmer_dump.csv") + + @pytest.fixture(name="light_no_rgb_data", scope="session") def light_no_rgb_data_fixture(): """Load light dimmer MQTT data and return it.""" @@ -139,6 +145,17 @@ async def light_rgb_msg_fixture(hass): return message +@pytest.fixture(name="light_pure_rgb_msg") +async def light_pure_rgb_msg_fixture(hass): + """Return a mock MQTT msg with a pure rgb light actuator message.""" + light_json = json.loads( + await hass.async_add_executor_job(load_fixture, "ozw/light_pure_rgb.json") + ) + message = MQTTMessage(topic=light_json["topic"], payload=light_json["payload"]) + message.encode() + return message + + @pytest.fixture(name="switch_msg") async def switch_msg_fixture(hass): """Return a mock MQTT msg with a switch actuator message.""" diff --git a/tests/components/ozw/test_light.py b/tests/components/ozw/test_light.py index e35a7214858..f1055be90d3 100644 --- a/tests/components/ozw/test_light.py +++ b/tests/components/ozw/test_light.py @@ -350,6 +350,48 @@ async def test_light(hass, light_data, light_msg, light_rgb_msg, sent_messages): assert state.attributes["color_temp"] == 153 +async def test_pure_rgb_dimmer_light( + hass, light_pure_rgb_dimmer_data, light_msg, light_pure_rgb_msg, sent_messages +): + """Test light with no color channels command class.""" + receive_message = await setup_ozw(hass, fixture=light_pure_rgb_dimmer_data) + + # Test loaded + state = hass.states.get("light.kitchen_rgb_strip_level") + assert state is not None + assert state.state == "on" + assert state.attributes["supported_features"] == 17 + + # Test setting hs_color + new_color = [300, 70] + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.kitchen_rgb_strip_level", "hs_color": new_color}, + blocking=True, + ) + assert len(sent_messages) == 2 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 255, "ValueIDKey": 122257425} + + msg = sent_messages[-2] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": "#ff4cff0000", "ValueIDKey": 122470423} + + # Feedback on state + light_pure_rgb_msg.decode() + light_pure_rgb_msg.payload["Value"] = "#ff4cff0000" + light_pure_rgb_msg.encode() + receive_message(light_pure_rgb_msg) + await hass.async_block_till_done() + + state = hass.states.get("light.kitchen_rgb_strip_level") + assert state is not None + assert state.state == "on" + assert state.attributes["hs_color"] == (300.0, 70.196) + + async def test_no_rgb_light(hass, light_no_rgb_data, light_no_rgb_msg, sent_messages): """Test setting up config entry.""" receive_message = await setup_ozw(hass, fixture=light_no_rgb_data) diff --git a/tests/fixtures/ozw/light_pure_rgb.json b/tests/fixtures/ozw/light_pure_rgb.json new file mode 100644 index 00000000000..4e66e8459e7 --- /dev/null +++ b/tests/fixtures/ozw/light_pure_rgb.json @@ -0,0 +1,25 @@ +{ + "topic": "OpenZWave/1/node/7/instance/1/commandclass/51/value/122470423/", + "payload": { + "Label": "Color", + "Value": "#ff00000000", + "Units": "#RRGGBBWW", + "ValueSet": false, + "ValuePolled": false, + "ChangeVerified": false, + "Min": 0, + "Max": 0, + "Type": "String", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_COLOR", + "Index": 0, + "Node": 7, + "Genre": "User", + "Help": "Color (in RGB format)", + "ValueIDKey": 122470423, + "ReadOnly": false, + "WriteOnly": false, + "Event": "valueAdded", + "TimeStamp": 1597142799 + } +} diff --git a/tests/fixtures/ozw/light_pure_rgb_dimmer_dump.csv b/tests/fixtures/ozw/light_pure_rgb_dimmer_dump.csv new file mode 100644 index 00000000000..68d8a7baab4 --- /dev/null +++ b/tests/fixtures/ozw/light_pure_rgb_dimmer_dump.csv @@ -0,0 +1,131 @@ +OpenZWave/1/status/,{ "OpenZWave_Version": "1.6.1008", "OZWDeamon_Version": "0.1", "QTOpenZWave_Version": "1.0.0", "QT_Version": "5.12.5", "Status": "driverAllNodesQueried", "TimeStamp": 1579566933, "ManufacturerSpecificDBReady": true, "homeID": 3245146787, "getControllerNodeId": 1, "getSUCNodeId": 1, "isPrimaryController": true, "isBridgeController": false, "hasExtendedTXStatistics": true, "getControllerLibraryVersion": "Z-Wave 3.95", "getControllerLibraryType": "Static Controller", "getControllerPath": "/dev/zwave"} +OpenZWave/1/node/7/,{ "NodeID": 7, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/010F:1000:0900", "ZWAProductURL": "", "ProductPic": "images/fibaro/fgrgbwm441.png", "Description": "RGBW Controller", "ProductManualURL": "", "ProductPageURL": "", "InclusionHelp": "", "ExclusionHelp": "", "ResetHelp": "", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "FIBARO RGBW Dimmer", "ProductPicBase64": "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" }, "Event": "nodeQueriesComplete", "TimeStamp": 1597142985, "NodeManufacturerName": "FIBARO System", "NodeProductName": "FGRGBWM441 RGBW Controller", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Multilevel Switch", "NodeGeneric": 17, "NodeSpecificString": "Multilevel Power Switch", "NodeSpecific": 1, "NodeManufacturerID": "0x010f", "NodeProductType": "0x0900", "NodeProductID": "0x1000", "NodeBaudRate": 40000, "NodeVersion": 4, "NodeGroups": 5, "NodeName": "Kitchen RGB Strip", "NodeLocation": "", "NodeDeviceTypeString": "Unknown Type (0x0000)", "NodeDeviceType": 0, "NodeRole": 0, "NodeRoleString": "Central Controller", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 3, 6, 8, 12, 13, 14, 18, 22, 26, 27, 28 ]} +OpenZWave/1/node/7/instance/1/,{ "Instance": 1, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/51/,{ "Instance": 1, "CommandClassId": 51, "CommandClass": "COMMAND_CLASS_COLOR", "CommandClassVersion": 0, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/51/value/122470423/,{ "Label": "Color", "Value": "#FFFFFF00", "Units": "#RRGGBBWW", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 0, "Node": 7, "Genre": "User", "Help": "Color (in RGB format)", "ValueIDKey": 122470423, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/51/value/281475099181076/,{ "Label": "Color Index", "Value": { "List": [ { "Value": 0, "Label": "Off" }, { "Value": 1, "Label": "Cool White" }, { "Value": 2, "Label": "Warm White" }, { "Value": 3, "Label": "Red" }, { "Value": 4, "Label": "Lime" }, { "Value": 5, "Label": "Blue" }, { "Value": 6, "Label": "Yellow" }, { "Value": 7, "Label": "Cyan" }, { "Value": 8, "Label": "Magenta" }, { "Value": 9, "Label": "Silver" }, { "Value": 10, "Label": "Gray" }, { "Value": 11, "Label": "Maroon" }, { "Value": 12, "Label": "Olive" }, { "Value": 13, "Label": "Green" }, { "Value": 14, "Label": "Purple" }, { "Value": 15, "Label": "Teal" }, { "Value": 16, "Label": "Navy" }, { "Value": 17, "Label": "Custom" } ], "Selected": "Cool White", "Selected_id": 1 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 1, "Node": 7, "Genre": "User", "Help": "Preset Color", "ValueIDKey": 281475099181076, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "CommandClassVersion": 1, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/281475104374804/,{ "Label": "Enable/Disable ALL ON/OFF", "Value": { "List": [ { "Value": 0, "Label": "ALL ON disabled/ ALL OFF disabled" }, { "Value": 1, "Label": "ALL ON disabled/ ALL OFF active" }, { "Value": 2, "Label": "ALL ON active / ALL OFF disabled" }, { "Value": 255, "Label": "ALL ON active / ALL OFF active" } ], "Selected": "ALL ON active / ALL OFF active", "Selected_id": 255 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 7, "Genre": "Config", "Help": "Enable/Disable ALL ON/OFF", "ValueIDKey": 281475104374804, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/1688849987928084/,{ "Label": "Associations command class choice", "Value": { "List": [ { "Value": 0, "Label": "Normal (Dimmer) - BASIC SET/SWITCH_MULTILEVEL_START/STOP" }, { "Value": 1, "Label": "Normal (RGBW) - COLOR_CONTROL_SET/START/STOP_STATE_CHANGE" }, { "Value": 2, "Label": "Normal (RGBW) - COLOR_CONTROL_SET" }, { "Value": 3, "Label": "Brightness - BASIC SET/SWITCH_MULTILEVEL_START/STOP" }, { "Value": 4, "Label": "Rainbow (RGBW) - COLOR_CONTROL_SET" } ], "Selected": "Normal (Dimmer) - BASIC SET/SWITCH_MULTILEVEL_START/STOP", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 6, "Node": 7, "Genre": "Config", "Help": "Choose which command classes are sent to associated devices.", "ValueIDKey": 1688849987928084, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2251799941349396/,{ "Label": "Outputs state change mode", "Value": { "List": [ { "Value": 0, "Label": "MODE 1 - Constant Speed (speed is defined by parameters 9 and 10)" }, { "Value": 1, "Label": "MODE 2 - Constant Time (RGB/RBGW only. Time is defined by parameter 11)" } ], "Selected": "MODE 1 - Constant Speed (speed is defined by parameters 9 and 10)", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 8, "Node": 7, "Genre": "Config", "Help": "Choose the behaviour of transitions between different levels.", "ValueIDKey": 2251799941349396, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2533274918060049/,{ "Label": "Dimming step value (for MODE 1)", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 99, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 9, "Node": 7, "Genre": "Config", "Help": "Size of the step for each change in level during the transition.", "ValueIDKey": 2533274918060049, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2814749894770710/,{ "Label": "Time between dimming steps (for MODE 1)", "Value": 10, "Units": "ms", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 60000, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 7, "Genre": "Config", "Help": "Time between each step in a transition between levels. Setting this to zero means an instantaneous change.", "ValueIDKey": 2814749894770710, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3096224871481361/,{ "Label": "Time to complete the entire transition (for MODE 2)", "Value": 67, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 11, "Node": 7, "Genre": "Config", "Help": "0 - immediate change; 1->63: 20ms->126ms (value*20ms); 65->127: 1s->63s (value-64)*1s; 129->191: 10s->630s (value-128)*10s; 193->255: 1min->63min (value-192)*1min. Default setting: 67 (3s)", "ValueIDKey": 3096224871481361, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3377699848192017/,{ "Label": "Maximum dimmer level", "Value": 255, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 3, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 12, "Node": 7, "Genre": "Config", "Help": "Maximum brightness level for the dimmer", "ValueIDKey": 3377699848192017, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3659174824902673/,{ "Label": "Minimum dimmer level", "Value": 2, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 2, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 13, "Node": 7, "Genre": "Config", "Help": "Minimum brightness level for the dimmer", "ValueIDKey": 3659174824902673, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3940649801613334/,{ "Label": "Inputs / Outputs configuration", "Value": 4369, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 14, "Node": 7, "Genre": "Config", "Help": "This is too complex to describe here, since this value is built up from 4-bits for each of the 4 channels. Refer to the table in the product manual. Default value is 4369 (1111 in hex).", "ValueIDKey": 3940649801613334, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/4222124778323988/,{ "Label": "Option double click", "Value": { "List": [ { "Value": 0, "Label": "Double click disabled" }, { "Value": 1, "Label": "Double click enabled" } ], "Selected": "Double click enabled", "Selected_id": 1 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 15, "Node": 7, "Genre": "Config", "Help": "Option double click (lighting set at 100%). 0 - Double click disabled, 1 - Double click enabled. Default setting 1", "ValueIDKey": 4222124778323988, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/4503599755034644/,{ "Label": "Saving state before power failure", "Value": { "List": [ { "Value": 0, "Label": "State NOT saved at power failure, all outputs are set to OFF upon power restore" }, { "Value": 1, "Label": "State saved at power failure, all outputs are set to previous state upon power restore" } ], "Selected": "State saved at power failure, all outputs are set to previous state upon power restore", "Selected_id": 1 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 16, "Node": 7, "Genre": "Config", "Help": "Saving state before power failure", "ValueIDKey": 4503599755034644, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/8444249428983828/,{ "Label": "Alarm", "Value": { "List": [ { "Value": 0, "Label": "INACTIVE - no response to alarm frames" }, { "Value": 1, "Label": "ALARM ON - the device turns on once alarm is detected (all channels set to 99%)" }, { "Value": 2, "Label": "ALARM OFF - the device turns off once alarm is detected (all channels set to 0%)" }, { "Value": 3, "Label": "ALARM PROGRAM - alarm sequence turns on (program selected in parameter 38)" } ], "Selected": "INACTIVE - no response to alarm frames", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 30, "Node": 7, "Genre": "Config", "Help": "Alarm of any type (general alarm, water flooding alarm, smoke alarm: CO, CO2, temperature alarm). Default setting 0 (Inactive)", "ValueIDKey": 8444249428983828, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/10696049242669073/,{ "Label": "Alarm sequence program", "Value": 10, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 10, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 38, "Node": 7, "Genre": "Config", "Help": "Program number selected from the 10 available.", "ValueIDKey": 10696049242669073, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/10977524219379734/,{ "Label": "Active PROGRAM alarm time", "Value": 600, "Units": "s", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 65534, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 39, "Node": 7, "Genre": "Config", "Help": "In ALARM PROGRAM mode (see parameter 30), this defines the time in seconds the program lasts (1s->65534s)", "ValueIDKey": 10977524219379734, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/11821949149511700/,{ "Label": "Command class reporting Outputs status change", "Value": { "List": [ { "Value": 0, "Label": "Reporting as a result of inputs and controllers actions (SWITCH MULTILEVEL)" }, { "Value": 1, "Label": "Reporting as a result inputs actions (SWITCH MULTILEVEL)" }, { "Value": 2, "Label": "Reporting as a result inputs actions (COLOUR_CONTROL)" } ], "Selected": "Reporting as a result of inputs and controllers actions (SWITCH MULTILEVEL)", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 42, "Node": 7, "Genre": "Config", "Help": "Specify which command class is used to report output status changes", "ValueIDKey": 11821949149511700, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12103424126222353/,{ "Label": "Reporting 0-10v analog inputs change threshold", "Value": 5, "Units": "*0.1V", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 100, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 43, "Node": 7, "Genre": "Config", "Help": "Parameter defines a value by which input voltage must change in order to be reported to the main controller. New value is calculated based on last reported value: Default setting: 5 (0.5V). Range: 1->100 - (0.1V->10V).", "ValueIDKey": 12103424126222353, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12384899102933014/,{ "Label": "Power load reporting frequency", "Value": 30, "Units": "s", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 65534, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 44, "Node": 7, "Genre": "Config", "Help": "Sent if last reported value differs from the current value. Reports will also be sent in case of polling. Default setting: 30 (30s). Range: 1->65534 (1s->65534s) - interval between reports. Zero means reports are only sent in the case of polling, or at turning OFF the device", "ValueIDKey": 12384899102933014, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12666374079643665/,{ "Label": "Reporting changes in energy consumed by controlled devices", "Value": 10, "Units": "*0.01kWh", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 254, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 45, "Node": 7, "Genre": "Config", "Help": "Interval between energy consumption reports (in kWh). New reported energy consumption value is calculated based on last reported value. 1->254 (0.01kWh->2.54kWh). Zero means changes in consumed energy will not be reported, except in case of polling.", "ValueIDKey": 12666374079643665, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/19984723474120724/,{ "Label": "Response to BRIGHTNESS set to 0%", "Value": { "List": [ { "Value": 0, "Label": "Illumination colour set to white (all channels controlled together)" }, { "Value": 1, "Label": "Last set colour is memorized" } ], "Selected": "Last set colour is memorized", "Selected_id": 1 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 71, "Node": 7, "Genre": "Config", "Help": "Set whether to remember the previous RGB mix after the brightness has fallen to zero (black)", "ValueIDKey": 19984723474120724, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/20266198450831377/,{ "Label": "Starting predefined program", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 10, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 72, "Node": 7, "Genre": "Config", "Help": "First predefined program to use when device is set to work in RGB/RGBW mode (parameter 14)", "ValueIDKey": 20266198450831377, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/20547673427542036/,{ "Label": "Triple Click Action", "Value": { "List": [ { "Value": 0, "Label": "NODE INFO control frame is sent" }, { "Value": 1, "Label": "Start favourite program" } ], "Selected": "NODE INFO control frame is sent", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 73, "Node": 7, "Genre": "Config", "Help": "Behaviour when an input is triple-clicked", "ValueIDKey": 20547673427542036, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/,{ "Instance": 1, "CommandClassId": 38, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "CommandClassVersion": 0, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/value/122257425/,{ "Label": "Level", "Value": 99, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 0, "Node": 7, "Genre": "User", "Help": "The Current Level of the Device", "ValueIDKey": 122257425, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597142969} +OpenZWave/1/node/7/instance/1/commandclass/38/value/281475098968088/,{ "Label": "Bright", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 1, "Node": 7, "Genre": "User", "Help": "Increase the Brightness of the Device", "ValueIDKey": 281475098968088, "ReadOnly": false, "WriteOnly": true, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/value/562950075678744/,{ "Label": "Dim", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 2, "Node": 7, "Genre": "User", "Help": "Decrease the Brightness of the Device", "ValueIDKey": 562950075678744, "ReadOnly": false, "WriteOnly": true, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/value/844425060778000/,{ "Label": "Ignore Start Level", "Value": true, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 3, "Node": 7, "Genre": "System", "Help": "Ignore the Start Level of the Device when increasing/decreasing brightness", "ValueIDKey": 844425060778000, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/value/1125900037488657/,{ "Label": "Start Level", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 4, "Node": 7, "Genre": "System", "Help": "Start Level when Changing the Brightness of a Device", "ValueIDKey": 1125900037488657, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/39/,{ "Instance": 1, "CommandClassId": 39, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "CommandClassVersion": 1, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/39/value/130662420/,{ "Label": "Switch All", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Off Enabled" }, { "Value": 2, "Label": "On Enabled" }, { "Value": 255, "Label": "On and Off Enabled" } ], "Selected": "On and Off Enabled", "Selected_id": 255 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "Index": 0, "Node": 7, "Genre": "System", "Help": "Switch All Devices On/Off", "ValueIDKey": 130662420, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597142969} +OpenZWave/1/node/7/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "CommandClassVersion": 0, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/114/value/131891219/,{ "Label": "Loaded Config Revision", "Value": 5, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 7, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 131891219, "ReadOnly": true, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/114/value/281475108601875/,{ "Label": "Config File Revision", "Value": 5, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 7, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475108601875, "ReadOnly": true, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/114/value/562950085312531/,{ "Label": "Latest Available Config File Revision", "Value": 5, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 7, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950085312531, "ReadOnly": true, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/association/1/,{ "Name": "Input 1", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1597142866} +OpenZWave/1/node/7/association/2/,{ "Name": "Input 2", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1597142866} +OpenZWave/1/node/7/association/3/,{ "Name": "Input 3", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1597142866} +OpenZWave/1/node/7/association/4/,{ "Name": "Input 4", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1597142866} +OpenZWave/1/node/7/association/5/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 1, "Members": [ "1.0" ], "TimeStamp": 1597142799} +OpenZWave/1/node/7/statistics/,{ "sendCount": 9, "sentFailed": 0, "retries": 0, "receivedPackets": 8, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597142969, "lastReceivedTimeStamp": 1597142969, "lastRequestRTT": 26, "averageRequestRTT": 42, "lastResponseRTT": 55, "averageResponseRTT": 62, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/instance/1/commandclass/112/value/281475104374804/,{ "Label": "Enable/Disable ALL ON/OFF", "Value": { "List": [ { "Value": 0, "Label": "ALL ON disabled/ ALL OFF disabled" }, { "Value": 1, "Label": "ALL ON disabled/ ALL OFF active" }, { "Value": 2, "Label": "ALL ON active / ALL OFF disabled" }, { "Value": 255, "Label": "ALL ON active / ALL OFF active" } ], "Selected": "ALL ON active / ALL OFF active", "Selected_id": 255 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 7, "Genre": "Config", "Help": "Enable/Disable ALL ON/OFF", "ValueIDKey": 281475104374804, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143008} +OpenZWave/1/node/7/instance/1/commandclass/112/value/1688849987928084/,{ "Label": "Associations command class choice", "Value": { "List": [ { "Value": 0, "Label": "Normal (Dimmer) - BASIC SET/SWITCH_MULTILEVEL_START/STOP" }, { "Value": 1, "Label": "Normal (RGBW) - COLOR_CONTROL_SET/START/STOP_STATE_CHANGE" }, { "Value": 2, "Label": "Normal (RGBW) - COLOR_CONTROL_SET" }, { "Value": 3, "Label": "Brightness - BASIC SET/SWITCH_MULTILEVEL_START/STOP" }, { "Value": 4, "Label": "Rainbow (RGBW) - COLOR_CONTROL_SET" } ], "Selected": "Normal (Dimmer) - BASIC SET/SWITCH_MULTILEVEL_START/STOP", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 6, "Node": 7, "Genre": "Config", "Help": "Choose which command classes are sent to associated devices.", "ValueIDKey": 1688849987928084, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143008} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2251799941349396/,{ "Label": "Outputs state change mode", "Value": { "List": [ { "Value": 0, "Label": "MODE 1 - Constant Speed (speed is defined by parameters 9 and 10)" }, { "Value": 1, "Label": "MODE 2 - Constant Time (RGB/RBGW only. Time is defined by parameter 11)" } ], "Selected": "MODE 1 - Constant Speed (speed is defined by parameters 9 and 10)", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 8, "Node": 7, "Genre": "Config", "Help": "Choose the behaviour of transitions between different levels.", "ValueIDKey": 2251799941349396, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143008} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2533274918060049/,{ "Label": "Dimming step value (for MODE 1)", "Value": 1, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 99, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 9, "Node": 7, "Genre": "Config", "Help": "Size of the step for each change in level during the transition.", "ValueIDKey": 2533274918060049, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2814749894770710/,{ "Label": "Time between dimming steps (for MODE 1)", "Value": 10, "Units": "ms", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 60000, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 7, "Genre": "Config", "Help": "Time between each step in a transition between levels. Setting this to zero means an instantaneous change.", "ValueIDKey": 2814749894770710, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3096224871481361/,{ "Label": "Time to complete the entire transition (for MODE 2)", "Value": 67, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 11, "Node": 7, "Genre": "Config", "Help": "0 - immediate change; 1->63: 20ms->126ms (value*20ms); 65->127: 1s->63s (value-64)*1s; 129->191: 10s->630s (value-128)*10s; 193->255: 1min->63min (value-192)*1min. Default setting: 67 (3s)", "ValueIDKey": 3096224871481361, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3377699848192017/,{ "Label": "Maximum dimmer level", "Value": 255, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 3, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 12, "Node": 7, "Genre": "Config", "Help": "Maximum brightness level for the dimmer", "ValueIDKey": 3377699848192017, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3659174824902673/,{ "Label": "Minimum dimmer level", "Value": 2, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 2, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 13, "Node": 7, "Genre": "Config", "Help": "Minimum brightness level for the dimmer", "ValueIDKey": 3659174824902673, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3940649801613334/,{ "Label": "Inputs / Outputs configuration", "Value": 4369, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 14, "Node": 7, "Genre": "Config", "Help": "This is too complex to describe here, since this value is built up from 4-bits for each of the 4 channels. Refer to the table in the product manual. Default value is 4369 (1111 in hex).", "ValueIDKey": 3940649801613334, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/4222124778323988/,{ "Label": "Option double click", "Value": { "List": [ { "Value": 0, "Label": "Double click disabled" }, { "Value": 1, "Label": "Double click enabled" } ], "Selected": "Double click enabled", "Selected_id": 1 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 15, "Node": 7, "Genre": "Config", "Help": "Option double click (lighting set at 100%). 0 - Double click disabled, 1 - Double click enabled. Default setting 1", "ValueIDKey": 4222124778323988, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/4503599755034644/,{ "Label": "Saving state before power failure", "Value": { "List": [ { "Value": 0, "Label": "State NOT saved at power failure, all outputs are set to OFF upon power restore" }, { "Value": 1, "Label": "State saved at power failure, all outputs are set to previous state upon power restore" } ], "Selected": "State saved at power failure, all outputs are set to previous state upon power restore", "Selected_id": 1 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 16, "Node": 7, "Genre": "Config", "Help": "Saving state before power failure", "ValueIDKey": 4503599755034644, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/8444249428983828/,{ "Label": "Alarm", "Value": { "List": [ { "Value": 0, "Label": "INACTIVE - no response to alarm frames" }, { "Value": 1, "Label": "ALARM ON - the device turns on once alarm is detected (all channels set to 99%)" }, { "Value": 2, "Label": "ALARM OFF - the device turns off once alarm is detected (all channels set to 0%)" }, { "Value": 3, "Label": "ALARM PROGRAM - alarm sequence turns on (program selected in parameter 38)" } ], "Selected": "INACTIVE - no response to alarm frames", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 30, "Node": 7, "Genre": "Config", "Help": "Alarm of any type (general alarm, water flooding alarm, smoke alarm: CO, CO2, temperature alarm). Default setting 0 (Inactive)", "ValueIDKey": 8444249428983828, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/10696049242669073/,{ "Label": "Alarm sequence program", "Value": 10, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 10, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 38, "Node": 7, "Genre": "Config", "Help": "Program number selected from the 10 available.", "ValueIDKey": 10696049242669073, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/10977524219379734/,{ "Label": "Active PROGRAM alarm time", "Value": 600, "Units": "s", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 65534, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 39, "Node": 7, "Genre": "Config", "Help": "In ALARM PROGRAM mode (see parameter 30), this defines the time in seconds the program lasts (1s->65534s)", "ValueIDKey": 10977524219379734, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/11821949149511700/,{ "Label": "Command class reporting Outputs status change", "Value": { "List": [ { "Value": 0, "Label": "Reporting as a result of inputs and controllers actions (SWITCH MULTILEVEL)" }, { "Value": 1, "Label": "Reporting as a result inputs actions (SWITCH MULTILEVEL)" }, { "Value": 2, "Label": "Reporting as a result inputs actions (COLOUR_CONTROL)" } ], "Selected": "Reporting as a result of inputs and controllers actions (SWITCH MULTILEVEL)", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 42, "Node": 7, "Genre": "Config", "Help": "Specify which command class is used to report output status changes", "ValueIDKey": 11821949149511700, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12103424126222353/,{ "Label": "Reporting 0-10v analog inputs change threshold", "Value": 5, "Units": "*0.1V", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 100, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 43, "Node": 7, "Genre": "Config", "Help": "Parameter defines a value by which input voltage must change in order to be reported to the main controller. New value is calculated based on last reported value: Default setting: 5 (0.5V). Range: 1->100 - (0.1V->10V).", "ValueIDKey": 12103424126222353, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12384899102933014/,{ "Label": "Power load reporting frequency", "Value": 30, "Units": "s", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 65534, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 44, "Node": 7, "Genre": "Config", "Help": "Sent if last reported value differs from the current value. Reports will also be sent in case of polling. Default setting: 30 (30s). Range: 1->65534 (1s->65534s) - interval between reports. Zero means reports are only sent in the case of polling, or at turning OFF the device", "ValueIDKey": 12384899102933014, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12666374079643665/,{ "Label": "Reporting changes in energy consumed by controlled devices", "Value": 10, "Units": "*0.01kWh", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 254, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 45, "Node": 7, "Genre": "Config", "Help": "Interval between energy consumption reports (in kWh). New reported energy consumption value is calculated based on last reported value. 1->254 (0.01kWh->2.54kWh). Zero means changes in consumed energy will not be reported, except in case of polling.", "ValueIDKey": 12666374079643665, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/19984723474120724/,{ "Label": "Response to BRIGHTNESS set to 0%", "Value": { "List": [ { "Value": 0, "Label": "Illumination colour set to white (all channels controlled together)" }, { "Value": 1, "Label": "Last set colour is memorized" } ], "Selected": "Last set colour is memorized", "Selected_id": 1 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 71, "Node": 7, "Genre": "Config", "Help": "Set whether to remember the previous RGB mix after the brightness has fallen to zero (black)", "ValueIDKey": 19984723474120724, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/20266198450831377/,{ "Label": "Starting predefined program", "Value": 1, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 10, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 72, "Node": 7, "Genre": "Config", "Help": "First predefined program to use when device is set to work in RGB/RGBW mode (parameter 14)", "ValueIDKey": 20266198450831377, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/20547673427542036/,{ "Label": "Triple Click Action", "Value": { "List": [ { "Value": 0, "Label": "NODE INFO control frame is sent" }, { "Value": 1, "Label": "Start favourite program" } ], "Selected": "NODE INFO control frame is sent", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 73, "Node": 7, "Genre": "Config", "Help": "Behaviour when an input is triple-clicked", "ValueIDKey": 20547673427542036, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/,{ "NodeID": 7, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/010F:1000:0900", "ZWAProductURL": "", "ProductPic": "images/fibaro/fgrgbwm441.png", "Description": "RGBW Controller", "ProductManualURL": "", "ProductPageURL": "", "InclusionHelp": "", "ExclusionHelp": "", "ResetHelp": "", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "FIBARO RGBW Dimmer", "ProductPicBase64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAAChCAIAAAANwWdbAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nLy9eZwcV3Uvfs69t5bee3pWjSTPjDZrX2x5xRvGMsaAjZ2EJBiIkxCWQEyIEx4Bs2SF5AFhCbxH2AwYSAAHsMHxhi28yKssW7ZlybJGI81oNJqtp/da7r3n/dFWu1RV3ZLJ75f7R3+q69zl3HO+93vOraquRq01IkKbQkTtpEQEAL+xFBGDnUeP/7+Vhj5buv0PSJtqBI9b1YiIMdaq2VL7pGZvdQUAvu/7vi+EEEIwxhCxg0NDmgRPxs4oqMyrkoqgAyAChZCTgtPrbIJTkYaG/p+URt3z35G2OwMBqzYXcNMgLfWUUlprAPB937IspRQRcc5d1xVCcM5DdosCAgC01r7vu66LiOx4aeGMMXYqnbSbUawlT0UaA952o3ZeB79x6dzz/3/S/5kSRJjW2nEcIYTWWkqZSCS01k3GklKapgnHde5MV6HieV69Xm81CZJfE2HNT855k8+iHYZWY+y4r1aKrVDYmZk6zPM3i4bRUdqN9d+Xdh73vy/tsDibhAQAnuc1WaTp9dhWTUiFYmhQGoRpa3TXdRuNRkgU8mnzDGOsCS/OefMgymevdu7tpOEJnOIqb5fi/AZfIRA34UQgdpDG+rJD22ha0K6HDtJYO7RT5lQm2CyMsWCghICVggehblv1m8AKDheaUXS4IM6aBQMlOlD0+KTS+EUQUjFKCf9N6alw5EmlwfPt/BFSKeTgk2YbsWlTh/pwnBvaieDUKDxW7dDJVs+e5zUZK+TjqBFaugXjZhNhrYNWeVV6QsSeIsiWoXoQh7Zg5VgmOBVp0LUhZ5+iNDSfEBQ6jBsLmuAET0pCQfvAyVYCnIjmdk1il347aUiB6Nd2qAp11TrTjNeI2DqIgiyWy6PYeOWz84RjS7sFEa3TTnrqNdtJg/btDIJWlhP7tV2fITSc+vI9ac1XywQnLY7jtEJhtIRWeyxjtU6GMBc8CIEsOlAIOSIqg5NN/lQo/aTmi6W3U5e2DoIkFDwZrRzSLRaO7VZ5u1m8Kp2jNmlnpQ6pxasqr0rz2OyiVbTWRKSUCqKwlftHIfEKsE6dY0Kq/2a8FeK8UMiDiFGaE2udP8WAFTsWtd/Sd0ZSq2HLEyHy1lo3Lz61C+Ltxo1Vo93XkIYYSB6iqsKJXjiVwB2NBkFWa/XTiqHN8yEyE9FR22kZNVZopA4r8qTS0FelFATQ0yHJCHkrVvlYzWM1iX4NoZ8xppQSQkgpW7s5OA6pIFlGdT4VDjtFaYd+ogYJ9dMyQpBiWweh+p01DB63TNHsTYRWUgcXhpRuJw0NGTvDk+YuQUU7k1MUEyEknRTcIeIJgS/qFcZYE1VRxoXAKm+N2PoaumiEHaPPqRBbSDGIAA7i7B/bcztpLGpjFzOc6CMRO3ZwbidFSVQaq31Ujw5qQcS1seQftV20SSi8Ni0SHTRIKkFoRlmnmcOGtHUcxzAMRPR937btWq3WaDTGx8dLpZKU8qKLLjJNM4SzWCt1Lu3mG1oSIeU7k19Ik5C52ukQOo72L+BkWI6uxZZvQvEl2E9nKZyI/aB1IJKSt4tT0TXaOdCEhutsazyembZwxhhzHKder3ue19/fPz8/n0gkTNM8fPiwEOK+++4zTfP0009/8MEHP/ShD+3YsWNsbMyyrMsvvzyZTJqmicdvF8ZOJ2qNYDmpKWJhdNKZtqOi2ArtFIiulmYRIQB2iE3txmiZvt2sotLo1xCRdLZysPNonWaFYAIUDUathk1RM0NyXdcUxvT0dLlcXr5ixZNPPjE9Pd3f3z83N3fJJZfc/+vtQoh77rnnTW96U39//3PPPbdkyZKRkZEDBw4U54vXvuWasUOHdjz88NTk0Wq1Oj4+3t3dXS6XJyYmOOdbtmyB45e82xm2XfiOdUfUEe0oFgKw6+DfDswCJwLoVNYnNnOsDhOLahzCRGzvpwj8zgux3erscCYKUDgevJRSUspGo1Eul03TrFQqs7OzhUJhyZIlnPM9e/Zs3br1ySee6C50v7B3L2Nsenp67NChSy65RCl5yy23jCwbmZqauv7668fHxzdt2oSIUsqDBw8eOnTo8OHD+Vw+m8stXbrU8/3Vq1fv3LnTMIxUKlWr1Xp7e5PJ5KkzZahCh8od8rPYM+38+6qkp9JzU7GYXWGoQTtIxjbprNCrqtOOY9utvNjcpVQqvfTSS6VS6bzzztuxY0cul9u5c+fatWur1erIyMj4+Hgqlerq6jp69CgAeJ6/+7lnL7nkknQ6PT4+vnPXU3v37c3n85vP2PLkzp3N6wjNiNZUQCnV19c3MzMjlSSgyaOTiWRixaqVd99997Zt2xzHQcR6vT43N5fL5UzTbHfH9xTN9aqkJyWn30yNzpgLSkWQPGJDdahNlHhjs5nWQVR60ngXSstiQ0MU2dGoR0S1Wu3AgQOPPvro8uXLHcfZtm1bo9FYunTpjh07BgcHW62aoGEMywslrfSTjz9x6NChfDa3ZdNmx3GOHZ0yTfPOu+96xzve0bwO0szfly9fPjQ0VK1WtVQ3f+vbqVTqda+9VEr5xje+ccWKFfV6fcmSJY7jJBIJIUS7abYrr1Ya4owOBNZZ2plH2jWHCBGIdlVPPbpFff8bS9tNMmSvWKSG4BV8MvbSSy99+OGHs9mslLIpLRQKp5122vz8fLOC53mu69br9dOGhg4ceCmdTmuthRBjh8bq9Xo2m92wceP4kQkiWrdunW3bALBp0ybbtoUQZ555pmmY6zdsSCWTwjAQgZ34gF4HLjl1AmgnDQX94Jl2i7ADwqKqwombtnZfQ1QCoVs67caLKtdOGmuOdtLoPKPKQARnUcrs3AoAurq6hBCjo6OtywREtHfv3nq9/uijj2YymSNHjmzfvt2y7TPPPHP//v3CNN589VWVSsX3/f6Bgd7eXsuyrr/+egA455xzmj13d3c3O08kEgBQ6C60yxxCnuhgqOhkTyoNjhjKHE4a8jr4onMPsdXCJ4NX5UPtYzcCseGpHbW8KikElojv++0UCwGx3Qpr7grn5ub27t3b09OzdOnS0dHRjRs3Tk1N5fP5Q4cOaa2XLl1qGAYRJRKJZttWGtR8tjOo5KlMvzV6lE6iU4jdu0TbnlTqOE61WsVIPhr1HQSAGDqILv5Qk1gqbTdTxtipPtEWndtJT3aQxjomqImUMkjpnRmxVQ0C2Ar5g4iazwS3bNQBoLG+DJXoammnXjvF2kEz2ryz1HXdarUKkSUX0jY0wVhgYYBcY1WFjsZpmYIxFpNjtasdBHKIwKJT6iAN9RDUPmqRllfaOSw0XHDE1mfrRl4rFDbrNEmxebJFckGiCoEmROFRJaOmh4ibQ/wXa+RQw87SDgaJ7acDTGPbthu6xWTRKUP0lk6sliEYQcCRoeGDmrWTtiOwdo5pR5An9gkArVmg9Jv4IEBkjI5fLAUiYgDIGRGRJkQAJA3q5d4AGOfUZDWlgTQwIAKpFEfOiICIAAkBAUirlpP0yyBsmhiQmS+rTaSJCKipICISMEBEBMaa2iJATD4e5I9Tl7ZzX8iSHWJOLIxifRE9CLWKeR4rxCtBpTGSQEQnGWL+qDQ6vWC12Jm0s0gIW0ppranp3GbU45yRJg2AyIg0IijdfLgRifTxxYsAQFpDsyfSSmsiDQAMAEELAiJfNXeaAAiggAAIEJAhNB8epOP6IGpTIoA+/qgPAidADQTIEBkgMsYBBGMcMd6YIVd1lkZFnUtstXY9dBg0WjMoFbHeiu06dhG0G6CDNDpKh0gXy20hBm5ZnzQoRdSEBQEyFFwgaz5lCwQaATRwBGAMSTcfJ0LQihFprVAqpjWg1qQ9zyHAhJVAAmAakJCQK197HipJQBoRGCJjwJGa+wwCwmZgtZFx0pq0BgQNQMi0boIIARkJA+Hle5GhyBhLGJ2lsUaLXcAnLR0qR6NEtH5QKjorERslozU7R/EOQTA4SmgIjHsAJhptERGg1RBJge9rrVRTpARwBkIwwmZkbAYlAgRG2nMd0JpppbTW0kPf8xp11NJA4NIF6XuJtMh3MVMgSVAgSHmlIvgOA0LGiDMUnBlcIyCAfhnNDLw0Exy0Bq1RcMU5IEfNAJCAAeMEpBkncUIeAhF6aLfCY6UQQVXUZbGt2rkj6K9Q9hZNWkIBGkKMFcsi0SXVbs7B+UTtFTvbkGbBMNpZb4jsdACACKTUTsOXnm6GQsaUxbUpGOcvA4tpYkCafAPBX1gw6g3SCpUPvkeeg/UqNerK97j00SnJwcV8zVrtMAEgAYh8Xp9nTp2UhwKJM+KMmQINgZxxIgJAxkjNoRBMStAazSRZeQCOmmliihtEJjFBpDoYJFbUQRpNvKLSzpCKrRN1IpzIDlESPYGxIMIlQY6BCJ46AwgC7j8VaWisk5ZXAt8rtkAkUERSac+FyoKrXKUlAqcU85GhNoRhMo6AWpHvoZQaJFkgDh1TlbJQHvPqWjrg1axyTXpVUS8zWdMMzYECNsoGcUCOytEMmWEAJqjSgHoFDEDBQBlICIyBZoBAFgcCkIDSRwJmFlAjMFMBY4q4bzjItJEFDdw3EQQzERAIoLkliKXtkF+j0qBlojRxKoZtxykQt4BjW4WKiK3RgW+jE/iNpVG8RkePIj5WN41aA3lS1WpOecHxSj42NDDtM20nMJEQAFp5DUFacx+VEiRBaDo4xmemmfaYX7c9l6TDGjXu1bBRI7/Blw/rTJpqHqFLpJjySEtCINQMGuSVyPHAZJRJIYCUknHOTUNLhYBkgEIpDIukB+igyHBCTXXlN1ClmFgueA6Eq7lHKAAMJAYEyDpFxljXtLN27JI+xRILzQ6QCI1F7W7pQITuoA2ioxWiWVSsNJoNNJdFKBRCBFuxNiIgAvKlch3pVGW17Dfm61hqgC9d0jmOyEmphtCuaVBCeAK0DYpsrcaPiMOHNbikGlp7qHx0a8qrc1cpBvx0watVrHqOnLZ8DUDoVIg8tA0wAesLzGtAUpCZJztvIGglQdqMCUAfTI7K19JBn4NyQdQQgOkq6AbzUFcdbRAYI8g4agHAkBQDAjxhnYciQ2x+eVI2Omk+3rl5J7N3bP7yBVJogzuIg22IYCDg/g6M3ZJ2IKHYaXRgzdZApFC76JZVddarTztywcGSY9elnizVporKdbPcSaYwmRVm2sVGjYoLjHm8VtGyYpAE7SpygREJoHxa92X8xX1qeKnICDF+lB07KjMp1C4vT/nFo8xEyBhceFo10Dcg3Y1WPzke+gq6enW2wJII2uXga89HQMASKY0CkBT367ZfrNeeLC88b/dfmuw+nRkZCaYGyRhwwND7FKJLsZ1roziLZlpw4kKNGjbq9NBYISKA9rTyyhKJzdZje4c2aI2O2k4anHy0t1hzxH49fhKUpyvF+uxkafpwaWF0ARbclEZnomS+OGMv1BLSS+tKgjmWpRO2Q1yBXyXTYyiZoSHLYbAbexOYSSEolhaYyWB/j2Gm1WRRo8f7e6i3n9XmlTsnkjair1QZEsQsrRMN7U8xp0S1GvqA2RpLemQl9UKFe4oBgHRJlxXWGHDUBjTmuTOfJgN0uTHlzi8cznSvTeaHgNkETKOOeivk6Q77p2i+FarQrqt2uVS7jeSpxFkRrRHLw9GWsQl46MypS0OLILh0Wnl6cOiWskQoparVvdkZd3bCKY6WGgdLNNcgV2Yn3OSMn/MhKf0E+pZ2LSTgLlpEqLklaTjjr+tlNme1EozPq4UxXJwDJFrWbYwdoskpUD7kk74nccNKtmatTOatVWu08sEpwuw+EiVmuMAlQY3lPQ1SWS8xPEyScU9qT3FDKa/MeEMYiqRASoAmZSIXZopNO94LTHZxsYmQ0PcQODKB9Mr972goDLmmcyiMXfyxqIrGmRZ2Q7u3aM/tFAhfSglpEOKw6J4leBBCQAfpqQS76BwgslaIQErleX61qmoLfn3a0SVl+iLpMPOQmyuxtMMSWhlEBAyZqbVP5DJBNJKBLasUOnxyGkljsUYDOTWUMgzJckm5Z4obDEjTQJ9iTGR9duBFVTpqmqDmjuDyRZS1aPHrkM3oo8+jOaO9IphSdCWkcInKJBFAauYzkwE0tHa5CQAeSNK2LbLnK0wqbyZvZQoDF7PcGkmMaXD9BinFGGMQb5zYPKRdAtOyXsiJ7VwT+k1zO0eEANdCRbRVzJX3aOmgWWjgdkE6JA1OuDMvRvU+XpkRodZaKU0EdoKnk5DNUX551lqcYY4v9i+Iu0YTqi7RMRlxElp7Htf2skEczlJ1xp2YNc5ZrtPMq5TNM4Zgeo6Vi5S16PA0W6irvhSrMn/3jJ1Ow9YeShHzOKVI12f4ERLQB/6Yb+TE6Rer6d1cjEKqrA0fhGDchIUaOIpzJKnQtjSTxFB6jrC7WGIbZa5C7DO8F6my1zMsUzAOxBRnZIJWL5N14H55dKMDJ3JJ0MIho3UIjk1pkx1DdAgR5MU2b9dzs8RceQ8lQx3SqQ5Tiq2JkQwsyGEd4BXSrVlNa5K+BgKDCzR0T0+6cGZKuz41fOl5mTXdMweOsGdqhhYmeYLISzF71SIw0X9xnC3PGVuWggNwkJm+QX0cG5IJk/bPyWpdHyuatgVHKny6DCtIPTvHhIMXbKXuAaO2AKVDVKpifgnjWh7bb+SWo9Ej8XlGBzkDIkHMZiaAqSnBUDDGTQACI0N8I4rzlbkIzSE0e5RhiEQfAWpVV65WhEhca42cIXRy26mkOB1Ky86c8+bdzODPh0LrP+SpzjR0QvIe5aHYaAXtfR9ERpQbo9LQ9DqM2EGr5mrTWnJuIDCftOO4mXSa91gcAOfdmnssv2YA5ziNzySImSmVOmvYK1Zgz2GxMustzePMAhQ9uP8Q1y574oDe1KMajvVsQ1sIvCCLJl2wkpcr2imT70IuQ1ZeWdx99mkrpbDi0TEUI6sk6/doynTQWHQu1WxJY4IY5GySABahbSAIkA5oKXg/sPUguhlmADOU6OXJdQgaNAMtqrVpYoKLFHDRvLsd3cFAm0UOkcgYSoxi0wnXdfc8//yGDRsN04DjDza2jByydjRitINES3ry92u1g1Gwo3a5V6z01Etsw+OdE2PYfFyAccYMMFIsmTcTCQOV0r7EmjY9bbiupRCzxC9YBmNzllJ862mwbtBIpQyP+L5JWNerF+X8epoer5k9Q36hH+qC1020TWO+zLCBhku9WRxe4lUW8NiEPTjIBoaUAlKOllok+i1r2GNaOs9Qcj1LbdB2Eo0M2AUQXchzALb2GXkcVDdhAYwCiiwiA/ARXAAABAAETkRSK19qpRXpNjdzOlsmhCc8vu+J1mSMVSqVT37iE1NHj3JkzTviHWAUBG4sDUWlbZ95j42PUTIMHZ+iNJgBRHc9oebtYisRGYaBgAgoOOUGUoKhnHX5gnIIU5T0yEyQMAqWOHMxqxTVhkHo58oiKpbZ88f8YzNi8zIhq7gs4T3R0NPlxp5jybNPk087upDiK0zdb0MakTyDOBmWWTvCFkratIhlfAttwahyQKcyuLDPWASgu5WzkycvU4bB6AgCEQggRlhFngA0ifeB2UtmF4okEYB0AF0QBUAC5iNXCJxAAykCajJW1Fax1oj1XchQoYZa62w2u3HDxm9985vX/tZv5XK53r7eVDrdISiFMrxYBYLHbR+b6QDMaAlVbgevWIVipbGdB9siIgBxLgBQ+tKw0GKpyrEqzUlVkqwu/YpWXLD+DCzrV2PTdMlKP1Mz5z3OG9pDqCu2dogvLciax3pSbOywOLLAjjhqg+13A9uUogGfezXaX9ICxYZhqlbZ2GGSNV3oYr1Z0+wHbjJWg/qoMitQRjDykEnA/GN88DUkq8BrCCYAkiIGDIw8mL0kcsCTwCwkBqCRfK0dIFCq6PvzoG3GDan85qNajL8y72h+E2uraJ7ULlYQkeu6YwcP7tu3745f/pIL8d73ve8P/vD6aO4b+hr1b9TRzUFFcDUEdQq26Zzdxw7fQRq1S+fEsD0LMiklEUhfmjZrzPuNI5LNK2+qwmacRJ0Jn3DjInxmAi5cqbO+va+KNc+Vc8Zc3e9KW4u7dLkCR3y471lhE65Nixd9VXUNy+bSIy1o/KC34jQz2a2efxGgpJMmE1nWRWx+gU5bJt1pfrSGS3w2tBVhRpePgnkayCmEQyAuIngOqAh+jbwyAAFHQAuZgObzWQyBM1KAVAPtkz8r3WmkLEvmtFYKfKUFMv5ymDzRl6GDoME7uyB4BhHT6fRnP/e5H3zvlkw2ky8UtmzZwiIM0tmhHXwEsY8mR/EUy17RYdrhPQKIcIm1TjM/wONvYQg8rs6kVFqS66paxQdfm5wrIRolV9UUlR1WIlXUNFvBxT24bxa6UsbRqmogTXowtWAkiAay4oxe5dWgqJmr/UV5URDgVlH3M9+m5WmvUTKntLbzom6pDKAAwDQCVxmF7pxDvo0awEJbacyy5FLi3eDPc63BTIOaB79b8hQXRWJZzuYAXWAeMAloA9pADsgZQAcgBag01rnRncymfZ8nUgVErjRwwQgolFCHdj9RG8ZiLuT7pg05Msdxv/iFL+za+dT5rzn/4NjYxo0bIcA3wQf/g/+dcVLXt8oJ9wo7E0Ysr7RLBTpI2xFh62RL1+b7gJVSyWTSsix4+c3VBACNulctem6JVM1LJuw6uVBRasbhC1pXpa67mLI0Rx9RLMlyICq6ZCWp24M+gyeQjtb07hdpIMNPXywaZc3qaNpsc8p7YJI/NYOXLfZrU0YiCQtlSpHWWiQNxaookkzkrJTw6rOUK0D9GPEhUBWUk8AUqSJYBVJzQIcNY6OUs9w2QGeRa2JZAKX1DKoMA0Y0DVgGMAHTDGxNmUSiJ5VOIZhN72ogrRRizE/yO28MQ6lYaOtNxy+EaqK52dkXX3zxIx/96DO7n9kyMnLb7bevWbeOcRaq32rVelVO6D9ygkPE51gnTbaCJ4NfO8S4dtLgViCoVvO81npycnL37t0HDx6cn59v/nx5/fr1V155ZfMFG81nzGWDqMqhLnxXmmluKz43W0z6NrhEHKAnwxfqsishui08UsapBvSm0ZRgSiJSL80yO+unkmgL37DslK0NH0mLS4foSNmYWnDXFHDsoPRd9G3ozsv5okim9ewUJUFbWSNpANWody0DRx17TOQFQJ38eRRDSDXFyoySzFpJNIF2D4EAKCAUGCDoaYAi0mGCGUIHqQtYP5BQ/lIAgzEBBICMtCZNhDGJR5SHQh6FAPdHXdCszJElk8mEnaiUSqQ1IFYqFYAT0vDmm1QYY4ZhKKWaF72klM1f0cX2HBxXhLwe8nQHmmkHtVBpJ43SdfN4fn7+7rvv/s///E/TNAcGBpo/OB4dHd2+ffsjjzzyZ392w9DQMBEhB9LSczwCZWUsK42GaWaX5PWM5xb9ZG/e9T3NVXpR3k8arKbRV4b2UNtgI80tsFSeoWOt6NPasft6lazSZBGEqfKOiXNgo7XgEbd1EgVxzBaICd+WRvdiT4DIZbSvlG2bhV6/ss9QJaAUWRylASKppYmmTTDO+DDosiKbgwDIEKaBZQBSQFUAAlAIFaA66RrobuWh7yoUKak5Q4tzGxkivnLTMNZ6HdATe4zHfwmHgL29vW+77m3/9OnPEIJt23/793/PhQh22MQQET300ENTU1Pd3d0bN27s6upqoSrWua9E20996lNRHETjeohU4UQIRifTThq1RZC99+3b97nPfe6ee+5ZtGjRlVdeec0111xwwQWbNm3q7e09cODA3Nzc2NjYxo2bksmkIQQj7nt+75J8sttMpu26cjgXJjNq86VEJi0dN9OVbxSrcsE15+piaV7Va1TI0kKFPMUsrgcsneGs4dPMPB6bhVKZZxku1CFlUlorUmhyYorKdZ7Py7QwlILx/ZBKsNVbcHg998rgHCXuoE2QTEIStZSQWYpKcrMAfAZwBKiEkECyEBkxQcwAxghnCScRF4hXkZWBl6Ty6rVGveqU69JtuFIiogGAjLfeGNuMkGFjNl0jpfR9PwSv2EDRTFg5Y/fee++Oh3fYttXf11foLlxxxRUXXXyxaVnB/psDNI+f3rXLcZw1a9Y0//CnnU+D508AVrtsPTSTV8aOi3QtLHaOkiEINlfG3/zN30xOTp5zzjkf+MAHzjvvvEwmY1lWKpVatmzZqlWr7r333lJpYd++va973aVIzE7z3CIz2WWCQOkSl4w80MAQJBNcV0keLeNhD6f9uicNYfC+HBmEk1XWY0oLzOFFWGtAw9XlKjJCoQl8Va9w2yDfhf4MQYPPVvWiLPccrJVhdopMA3t6sLubgFRtFLnPuQEZg9IGgxqZGW4BqQWW3gw0j7xHswKDKgIjQEQDOEcEZA1iVWCIrBd1H1BKS3ScdN0xGo7nuaiVqYkjcs5bfxMHTd/FxjWlVAtYGJfjn+BTRM74rf/xo3+/5Zb77rvvqZ07jxwef/zxx81k4owzzmjl6QjIkCEAAqRT6RXLVzTqjUw6k8lmWgMFxwrBAxHDOVZoX9bSLHqhocVJQVYLRswO0lCp1+s/+MEPbrnllp6enuuvv/7KK69sRvRgb5s3b37Pe97zjW9847HHHvv5z3/+5jdfTUQISD4pqbUtsYba12Wnkcrl5VhVe1DXmEiYql4TqaTM592pY1apZg0PwqoEMJc4+dNHRTojDPSxzH0AC4ylA+RU2YImMBiZygOj5gPzmFNysWYPrdS5BFSnvPEpc6AAXX1aANhVEEor4HZeygqaXYrmEADIZywPlABWBuYCMxATwJYCrmZwHkAdqEx4SMGch64WXcIqoEQiJpV2XY9xwQUTRvNnINDEVsjC7eIdxLHIy1IAIr1p8+Z0OmUnEik7QUr7Sq3dvKHVZ9P4moghAwTf92+++WbTNDdv2YJxTzGFFGiWV35iH42a7RqHAmIUPbEbSYyLfQAwPz//la985aGHHhoaGgk2CGoAACAASURBVLrhhhs2btzYDO3NWN76dbxS6rLLLtu1a1epVPra17629eyz+7r7tQva9UGDYYv6lFM/0mC2AAfkRMWTioPRMJ3EYJJZ3AVHppnIFypzC+l5jgtFX2gu0lSpQVeaez6QIkZUdPXqAaM/p8tFpXzGgLhmris5mmvPJCvBTOVXJ0zTYabwLYs5LwEnBBstJCGJXGHapB2ELACA5qAZiQZgFYBA5zTMMQDAHKANVCU2rVVRSs91ueMwz+1RSiK6ns+AMSGEZZuGEfZjaOsXD6B2iS+B4GLvi/vuvvturRQqjQS+kteJP9i69SyttO/7nDEhBHJGQABYrlSGR0Ys0/RcN/oa1XY6xDzKEntBocPm8VWV0HDT09Of/vSn9+zZMzIy8pGPfGRkZKQpbXJ7c5fbPNMMCnv27Pn6178+Pj5+6aWX/dE73l2b892Sywm5FlP3HcESZUYK9bmiMS3NgZw3XhY5y5+vEUJGa2aSpZRRIixIY3aOJRFMF2wJ3BXYAHC0W1W2YmlkjSqOTmizrkDxtE3ch4LJuCThEXN87turVipTkwlgVnkXUCKHyZxmDseCsnKESYQMMhMgraGIvIpYJhSaehH6GGYRTEKfsAYwRsr3XV5vpF0v7XgpJVMAOWHYlmWlkslUOiEEB9DBi6Uh1zTfNhPyXWxezxARkAAaTqNer9cr1Qd+df9CsbhsxfINZ25ZsXzF008//ZOf/GT16tWIaFnWm9/8ZjuRKM7P33bbbVrr1WvWnHveucH3XwRhExz0lZeCxCoNJ0ukgtPrgKQgSwVP7tu378tf/vKePXsuueSS97///YVCoVWh2WHrpR3Nk1rrFStWDA4Ouq77s5/+9A2XXm34aeUoTeCUpZ0ypydn7VrKq7vaJ65kQ5Zzdl4LX0ntj3TXS3M93bmF0ZlUj51aNCi5m0gACN93q5jlul4iWxkCVNaQtQW2abkStjAtYghAmLYJFKY0GGjbGZnqY0xwVQXh6VQGOSLnSHWiPmK+YAwgCaiBDMY8AhfRBfAROQICSAAPUANZjBYr8jRqzThxQxNKqQElgRTcaD5qBkBNewRDYazLQiUkRUQiACRATKVSpmXe/M1v/egHP0wkEtl87uvf+pYmvXrN6ktee8mRiSObN28eGRmxbAsR9u/fr6S0bDudSkUTnnbwiv8jzBBLQZsVEIVOrLRZQq+bevzxx2+++ebR0dGrrrrquuuuy2QyQY5tZaNBGzU72b9//0033bT/pX0ffN+Hr7ny97WjkbQydMpAZ56ogU65ohuacUMTSaUNTcJmVtbWTCKpJCTIczzhW1mTGg1Da2QKLIXKI68GJikO3EB068Q0A/QYGPkc+YpIcsGAc80QBdPK4YYG0wAjRRwREJAD9wHrSD0ACIhECKCbv3XVJAFdhpLAA/ABNBEx3dDgOa6s1QzHMauO8BwTIctZ0jKTmWyq0J0xDE4EiDE3W1qMVavVQp6O/YqB2xhTU1Pvf+/7/vc//XO+K//9739fA9z4V38JAAxZtVp96IEHRkdH3/Xe95imOTN17Be3355Kp69845XZXK71cp6gg0LAYq2nB6N4D4IxyF4hnAU3ku2yKAz8mw8AaK2fe+65H//4x2NjY9ddd9273vWubDbbUqvzKtRar1y5sr+/3zDFL++6jdmap5BMQi0cIp5jUviaCWUaErVhCsEILaYNcKTDDRQJwwHpAyFxqJMgSyogSZqYBgbchIY0qg1WqlDDoVqDHMfUmsplXVngTgWkA7qBqg6yxLFKuobMA3SaHEOsQlQhLYgEESc6vnwJAAQQZ5ggzVEDkkbyUDcUNHxV91RdkefLhufXpXJ933Uc13Fc6SulXvm/mpDNY0NB6Ew0Y27+y9LLN4sAe3p6evr61m3YMDc313T0/Pz8rqeeKhaLw8PDzcek+/r6Lr/88v7+/lq11sJDCwxRkDXLCf9XGDqOklCwR2i/PkKZe+u4aaNHH330pz/96QsvvHDDDTds27at9Z7+5uXddpAKjnX55ZdPHDm8Z++eY3OTfYUBgULXwGtozZWVNhfmKqYQSEhIGrQCQzsaXSV930qaCsBAjgpIgiF9I20BM4TrETGybPA8XS6hCZCyGAMiXy8scJIAWuW6uSWQFOg6yYY2GKbSGhWAz9AmvqChDLqbUQGYD4BACMgRhef6xWI5mbAr1XL/orxgWoMHoIhJpT1PO77WruSuFL6HCTNpWXnf49IDKTURaE2cU+vSUrvtXjvwhVzesnZPT89ZZ5113XVvG1m2bM+ePX/+F3/BGd/97O4vfeGLW7duXb9+fU93d/NdS7V67bbbblu8ZEkqnQ4NFErkgwrwT37yk6GI0zpuNYsCP3p8UmkTOvfcc8+tt946Ojr6mc985txzzw3+HKU1ZzieXQUVC6I5kUjs2/vSwZdGV688fcXy05VSnna9qk/K87Ce6+r2ayS1PjZ3bPTwQQ4ia6dKM9MzR48cO3a06hctzg4e3F+tlnN2Yt/BA0/ue3aZlTs6P3vXIw8Nn7YUtMcssXP3c1+89fZ7n9yZtOzBbIbZJqS7WCIhZY28IuMSbAsTaRK2Ivb4Y7tcfzafG+DYA7yGTDZhBcgBxK0/+dlf3PBXppn+7D9/8Y1vutJOGoQugae05yvXcV1fketyjlnbWpS088lUl20npSQlwbYNw+TBP9ZqRQYIkJDnebEGD3kTjtMEY6xYLN59510bN24koquvvvot11zDBV9YWDjrrLO68nmGODExsWhwkAt++89uG1m2rF6vr1i5wrLtEDZiUYWI8W9Nbod3iETT0DSC/BSsT0Se5z3wwAO33nprpVL5xCc+sX79+hDDhZQLahI8RsSBgYG1a9c899zzOx5/4qILX2+AefvP/uPWW7+NKDxfXbHtijde8O6nnn3wq9//53qtOlBY9tH3feL+++659ZHvCTMpdXXrmrMHcgPTMxP/+N7P3nrfz+/e9avX/N33H9u3+4vf/j+v37IllS08c2j/+770r2nD9hluf/y57/39X67qX8I4eN4xrh1mo07nMJ1RpBmKeqP++c99+6prX7N8+XkAvtKeJFdgxqnXDKPXMNFXXq3GSAMok0D5vjk761u2SmV1teE2HN1wawtzdNrilZboUdLQGqX0LZtxJpQk0qy5JYzmslHjh7zWbuUTgGlbC5XyG9/8pnPPP8+ybWCMARbni8NDQ67jDA0PW7Zdq1QNYSSTycu2bfv19u2zs7PpTKZd7hQq8X/S1EGt6A4xiLZQ0GyhSkr50EMP/fCHP6zVah/72MfWr18ffCljsAlE+Dw6umVZixb1Lx1avOeF53zpMmTF4ky5Vrnm6utmZ+e+c8s3hwc23/LDfxtZsvrCc7f9+Pav//TeWwxKd+VyN1z/oZdGj9523/cHLxo+uOtouVo9cPSlYrU0VS2/dPTowOCSpJnySs4td/9yINX1T+9/h+b0/k//6707Hl48kHrgV7uPVKYueu3Z/SP5u375dG9f6rHHdm3aslkY/Ohk6b57ds9Of6evP+fUIdMt3Vryrjvv7+rOffjDHyZSBIBMIUPf01/4/HfuuP1XPT3iE3/37vvvf3zv8/OO56Qzyb/68NkKFZEg0giYSCYQmn99eIK1Q8ZvF4mi1jsBdgAMWdpO/P3HP7l+44Z0Ov36K6+87PJtnDFN1Mzsmp2nUqlsNvudm282DGPz5s1RLohlJQjehI5FUrTE1onOFgPZpdb6/vvv//GPf1yv17/0pS/19fVF20ZRFSotumpWGFw02NPTtW/fPs9rJBIpBVAoDF533fWlUvUXd9y6a98jM6Xxmz7y2aX9yxb3njY5MzF6+HnLsBbn1x3LzOXTXVtWb/3F7T8dq03M1IvZdOG5I/v2Hxnfsnyznlcz9fld+/e+8+pr1qxZB37l/378RtOS/3LzT+/cfWjjxt7vf/hfP/rRP/6bm766ev36nt7C97772Rv/6v1KMdOGX/96xwvPj61dt6J/oOvxJ5/7k/f91mMP7f/zG268ZNtZwJSnXMb4T378s5/99Gcf/8Qn7r/v3v/14a9sWrd8+72PvuHN52678lxNc26dE2hDcESO0LRcM9ltuyWMNQ5GUvuXpQDNe0MMGWpKJpOve/3l9Ubj2PSxI5NHkDHX9267/bZKpbL/wEuVSuWaa65BhhddcrHnuradACLC8JXIdsgWUbLpXKKziq3Tqial/PWvf/3DH/4QAL7whS/09PTg8Wd6gl216zP2pO/7uXzBMCzf96anZ62+vFPz52anv/H1r00cGV+/blO+N0+m0dO1+M77/vOppx/o6l/iunLi2MGbPnvDQu3YqlXrlvWPpFL84ed2GIZ13sZNT7/49OSxI2/Z+loAqyqpPl/u7cqRyHNhLF9CE7NTDz21/yMfuPa1l6/5k/f8n7vvfsawxQc+dN3KVYNvfv0Dp6/uGlxSuPT1W596YrS04H7m83/5+c/cfN5FW373+ku3bLz4fX98w/LTlwAi56Lhyh0PPZ/LdT/x5ANTs0cOjh7r7+4dWJJ/1weuEIZFxJX0gZghNGdCKSWEIYTRLpwF12E0hsTnGIiAyADmZmYe2/HI2NjBD914ozCNRx59ZHL6mNbaNq3XXnxJT28vEEkle7p7PNf79re/LX1/cHBw27Zt6WwmpEZUt5cZK3Qq9AkncklsEOzANL7vP/LIIz/4wQ845x/72Mf6+vqCGXosmcdmD6HzCJjNZJv/PFJaKC8uMESjXi8/s/vx+fnikqWLhYnA0ZOmR97YzIFnx/edu+n8we6RD73/M7NHJ778w7979sCegZ4lTzz2QKGrZ8uqLf/+i296vrt0eJnMZ9J+OplJTx04CIuXeGbtW3fde3C+WJPekrUb0l1L0j250SNHkSXSWWnZlDDzpBqI5LvAmOpfnO5flC2V54cGhzjPpTLzno+OqxCF1qgkNOoVBtpxa6cN961cPXJw30Qqkxa24bg+N6xEwpa+IK01aKWJM40vv8j0lS1OECitDPqk6/yVmkCI7MV9+77wL/9SXij9rw/dyDl3PPc9730vQ3Qcp1KpIKJpmolEgnHGOb/6qqueeuop3/ebt9qadxJfAWvANUEdWCzwo7WjaIv6O4QtrfWOHTu+973vNRqNv/3bvx0eHg6iJHi9NGiCEKoCXxEREDUAac1MK8m5WSzOHRzd7/vaSsNA3/An/uprH3zP5599drdQ3arhPPfCrtdd8NbXnf9WJrWnlG2mFnctWz603uSJmdmZ4UXr9xx+dmjgtNXL1x2cPyRMq7dnmIORHVy6bPGK/3rk4ZnZYwcPT3z7rgeT2bRtp6aPjVd8PjdbXL+mV0lJ5HOuNEjNfNC+V1eafKmlYu7wstP37R1tlNXuZ3Zn83Z3b5/SikBbCVy9eqVhsj+74e0Xvuas6cmpfN4i0FqT0lapPl33HOLMU9BwpeeRLxXjBKBJYzOaRY0TclzIgK3QEfAKaaJNW7Z8/stfOveiC77wlS9/5nP/+wtf+dff+f3fa17SKJVKhw8f3rNnz84nn/Sl1EAzMzOe6yaTyebmtPV/4xC3RW2VmHc3xDJQbJ3oDIMnd+zY8d3vfrdarX7+859ftGhRMN7F89ApXKc5vj8ChmLJ4LAv5f4D+y48x/ElOI4zN62Um3PqZd1Irjv9NV/4xgeXLRs5PHnMzHIrZ+8/uuemf3l3vTwpTPP0tWemUj3ikR+sWLmiu6+QMNNDS5YlU+mGI82S96cX/+6Hvv13v/1PXya20NfV9SdXXdKTNz7zt9/qWXQHMHblVVf8+D8eRkQFUlhCmInexbkv/etPkBtrNg4o8q774yv++s/3/N5VH6j7pfd98H3IWTKbYCalstY7/viNn7xp/7VX/SVy/ac3/M6Lew8lMylfC0VQrVXLxclcFkDZSjLTsIQQuomM41eXgwCKOiXWU60SbJJMJlevXj0yPPzXf/3XpmkS4tvf/vbrrrvOsq2tZ21duvS0SqVsCMM0Tdf17t9+fzqZYs3/c7StFmMFiSYEegAIP8LcThWIo+IoOJpFa71///4vfvGLc3NzH/vYx9atW9e6XhW61h8b+IgodEsHAAAYoibSRCB9pjx44MEdN374PWtWrf3z93768MTeZx4dvezC3/N9fff2f9u64Q3pfNdDT95SdSpnbbm4Viklrb6Dh58EhGQiv/nMrWuXbzl2ZHrX8/e/Zu35Pdm+O3fcMdg1sH5onSCWbHg21sbKB+7eeW867V56xuqhkVS5235s956KK7eet3lgcXLHgwc2nHVaMmHs3LVv1drhSqW2d+/hZMruKnSvWrHJZMlj45W9L+xJ9FqnrRiSHi3MOl15cGo4tDRfKfkHDr5gJcRpI7nifL1UdowU1urkOmajbFo8l7J7SQqGvFDI9fTm7IQwTYOzE8wVzFhc1w3e0gnZLZTANJswwJmZmev/4A8uuvDCXC7n+v7ZZ5990cUXH5k88utf//rCCy+871f3JROJt/7e7xLC3uf2PPfss6ZlXXHFFcl0KhYkofLyX55ErxHErozocQgZreaHDh365je/uXPnzn/4h39o/rlosG27aQd7iLtXiEQakbQG3wNZhxf3H/7Ah94ulX/TjV9dvWrN5Eta+3Ui4fsqkTI9l7r7UsC1W0XlerLB7Yw0klxK0MoXiKZgJKWphfaVj8BcLZRrG4YpRIY7ab3AxSwkqtqYNQZt3WWyLNcGRyOpyUO0FHoKXAWeIpsBEAnBkgIySHmCqiJZU/Wq7wBykyUFppSctUTWNiRqG1jD8xsNf9aXnuvpSk3VHKpWmFO1yLdTRk/CyFrCTmfsnt5MNmtbtgEnMkTQXK3/0okCKxQNWp8c2cLCwp9/8INf//rXc7kcICqltNbbt29ftmzZxMREoVAolUqLFg8OLl585x3/JT2vt69v69atiVQyyETRjByO08crobADIQWl7divdXJycvLmm2++5557vva1r23YsCG2ftAKUViH4H9cq5c3y4hMa11v+KaRHhgYfPa5pw4dPjQ0sIG050sg1J5vJIXw3eLEuMpmRa1RJwUcLK/B0pZBuu7VuPRVNqtqZWmYmgOXUgtgQjOfqYZbHuxKc1FIat8A8lTR4SKVzpBFwEkiQ8goqJTntJXMMwMABCOhJSOWq/hFV+0DFBKENpCYZVISJJoJA42cQQkGVWSerx3OwVBJCVzpBjChtIdcIEflsbrje04ln2G+xy3TNAyjwyKM2iomrzrBjAAAWmvTMtOp1Nvf9rbh4REm+LZt217/hisIiHE+MTGxZu3aubk5LrgwxJo1a57ZtWtyclJr3fodXpQ7QyoJipBnlFeC7UPYb4maQK5Wqz/60Y8efPDBG2+8ccOGDaFqHRK1UG8QKcdPImnme9J1Qfqip2sxqZ0Hx/afsUp6UrmK20lUDtYd2XAFB/HEUw/PzI1blsU4IYhKtZjP5zOJxSuHz33g8V/0FZanrPyTT9+eSiVMkTpn62uefv6p+YUFwfG1a85ensg03L3b92yXp9n5pT3A5Juuea1T8++88z5uulNH5wFyg4v6F2ZnJyery1cv2rn7hf6l/dwy0mlz787R69/7+4VF3ZWjte9+4zt/9qH3FPI5JM4gC6gYmgiuaZDQBEIq1xPAUDefj+FAyA22aFGvISCRFKzN7dOQ9Vp+bBkwtGWDwNJlnPtSjgyPIEFxfh45a9TrSqmRZct++ctfnHba0NTRowdHR8+74HzP9Z5++ulKubx67VphiNDLlULgCarEgv4O1j7FEqzsed6dd955++23v+ENb7j66qtPvR86Xk46CiIwDobBXFfV6yqb7WOcHx4fHR8vzxfrrnQM0/a1Rww8hT7BbHFyZOiczZuvaPhusVxesfyCM868dHLuxbpfnpo/MHb0mZIrs71LNp9xbffAsjsf/vnR0sKZW65dvuzcu3fdP2cnd40urDlj5Pfe9ZZSvVar8f17p154/pDrqfmS+9Y/fN2Wi1aKlL7yiksHl/a88S2vT3fZl2w7503XXnruOedPT5XGx2YMZAf275s5WtQ+RzI4moqEIgEgEAQnO8mNtBApzpKICQQDQSCYBuvvKxS6s719XVwgoELUiK/QfGdDBeNAu+xeKZXNZt9yzVsAoN5orF+//rJt2/DlF6Dys886a2ho6Hd+93cZMqVVo1FPpVJnnnGGZVqtblsl6L6gYuH/0omiG07kkg5s/Oijj95yyy1r1qx597vf3STwEHMGP6E9KcaaDJEBEJFmjAyTGybTCmwrg4hzxSlfasZNT9YVMQmeK0Uql6lVGxL9w8f21fyG1Myl0uHpZ13oN1M4V55eMnjWTPFQ2SmVvdJ0cW5ibmy+Mnv+2W9eUGAkeo829L4KFDPLn3r+Prl+4NrfuVwDfPur93DTe+Nbz/63r996x88zZ194+srVucasVswFVI4jn35mT7Yrk8DM8GkjYy8dfs0FZ4+PTg4NjTDkHknlOxKlr2pa1RE8JI2eS0jCNJImKZcamgb6enOZgWwuA+BxkQTQQNC88B50Wyy2QuaNrdB8HotzXq/X/+Ef/zGTTm89+6y77r576dDQtb/9W0uWLLn66qvvuOOOgYGBXC636YwtSinDMIRhHBwbW758uWEaoVS73XDhC6QQCXNBDMWiqtlq165d3/3ud4no4x//eDabbUKq+bh6FJchuj4FVAUDuSbSXHhdXYlMOuP7frkyX/OKKXuQNK9UG5l8qlRyrJTs6csrImIKGHGTGcAMyy4uuA2HRo88hiTq7lFR7hobf66y4FQbR3PJRENLNHlPlps5dsxG44xzl3b3/uixr+Xvffb9f/rbXQUr19XV19/3++99y84n937rq3due915I6tNAG0wm1NKObZb44k09vZ2gfZnp0oMUpmCLDrz4HmCG4xZngIHfJ88z/cNIO16TBIokyNLJ8yB3h7bzgCSYQiE5t85Nc39sm1iE5rO+UPQiS2zz83NVarV79x8M3J+xpln3nXP3W+59hrOebVaJaJ0Om1ZFmPMtuzh4eFDB8d2PfVUT3d3d29PKFdul5qzIImFInRshAoCopnNEdH4+Ph3vvOdsbGxT33qU729vRB4nrgVlaNYgRO5MDjzuDyveZ4BIKDOdolFS5LZfFqTqtbn6m5FASgyy3VPWFwj1CqsVncVYt/g6cOnr9FMey51pZYOLTu3VtVHJw939Q2aqdPK1UPLl12yZet1S4fPd/z0wem90qDR6dlpV8wl+NPPb5e9iy99+59BIbVrdG/3UE/vokWlOW/n4y+e+/p1b/3Dy/bu3Ksk+kAKFpI2O+/8Dee9dsPpq1daprFq5en/deuv+5Z3O+AhMxSyaVmccuZqQLOOO+e4JSnnXb/qs7KCCmkFiWymx7IMzsE0DMF56wcJCK8s4HYYiqWDkOOCbVPpFEP8+U9/tnvnru333dfX3yc4P3Rw7OGHHrrqqqs2bty4evVqgwuD8bO2nlWtVjnnmeP3c1pbhJA+4VAY5aEgJKMHLVQ1QVOtVr/61a8++uijN91006ZNm9qFdojwX7Tn1j3E4LMPgXdjvNxKCGHbfL6sPc93HZK+J6XUJDlKzkFDrdBD9VoDudPVnUpmRLXSkF7OldWdzz8ALz3gunMDA2sLfRuzPeVHHvzP3CA20DdyqcHc+hde3DFXLJbqx0bOfkOjt7fbWfTAN77Rva7XnagYV/dNzr4gwMAE7X7iJcc1547MbD59jYOW7xtI6UrJuPV7DxYKOZOhq/nAqpEvf/X7n/39v3l+z3OeUmXplrVLjptKpRvK06BIE2iNHmkt0Yc+O9nXvci0rOZv+o6vJWwiqwWvKFxOeibkuKYN8/muP/yjP/rY//oIab10eOj/fv3fgMCyLCKq1Wp33HGHaZrvfOc7kTHP89atX99oNBzHNUyz9R6HWJC8cjL0873Qcbvw1AJstVr90pe+9L3vfe+jH/3oddddF307Smw6FZz8iWHulVvXENhIlkqlycnJiYmJ5vPdpVIJAEklH3hg+z2/+nlPT98fvP1Plw1v6O63c7mMnWKmYQKQ73uu6zYavlO35+ZV3ZGVitau7emkj5IxQuK+agjT7B7Icws8qerKnJ9bEOkE9g8kUrKQEz4eRafatVxkcT5FC3kh0wmPPHPf8xOFXH7dqoGicr0GClNWSymv4lo+S9m5ku17KU+4yZ6k3XBcSvJj1TlXe2kriUiVRlGR4oSKFDqoSVMNhzIrF3ctFcIU3EI0OGPHf68ag4ygxZp/3Rs6GXJBMNFmjAnGD7z0EjKWSiXv+uV/XbrtsqGRYUDcvn378uXLJyYmBgYGZmdn+/v7lyxZ8u///u+Vcpkhe8c732kn7ODLcEMubn1ljJ3wctsowqKoCmKiXq9/61vfuvXWW9/2trdde+21oR8GRUk7GHCDXQXHBYDWlYtisTgxMXHw4MF6vZ5KpZRS6XQ6k8kUCgVhoNZg2OcQK9drdV/NvDj6kLN3TpPIZrKFQv+igb5kMqk1kiZAlso0rLTM5BKeyyoVXqtl6g3bI1SQIMfzputocdvG9FKRKPQhgpX0GHdtZgwV+qWx2FIemWmmSUGl7qvk/2vuzaPtPKo70V3DN53xnjvpTrqSbEmWLdvYlgdwwMQ2HnA/XgKmkxCGbkhMSLvpRx52MJ3QCeatrPDPs1dwICYvDIvFAscNpI3TGEzbEgZP2AhjWZZkyZIsXd17dcdzz/hNVe+PfU+pTtV3jgT9R3f9cdZ3vq/m/au9d+3aVZWTO3dtdqUTQVTg+UUvThw3GCSjoyMOcVaa9Thq57wAfEdIGrmw1F5pydijLmGs2lyRDGQCiQQpgEiQkkjien6BMxcPjSQggKxP2JVqBT0EiD3sDfDp/U85o5K8+MILf/2Z//K+D33g9ttv3/PUTx559PtfevDB8YkJSogU4tTJme3bth07enRqcsphCWmD6AAAIABJREFU/N/cdttP9vxkw4YNuSAg7MyxMzqh7cpwpdwYMLRfGpUWQjzyyCPf+MY3Lr/88k9+8pO5XM5upAEgsIaRrsNh5DiOFxYWfvazn73++uvLy8vVanVpaandCVEUEUI8zyOEMgblcjkIiqVS/uSJ2ShKw2R1aHCaymh1+cjhQ695rju5cXygUqLgpWkqZURksyWaLuE0kISX49ZgFOdoGtRbK2nsFYQbzxPhC8/xPQ8O/fjhuamp9LpbUrq6gXt+XBDSrbUjL8cgTfKcxESkwgOREiJZxB0pgMJqOz5QO72huMFP8nXWqqfxStyspi0ioeLlao2qpJBKCZQSIYkkAFSkqUO4w30hgDECQCRIvS8z6WJ0tS0BM6QEIUIIhzv/9eGHJ8bG33HjO7jr/PmnPvVfPvOZf3300T/64z++cMeF3//+9ycmJuZm52ZOztx4w43NZrNULN16661JmgJZZ3iGZMskdNetQJlcSr3UnSXQ/P+3f/u3V1555ec///l8Pt+L52U2VQerihmG4XPPPffMM8+8/PLLp06dqlar9Xq93W7j1FIlUQJeoq+jlGma+r4/Ojo6Pj7ebh07fLjluu7k5CTj5eeffyGO4y1btuAqOMbHTES6nJPHHF6O0vGZRemVip5fWDn2+onjBze9+WY/zB9++dmLL/3kiWp7/788eN2/eff4xOgvf7g3nD/SrC1dcOkUOCRw3bSVPvvUi//pL/7olb0vtxrJ2256y1e//h0W+Fumz7/y+l2S8F/t3UenyjnX2VAaWQoXW060tv+N/OigVw58Rib8TTVWa8WhiClh67dWEwKUUsc5c1ZkL7ZkdKw+r9L7uYu4QKSQBw4d+uAHPjA+MR4lyYU7d976znceePVVkHJ4ePiOO+6glDabTVzh3b179xNPPHHzzTe/9a1vZYynIjWoZitb+LfLg7SXRqWS4YFJUsrdu3d/5jOf2blz57333js8PGwn1/Mx+J/eeCRzq9V64YUXvv71rx89erRarcZxjMxpHQFC6EnWL1omRA1iznkcxzMzM6dOnWKMVSqV0dHRgwcPpmk6PT09ODj4xhtvHD58eHp6enh4WO3cF0KIVKTJCpDX3XZrYSZNp9986sSrjeYclb/lra4yXswXi9WDPx8tlI++/OL+J07JdvuSiy85+tovVxfqrsda0MgHhSQVxw/PHzp47LwtW188+OqmbZuvecfbHvp/v8LyRIbR//iX/zFw3qSbiIHBIXcyXxqp/ODrj/zWzde/45abeCzGnQ0j3lAtqs/XFmWUpCTlDnNdl2rm9kyWYKhTRvdmkvJMPoRMTEy8+OKLt912WxAES6cXnnv6mV27dgkpCaWzc7MHDx4KfP/Ciy4sFoo33XzTRTt3/vd//dfHH3/8sssu+4M/fJ++k88oV68A++u//msDEL0ULCVcX3rppb/5m79pNBoPPvjg1NQUdLst9Jf6BuyklK+//vp999335S9/+eDBg0tLS/V6PYoiXBNVIFC8SmHRGIu6d1e73V5aWkqSJJ/Pr6ysnDhxolAo5PP5+fn5er3uuq4QIo7jOI6TOElTGUex5ziBQ1g+36rV8vnh0W1XLxx4Ggql8W3bjz/zo9rS0vDE4JHnnp7cvn12/vR5Wzc3mg2I0iQRbuC3wraIUuo4vOhHPnGZU5we2ff0L9xiXjRT6ftBMbjysivfmD22trrs+qXmyeXr3n79tqnzB3iFgZOkUggKwAihebfg+b7jugCEUEK61VAjKKzgaTNnVXrUUKSEMMa+9c1vPvvMs6++8srXv/a1EydO/Nkn/+/K0NDS8tJjP/zhxumNYRj9/IUXLr7kYkrp4GDlmmuuueTiSxrNxvSmTYyxM6qfBhgDOWe2f2WqWdDN9ADg4MGD99xzz9LS0he/+MXt27djZN0QqrfEyNbAeBzHjz322F133fXss88intCpATvLyNDWMHQtTXY8GzEhAIRhuLq6GscxpfTIkSOrq6uVSkVKOTs76zgOJonisF5fq9UbR4+9fvzEawcPPEdoUh4aGpoYP/DzR7a+6a2FwfyGzZMkaUfNdm5oYHFh+aob3ja2ZWJxftErOAmIXCGXimRttTpx3uR8a3XTeee99NwvatV6ay0MKDs1eyr1uAgjl/lvzL8OcSSA1VaWt79px/jgqAteSlIpgVM3FxRc7g0WKw53CSCosgcndAs76N7+pRNLj6z+UkqlEOeff/7E+PiLL774q5d/tWF8/JN333XJZZcxSl/4+QtXXLHrgm3bN23atFZdIwClUrleq60sr4RheMGOHZ7n6WDQualeQ2Js/zLEp81d9+7de8899zQajS9+8YtXXHGFgdlM7dIWf/hpfn7+/vvvf+SRR5rNZpqmChDK6IqbV9VLBV+9niiaVQTZ2cifpil2YqPRaDQanufNzc3NzMzkcjkhxK9+9auNGzcWi8WlxeXTcwv15mqSxhIYl/LkkROri4vjGyfikJXHx/NE8tLg6NXXJtDijhclzYJL4jjddOkled72GBQ9h8qLXc6pT1ZIUvKKpXfeMrdavfrq6xbnZqbCtsjnDj/7y2aSvvWWW8K0Va/VL7r8vMgJw7SVithhjkMdlhAGrhcE3SPwjN0uE2HGwNPHrU2X9U/rGjwQSm555ztveeetURS7nks5S9KEUjdOErfjTJGmqeu6BGDv3r2PP/749PT0v/2938uRHHRzKaPQM6X3d/RDP1TM6Nlnn7333ntrtdrf/d3fXX755br3uj6GDJGnxJZe8PHjx+++++69e/fiUENUKeUJNOmmtDr8FUK4rssYC8MwSRIVGbNFcVwul8MwjOMYjWGYD56o2Wg0kiTBtdZ8Pi+lDMMQawgAUgKjQAgpj06943f/cOvVNxY9woIcc1KHUUpCxiQjkhEhZATQ8pnIudRlnLiCMNKAuOR4TQ6+4xMAyakDdE62mvMr0+XxFTdKoO6kUkJcYcUKVASDEiu70nGl43Kfc5dRV5d00M0MbOrg10x/LIOrredDiATJKQvD0HUczjkQIoUEAMooIWRubm7P7t1vvubN1Wr1tddeu/322ymlSZL88pe/nJubu/qaq8fGx6GbUdmCBdRpM0Zddcaj7vHZv3//vffeu7CwoHhVL8GX+Ukjnjxy5Mhdd9310ksvYc5GNH0JSGqu+2maDg4Ojo2NjY6OhmG4uLg4MzODnpOKVyVJMjExcf311ydJEobhsWPH9u/fTzRTfrFYbDQaqMPV63Xl6rQ+uihLRcpJujz3xn9/6Cu3l8obd16dC1PKRT4KwU0cSamQFACImxJCRCRiSRICESMu812XEoeK1AXaYNITXAoogDs8OlkGtw4xFyxkSSqhTQTzvFzi8ZRRIIwy1/WAMiLPDD9dltl92+slZDEzFSgQKcFh/B+/+uDpufk3X/uWy6+4Ynh4GM+rlRRGRkaKxdLjjz/OOS+Uiu0wLBQKaRSuVleTJOGMK5lgyB+dI0h9VmjowtDRx7HH9+/f/4lPfKLdbn/5y1++4oorCCGZmNAxawTMXAjx8ssv/9mf/dnRo0eRnRhSUp3dgNoSfioUChMTE8PDw57nVavV48ePA0C5XL744osXFxdPnjyJxi0pJXbQnj17CCGc84GBgSAI1GhGLlgsFlutFp7yjezwTE/h2cASHEZaq6e//cW/HXvPHzevvjVfywWFRLZpkaVFGuZJNEzEIMQONFzScknqkDjh0su7kYilSzzurnngAwNGqeuP0NLzyZrDQZK4JDhNIeKJEGTa2djmDSd1CAFXBlyeEf3GThMdMQbm7H7WCWHISiEFmkAHK5XdTzzx2GM/SNL0ggsuuPLKK9/1rndtv+jCAwcOJElcLJUYpbuu2BUEQZomhJD5+dMEAA8gNU4/0MvVX57hWJmDgBCyb9++z33uc61W6wtf+MJll12m+Iqhh/X6qzJPkuTEiRN33333kSNHlCKlw1xXkhAZlUplaGioXC63Wq2FhQXsccdxCCGLi4uEkFKptHPnzvn5+dnZWUzOGHMcB/lTGIaITlUEjpMgCIQQ7XZbSXnVI0KkjDuJlJwT2mzPfOdrOTqyeM0722nOYY2KK4uMBlJUQE5Rd0DEgWwk0HDoai2u8QZpklAmsJaTa2kSMEd4fF+jOuGU84S8Ep9mQhDZpkQ4jfZw7F7lX3wx2ZJv+9sHtm2RDoCUnGMbGWM4xjLFQuZfvTNtVKn4KQCI9L1/8Pvv+t3fmZs5tefJ3f/88MMPfukf8kFu24U71tbWrr766tPzp0ul0rZt27BL16prI8PDhJBjx49f+qZLbbQY5Z4BlkKDrg/h89zc3H333Xfo0KH777//sssu0/WqTMD2+gQAy8vLn/70pw8cOKDmbkoZx1RqsTkIguHh4SAIAKDdbs/PzzuOk8vlHMdxHKfdblNKC4VCs9nETXDDw8MTExOHDh3Cv9Ax59odLTs2C9/3AaDZbOr0o5Qgg6CUpEImjLntZvyd++kIy5131VY3Fi4fko5PCgXq5AVzCQtlU0KDSj8maVmkS7SdE6ydRAEHCSHIlDvxMZi7Baaf4zXCEz9NWMpCJx4V7t74YEE6Y3LIlUGUJowDkYIxagxaAzq95J3BFDI1E7l+kjs59vrRp3/60+eee+7YsWPlysCHb/yj337HjYQQkYq1tdpatZrE8al8fmRkhHNeKpWOHTvWarUGBwf1jrV5pxqoYF/SpD+HYfj5z3/++eef//SnP33dddfZUl+Pf6b2WVPCdrt9//33P/XUU7oqLTq3MKJY9DxvYGAAz8pqtVorKyue5+VyuVwu5/t+FEWrq6urq6vValUIMTg4uGnTplwuV6/XFxcXOec7duxIkqRarbbbbQBQqFW1MmS3Ai7nHKUkSkYCQLCPZEg4F9EC+a9fH/73ldnNmwuteNB18jxIBaOUp+BHELDEFVy4MmrJhCbSY0zEKUvTmKRUiEpEz/dGZ7wwSdoFQWOSFONkAoZ/f+jWubW5gnDr7ap00yRNQtZ2hcc5M0ZCJmlUowzM9YLXelaEMEI8x/3//vEfH/rWt7dt2/of7rzzure/fXxiIknTVKTj42M/feqnuSBglO7es+ejH/3owMBArVbbuHHjzp07p6am9E2FRjUMrLO/+qu/sisthGi1Wv/wD//w1a9+9e677zbcFozImfnqcYQQDz300Je+9CUFJt0rxnGcgYGB8fHxgYEBIQRaHwqFwuDg4ODgoO/7yLROnDixsrLSaDSwuGazubi46Pv+0NBQLpeL43hxcTFJEillFEWUUs654zjValVNHuEMZ1oHN2MsSRI8KV/NPc80kwoJNHWov7jYWJhZueiSHPMTJxE04ZRGLKzLaos1JEQBobW0nWO0IcMBwavQdqmsJW1CyMV8FCj9EbzqszCGeAByd5bedRVs3+JMXOxu9Sibqb5RlF7F2UBAejxgjFFKdYOoPkSNhuBvph3L+NvJCm03slwqjY6OtFvtJ5/c/d/+2788+eSTAwMD05umByqVC7Zf0Go29+3bt+uqK7dfcAEAtNqtn/3sZ4sLi5u3bMbRCN3MSS9CvVk/592ASxzH3/3ud++7774PfOADH/vYx3ztVCR9iKj4WW04Ew4cOPCXf/mXy8vL+vhzHKdSqQwPDyO/RTz5vj8wMDA0NFQqlZIkWVpaOnny5OzsbK1Wk52VQQULIcTq6mq9Xs/lcoODg5zzMAxrtRoiBrGytraGahbWSqEHOrYJzjnCETSVudMExqgHKUkD5i2teEDYju0eBUmikIQxbdahtgbVWLYD5hQkC0XbEyLHnDXZZESuiVaOskleOCWW18hyg0eciJ3yvHd714y6gy7k2rQJrXQgP+xEUHA3lL2c5+YopXh0QK/u1Qkpuy3vtqw0BzkmBDk9venKXbsuvfRNvuf96lcv7du3b9u2bbuu3HXijRPf/va3x8bGHMe55eZbKCFJmjzzs6ejKMrn81u3bnNd1zjyU6+M/rfLu0Gxnz179tx3332XXHLJPffc4/u+zZP0Bqhnu0mI0QceeODkyZN6bdI03bx5c7FYPH369Orqquu6lUoFF15Q5C0uLqLNCSW6moSigNOXqxBbY2Njk5OTuVyuWCy2223ECpqySA+9BF86joMzR9d1LdEDCQkD4sgECAndPd93L7xM7thCqRCMs5RK6kQpT0nQErLAvBLkUhBCRIHD23GT+tSNZaNdE17tQpJ7vSVqbtyiS6dby2VvKCDcTYOg6IyJ4Xo0SwMnYLnOFDVDM+mvcqjm6MIRugMh66fNcMd58oknvvfd77768ivVtbXLLr/sY3/6pzfccIMEcF23XC7Pz8/XajVMwii79tprB4eGpBBJmkBnvbgLr1lzBaIGtML7vn37/uRP/qRYLH7ta1+bmJgw6meIcyNfQ+oLIR5//PGPfOQjOFNT9vEkSS644AJcWsnn80EQEEIWFxcXFxdrtZqayun+FLqzlyoL32M7C4XCxo0bK5VKrVZDS9Xa2prjOPV6HQ/YBG2sA7IFSgkhSZI0Go0wDD3PM+YTqgcJZRxSsmnT4L/7eLqhXHBh3K+Uib/qVAO5MCW5R+ogV1JRT8Rq5IaLpA40bEBtJDdZJ6daXivxQnD4Zrn5t/PXb2BDJag44FFJiICwHo2UJ4v+gEM8QgguxtkYslUo/IrOj8aozsYfAZDgud7/c++9J46/ceNN7/it695WW1srFkuTk5MSJOccJFRXV7/3ve+tLC39h//0cdd1f/Lk7oXTp8enpq6+6irX93So2AySKEc/oxInT5783Oc+lyTJZz/72fHxcb0xNnMGiw3og15KOT8//8ADD6ie0qGQpim6sqB6hCwKPRqUoo056/xJvVGMV9EgSZLZ2dlmszk2NhYEQaPRKBQKaZoODAzkcrnl5WU8DB1FJADglB4JiViPokgJfcNVnxLJJRPHTq3tfaby27fElERRGLqSCjLMJjwSpzKOIsq5x3iuLGEuWpn2gwOympN0UcQNkhZS8IEMBOURZ6QoBgow4Ds5KWKSykKB5p0CB2ed/XdYljH5AE36GHMRW8c35Ml6LwGRRKZJ8t73vveb3/jGG8eOL5w+/e1vfzufzz/44IObt56/tLT0L9/7XqlYKhQKUbuNOVx8ySW/ePHFN44fv+yyy7zA748BVZMuD9LV1dUHH3zwxRdf/Pu///urrrpKh5QhJvQGWBKEKAA99thjr776KmhWNSQY57xer8/PzyuKhmE4MTExNTUVhuHKykqSJGhZUIszSZIoHUtxPtzJpIAbhqGaM5bLZdd1Eaye501OTlar1ZWVFUppEASILcdx8BrEKIpc111YWBBC4GE9GBR2pRBtxrlI0+d+Fl10aTw5UiUtcCRNmZRFQtuUBAJcKVIQJKTEpR6RlFA6nPDDTIyF7JTbKFCXxC2eQskb9WKHk9BjpVSk3OUu9RjlkgIQAuTMye6GfNERY2tdOtoMLrCeFoAAoYw9uXv37j17RodHVldX7/ijP37iiSd+snvPedu3HT169Nprf+vI4cPXvPnNRw8fRu/kPXt2L5xeuGLXLrT/GfofZMlBUOYGIUQYhl/5ylcefvjhe+65Bw8z1tUxo65Gpe1PhJCFhYVvfetbcRzr55NgtVChabVaruvidXh41eXMzEwQBEEQMMY8z0MEpGmKdgfaOeNV3XeFs9d2u431bzQa9XodrVnomFUul9fW1nDSFATB+Pj43NxcsVjM5XI4/0LbWJqmeCLc7OwsFoSQVQuUAJJQEbtubnGh9vPd+ZHfSahLYsd3vITyNtCE0DqRTCQ5RqJUlom/mtRHfL+ZRpfwgZfk7E5ZOhWHsbMyG+73qByhG7ksOtzjIDljlFCA9R1IBDqyvhtJmVSwOZkOOx1n6y8ZpZQePXr0Qx/60K23vvM//8V//v33/6EgsLS6AlJGUTQ5MTk8POw4DuvcS+g4zk0333z48OETb7xx3tbzlX7SBVlrKW/dQCqE+M53vvNP//RPt9122wc/+EHDmctoRqZwtHP/0Y9+NDMzgxZIpdBwzhE0juO4rouih3OOu9g8z1OeDrgOiH4vajZHO6fXI0Zx9oe/lUplbGwMZevs7Cy6npZKpeHh4Xq93mq1cB5QKBSCIPB9HwGKAELYAUC1WkUOp/iiGgwgJI8ACER7fzG2663eZNGJmCdZLOMlp9EWjTXZIml9iCQloFSwmJOBxAGX0lhsL5QWkqXtwbAj/GZcm48P+TnuOkXmOoSu8xFCzuzDsVVvo58zB7kNO9AE1vqzkACSEsIoC+MolWJubm5peZk5HAhhlP74x4/X1mqvHToUhuG/3f57nLGB8sDzzz9fKpXGxseV5m4D1yhune0//fTTDzzwwI4dO+666y7lc9O/3oYsN9qQpumjjz5KCMnn8wpVuN6iWAXnHGGBbAMtBWcIibOSziq4XpBuQVD1aTabrVYL0Tk6OoqMCs0QQRDEcYzw8n2/VCphVRHECDjO+fj4+JYtW1577TU98/WBDkAkCBpHjEK9KX75i9pwJWUidvNSxJGsx6IZMlEgjKSMS1knkBcsTljF8xZjEYRixM0nEd3uDI27m4bo1kFv3BOeTCWlnOK94kq36t3nuv6aiSRj5IOFUfScAUIefvifH/vhD48cOfKnd3x0ZWXlgx/+91LIjRunT5446TAu0rQ8MOA6LkgYHBoKoyiXy0nNlVeXVHatAHWsw4cP33///a1W67Of/ezExATp3FyQWVGb+2WK9vn5+f3795dKJfQgUOtfusqMAaVhPp8fGBhQfdERQOt5Kks9vuSdNTV8ryqcpinnPIoiIUSlUqlWqyMjI7i2iBPpXC6HaRUjxFSYJ2Ns69atCwsL1WoVB4BqaSoEkUApoRIYpYv79u246ooSd8GVhEccvJiWVtKWy6jgciFt84TH0ucsCIQngbZbackpuLKQ1F0KXjHweUyIw+MkkZw4lFFKpMgWApmdr3qpC/0asbNJtj5gYHx8/MTQ0PDw8IUXXlgpl4vF4q5rrpYgV1dXR0dHPdcdHBwslkt+EFBGT83Ojo6MTExOIsfRKa7LXF0WAwCvVqtf+MIX9u3bd9999+3cuZNovisGSzAkYKbUV/nOzs6Ojo6iHUHBCAmJz0g5xbHwV+dYlNJ2u728vIw+CyjjRkdHkdO02+04jhX4EARo1HBdF+cEvu/XajXchIn1dF1X+QDKzpq0ztsrlcr555//0ksvmQqKaqYQABAvzIWvvdoulV+LkpAKErU3OnwCKgNEFgEkcRb4EiVBPsfmmvMRjYoOrzfSCSc/7E8WYJRGrgAgjoMTUiFSQggQChpW7EFr4Ez/ajAnA3A67aWEdjv8+Mc/fujmmz3Pe+wHj720d2+j0SgNVnZddeXy8tL5559/7NixQqEwNjYGAGma3nLrLQdefVUKwRhLhcCRadTKfsO/+c1vPvHEE+9///tvvvlm2n2Ah94AYs0KbWam/0UC+76P+goCCBGmwIQgQ+RhHD0H2bGGO46DypCUEr38MAlyJsWuJicnca2w2Wwigj3PUwZS1XjGmNqro48iLItzPjU1NTc3t7S0pKqhdDtGKSMUAEQS1fYfLl14uSBJkTmzND2URCdla1PibJcBEL5M4QS0yPLyVirqxKnHYpwPeDDiOUMkyTWF9DwqE6CMpKlMZUoJw2oaqDIGrU0Ue2xDllRRv5QQytkvfvGLv/rMZ/6vT3zi6WeebjeapxcWpqenAYBRVqvVamtrp0+fjpJkcmKCc16rrr1x/I32aHt0dJRxpivvRsX0v/TQoUM7duy48847cZpNug2DekUNRmW0xHiPM/lisVgsFsvlcqlUwgf1jHZR13U9z3NdF1eCZSfonaL6Ua0zIsIQB6qqeBUP2q7U8rYuUvXM1QRTPagmBEGAZuFMThwncRiF7bi1cnKWrVR3xslUyMI4jtsyHxVp5B5LWq8ka69GjTfCJo14GrlBFCy26EyLsXY5F+dJKsM4jsIkkSJM4yiJkjQRQgohke56ibpuoI9tm0UZnxSBu1IhL2Hsnx96aPv27W+77jog5E//4503vuPGZ59+WkoZ5HM/eeqp04sLhw6/duDAq6lI0zTds2dPuVw+duzY3Nyc4W9sMBed9fA77rjD87zh4WGD6/TCow4m1Tx7xORyuZGRkUKhoCSgUuEVLfXiFL2RxyATQgcbtfcBOiZ4fC86bBk6Zk9lIyCdVSDU8PSmSS3YJMFKDg0NFQqFVqulIAsAKH+FELlcrlgYGJ2YAJqSsF5PYhkm3M35lHlMFjlpCDIs85LSJG4sUZmXdJGEcUpjj7ejlEQRo0RKQqRshwkICUC5EJRIiu+72ZDND8DiSWCJDls8AQABIABpmh4+cuT973tfoZD/1D2f2rJ5y4EDB2ZnTgkh3vSmN+3atQu7cd27KRVSyrm5uVarpSbL1DroxS6XX3TRRdDxhQJrmBoSUG+PGg02Q8Yk6O6i8KTHN6StSoXWUWyAlBLXVo0qoTMCui0onhQEAeZTq9XiOMbFGQQlaFsR9ZYbrFHlj0aKsbGx48ePKxHseZ7neaVSaf2XOaXhCd6KA5eN+NyLi/tkc4CJ6VxQE/FKRFPJisJZAicA1kgbMfUaqThBVjf7G/Mp8bmbClFbq6VEcMZBEko4Z4xzoJQDdF2eozffpo4uRmzBZ3AUQggQwjgbGxt76qc/ffvb375l0+ZX9u179NFH77zzTuwo9A05Y3fk/G1ve9v3v/993/ddz7U5JfQIXGpKhiG8MxmV3cLMgMRAAae/NEhrZBWGYRiGAIB2JmQ/+t4yfFhYWECnBuwLXEjGOePi4iJ60KIihZq70TqDS+nPaKdhjA0NDZ08eRLnB9u3b5+YmBCd4LoudRyZY45IWAwBFxskmUvEWI7HcRoKuZTShLs1KcbSUjuNF1i7mQZtIn+8fCps+bduKeVovg1hQ0boY8KMAAAdcElEQVScUM91iaQgqcM5YwEAkes3SICUXfqA3e0GgQ1ZaXMBWPf5Fu95z3vu/exnP/zhD+fz+VOzs9svuOD6G24gmkn2zOAHCIJg3YECzqikNqSMocuVC6WBJ1tU9RKImV91S5rRPFWW6N7iDACUUtS3kIOiFoXRlEELUYW2eyFEHMeVSgVJPjs7SwjBJEII3/dxGmiAW8e0DjvU0lTawcFBdOmZmppyXReXlTArh5ACI4OOuxqLNRYC5y3ZzCclJ4HTnAWRF8UkIWkjjAIelIlbbdeaVHCAJxszQILbpi8dzFMBpJIvce5QwoQQUgBWSmdXNoB6dXgmqgyZgG+SOL7++utLxeIPf/CDarX6u+9+99uuu67RauJR24pAikYHDx7EpX21C8EWZXr3rpMyU9exeZ2uoBlfjdGv/ipdW6+uEALlizJ76gF1LFyxwS0SqgFozER/GOSFCrhzc3P4i1oXThvRgK58rVQmUpsEKEmnLGS6/cL3/fHx8bGxMQQxohz9B6nLhcPTNMnJtFlP21HzRulvzzkOwFpNknZ6UVKZqObCyF+LHBJ5rizkWn4+yUNS+fH80t+/+Nyrs/MO93OO7zs+o9R1Xd93cYskgOjwDhMlNjmNv72GUBcpKanXas8+/cz8qbnf+T/+z6d/8tTvvef27z70zxhBkWY9B4DF5aVmqzW9eVOxXBLdl53otNMpLqXsOjVZ9hCCOryMsZI5hno1SdVGZxV6wGkdLksDQJIkqC2pplJK8/k8rjQjgDzPazQaCwsLvu+3Wi10CkUQzM7OTk5OIiwUqnQWpdcWupm06JwnoJak1BtKKWdUSNKKpMM5pCk0KGUOS+Hi4aFis/Fyw1luNFbayTKhbeaJJL1ydHTHZHnKyZ1u1E+ureyc3HjxhslBz+MOp5QIKSkhhGabEmz02P2WKWQMOhJCJAClhFP2yCOPPPzww5s2bfrUX3x60+bNH/rIh2+66SY7KxznN9xww6FDh3AKRbSzYXpVCdNyfRBkCjvZLTvPsak68TIRpmipE5UQks/nMQKuK4dhqHRJnASg4yEq6YitXC5XrVanpqbQAwLJj+uPy8vLY2Njaps1dINeZ8OGtqtDGTrWf+iY+zmjAKzelg6kFCh3WDuB5VpzJJfbmfcuzBdfqS6/dXTj3oXTrzWWt3nBB7desqHgRGGyY2RCghwcKOUYpy6jeK29kEClJP3kna2rZI5wvVcNykopgUCaSoc7+/fvv/29t//Hj3/83e95z11//ue7rtxFKEmT1ChLSokDdevWrfhJaHu/+mCA4BZ7Q5br9bZ5rzE4DHyoN8YCn3GzjwE4I5ViDKomykSJ+aDXw+rqqsqKMdZqtcrlMh5WQzteMbVarV6v5/N5oZ0vYnSQXqguEA0ZRDqTCUoppxADW6lFrCkGKiWHAklJqymqtXBoMKgU+Y2FScb868fG20mYy7Mg5yQE8vmyTCWT4BEmGZWcE0kpLqYTTqRpJc9UmzL7XKeLkYkuJc4cvyzlyMhosVgsFgqu49TW1gBIsVQ0RIq+sqcYFe3eUZzJdKQuCvvH7tMk4ysGNYfSuYKqpdSkoc0yVWTSfSiXslyomWCr1VJ3nYVhiKZXFJTI51zXrdVqigsqVmRwKfuv6kTFq5R0oJQCAUoYCJKkMmnFRYcXXLfA/TSlrQg28AA4Awk5SnI04IQA8AC4TIHydTMbJZSmABwQWXittyHCDDD151iZ4DOZccd9ggDZ/eSTR48cOX702F/c8+kwCv/gfe/7dx/5sA5HHQOyYz5UKoGqp8FQzgALLCYEGmYN6BjywmZmOmLQwmac7aGPIZk1d1V/VSa6pq9mi0LzyCMd/0HkZ67rtlotfO84DrodK59j5fesQ0rHrq6B6r2hcy9BGaOyxJOUBERAEksXIO8DJbEjfUoEA0IZw3FEKKEgUppyyikQCmT9iAhCQAoASghIKeDczq41yGQ8G3U2wKdOX/Y895WX9x07enRqampkdGRwcHDHBRfoxRtpTc7XXXrmQ8bBa5l10gszBpbxVdFPaqu8dtkYDE1Q53BCC+rkPuggQ0oZhqHRYHz2PA9N5GrBu16vF4tFgyephkiN84M2LVKYU6JZPTNCCBGMCM7Bd6Dg0ILDy7nAoYQDkZJoppUzMl3J38wBaQ8we8BnvpeanNLzBIu3Yf5xktzx0Y9+7GMfy5eKfi6H67ZJkkghpKYh2Alt8hloVn1L9UNB1GddWhtdD1nBHjegMQbZ2baVmdaANdW2FknNLoAvXddV24/q9Xq1WkVFHuOo1UbsKTS0SinxGYEF1lg0GKQaDwbDRxmquUFTLl0HOJXcJ07BczwmaRoRYGkcR2HquqRj1+1ieAaD1CtvmP36dHsvNpZJCDMmECHE6NgYAAgQEmQq0qSdEEIodAkcyBoDvWpiR+s6H8tIoPK1h4j+KZPn2ScT6dVV3aE6XXWx3jDDyMQYQzAxxuI4Rv8ILAu1LgURzjnuhwYASin6QYCmleMnw5YG2hCUlvjrYtWQeh4QBoSKQiA9FzgDIMILcq7jCQFCIN8SQhD97j69K/SR3Ist2SQ0uu6sNM5kDRKkAMkIxXXpdZssyY4MPTgf9IAyNi3jyhP7WU+pd7ddgIqsz8J0RUpVGq1TpGNhQlVMaLsI8UHJQSwXz05eN8Hh4TsdxUutVZPOxhs9f9FZOrUBpDdBxxnV3KBt2vs+L7qB43iB7/q+E+R4EHiFYs5zPRCSIKgAOdYZDzCjOD1nacmHszIMvdpgQcooSAeiBAAJQgpKKKzvscjO1mAKdhH2+zPA0lmI+pwp4I1gA1yRQaelXSGqeX4iyNR6s67g4xKV2uarjA7Qsd1RSuM4xj1eoMFCX2HEtLh0bbAKA0zqWYdyZsOFJIQwzqjrUN93XZd6DvNcB0SaxCHnXMg0SfEco/U8VBF2b+tVUn/taLa8A0utNmCkt1THFupSQIhcv0jGLNQONu6NPjQKyhCFvf72KtX+Kjvu5KSjFOu9kyQJUh23TqhUOqNCorqui+cySClxD6oSlGpdj1KKO72UaVTpZFiQ6A4GYYyxKLvFtyKSkqEdAuBVZMoMxThHjovmOgFAAQSyJDXdyxzABrh18OlV7cW9jMr3emkn7/8mk2Xq+WeWqD9nXDauPp87fu2mInnU0Kfath/EVrPZJJ2jE4rFouu6a2traPlEYwGyK/QENEirW19xY6CSoXoH2R5/GjJMVdrWKqQVtJZKQoAQCSABBD5ImQIwZagnhEHHVaEXGQwE6PXpQ2Cjq8/9pcHS7GhG23tF6NUQ0PDAjQ+94KInswe0nkqvtKK0bvLGr0mSLCwsYOT5+XmcFlFK0WEGAIrFIq4i4+0E6GgmhEBNHBkYLkuTjmOgXhB6/iu9HmdzOvLUANAbAh2mJbSJt9Eo/SV0Twg6MdczIWTdhqcPcT1AFrZ6SbRM0QbdSFJvdDlrk1V/MEhsiznVXUaedlq99K6bKexn+++5RAAAzrk6iNFgEhgBvXxkR2giJeI4VtaptbU13dSLPA93/7mui3sD0zTN5/PKXQJ5Ia4YonO9vvCsEw+6ASEtHmYnhCwS6jSDM7gRUmJyQNLoORsY0lL1VGL0InTqGpTOfND/ZjKqXvKuT+b9X64Dy6iuHWQP/pT51UgoO9Zw9VJxAkJIPp9HPyec6AEAavEqIXTsYaLji6zcYFT+ysqgtvrghgv0byHa8h8CDuWsYVgHjZwGmOxeznTMlZpFlxAi5ZklNn0Go2NLL1rPhHZfVtNLbOkcqxeeMuOf9WWf90ar7UJV75kGUmPQ2H91zgzd49UeuzqMVPtRQtVqNdxU6HlerVYrFothGAZBgNM3ACgUChgTNzHjqX8ILwBA307S2avDGEOfWn3GoOaGCDj0pmo0GuhUQzrO7CoIbV1StT1ztIB2YqAORAOXsjv0iayCrVSdFWQ6sAxK91KqzgU3NseygzFO9DecZPEbo08z/+p5GWmxqSiPFBz1GqA+lKZps9ms1+sICLwPhxAyODi4urqKG6PxyH+c32E+SZKUSqUoitRhyQgIZGaKP6k6YBJ1jk0cx3izsG7XUNqSepDdSozKFlukpgVgWVlV0C1nBp6gB6oMiOiYsNUPHXm9aN/nkxGMUnoh1c7fjoDPGY5+srdQNyphg0ZFRsrp7gwqrf7G9300L0FHymBCJRPRgB7HMZ6sCgB4gAyirVwuY0LG2IYNG9DvtN1u61RUVUVRiFqasqKBxWiV5q6Sqzd6nq1Wq9FooGM0buXQ56oGkjJRBRa8dAavBnMmUaXGUPWczwUNvUIfZas/sDKL5sZQyExsCwW7MB2aAKAkl5KDOlcgHYMnni2ztramqkg6BneM6boumkBRRKLwQogoaZimab1eX1tbAwA0maKmhbVCr1QpJV6jLTUjiF4oaDDSR4LQlhCgs52JUnry5ElCiOd5zWazVCoZuNFzQ16biS2Dq9lEkp2hq2POpoJeYYPq+nMvsP5aGpX9XnZzIgym8i4tuQbdXd9LIKqvUptVgabtqlJVJmma4jVdjuPgoaNGN6ltEYqrKSMnIQSN8ro/jOwc8d1oNHDBuFqtSilXV1cV18FTmYm2cGTzj8yxKztr5KJziEgQBGr/PmRhxaZKJv6gGxn6e1sOSkss2mnt+hsSCbJklA0dGy42xUFDgt6ZnFiCzC6mD5iMeshulmN3sSpCiTzcloPrM4ZbOnq+i84pTUpViqIIV6DVthnMTZEZV4EwuTJYoJdtvV4vFApCM7eqB71uqiZgyUr8nZycxF1AeFSEnZtNEgO++l/R8b02OJPsVk56UdcozoZCf3D0KqJ/uX2ylcZmCgyG4Dtrq+y00NmtRbQJmpFEdHbKq93MtgxC9Z8x1mw28bwr5GobNmyo1+tCiCiK1FI0ABQKBTy9iHZ2SxNCcEcGyiw8WVnB0eYcoKnqOnUV+VXn4NFLmLPeYwZu7FLsQg3A6d2bSQVpcSC9rLOyGSOQbo1Zr3MfEdn/EyHkzGYKG0CyeyE2E2E2w4OOZgMWVlSponPvkpo5io6fgmJ4AIAeMnjqldJ1KKXKdhqGIU4bsW54kocQAk+5QRSSziEzaJIAgCRJ8DpMm/YqEC1Ize6l6q/8pHUfssxBb3e6EaEXsHTi2TqJHkFP1Ydv/VralV6iXZDsFmsqsuoWLrulrIHZcwyZNRba2rPRETjiEX+6Ki263apQE1cZou+esqAiA1P2fdxDQQhBvR7zRwVfRVb1UfM+9aCqp5iTsFasVVv0HlQOoqRH0LtePfcSApklZnYvdFO3F730Mf9rsR98o5ZZM9uijz3ZuWIyTVPXdbsOtzVaSyzRm8ktM78q3/vMpqrjskCbcKkN0OpNs9nEInAlUR3AVygU0OKgu98oHR85EzIt9PKTUiL3Um7NQnP80uumGKfOSMCadmFLlUMpFqQa2wtemc96z/fqbXvw6wPVZnVGb2fislduehzdSUl027rVA/YVHncgpVTTrJ73FaoG6601hp3RF/pXRUKw2KnCgRr6snNognI1RkO5sowrA7qSQY7jKCcItaNVRVCWCMWr8IBuvZ7KoqGzd1vP04Glk4F2Nkbj3mvafRySHnTG1ieaAbJfK/QHlvHGxpleqE59ZZfGPseBTTsXsEHnoEbQDpVJO+facQOkBk+yEUosrmanlVIqb2D1VUcqshDoyDs8E0atKmZOjlBK6sfJqUNHVMAKoEUeAUc6x0wiqtAYRrp3lUG3ZFQam8Kf1CS1apHaqUE7x8rRTjBQpQejK4yetOF1jlDrIw0N+mZGINqY1+uAA1Xts0JrNvatSqWoZtgsuw7R00sCDdo2xtVfHYXqq65oG9JNla1nq6ZvauEZF5IxNzzXn3OO+hbp8CG1FAgAvu8r6wba1jEaQhbv5yWEMMbQf8tgPzrUQJsYYjTFxgg5c5EH7RzlrZ95qYKBJwVx40F1oI450i0BDGzp3agjxpDUdkI1XCFLzhiVwQiBdk21TimD+qRj7gYN4qbPO+kWDUbx0M0qQcO7Qid2HF7lpcNIly9G43WGoZu+SEf7xkslKKU4mwMAfHAcR3mWqoKQUaed2wbU6Q+kM4MT2uqeTgkl40THPVAZ+vVpKemcj6rkMhpNMCgRqbM0AFDiUmlmSj9T7EGnhXrIRICBJ6M5elBdarBbowhCiDq7Vl8qMMCgI0l2azhG5blRUbAGhxGMl73+4pVJURQpR2RVIb2FStFRb9QszyA/6UwhSWdLBWOs0Wgo40Kq3VsBGmR18aQ4kCrO7j6pqfZ4KYa+0UN2tmgr0ZAkCR5GgvBCTobTUuSXaES1ZSXVDoNQlFZ9Zcx7bKLomDNaRDrzEtpxftQFsej4fejo15GkQKOKk71VoMxnoswNep3sZ6PS0CNITRcZHR3dtWvXM888Q7rv5FQdoYxVtHOsLXZEFEW6DUwhTK3eEELUcbc4zlSGCEpEGBKVEBKGIfI2jEM1x0MFKfVGVUxKiYhBlKgBoHxW0d8LFyWRPOoIe6XRY8UajQae7406IsJRbwv+Km6qgHuOJDSooACqyybFNfHqBqpdj6CPJejoVXqf9KJ4fzBkLOnYbej/slcj3/KWt8zMzBw9elRnV3p/qVErNUmKv2o+iLvjRccxRh/H6BGPCKDaUq5OG2U+UH7MuDitV0kf93p3V6vVWq1WqVQqlUq9Xm82m0IIvH9genp6cHDQcRw04er6u1KziHaWPbIxxaL0fsNzxVnn9KV8Pq94iewsfdrUlVlaiupDvMQFDyRXh3tBR9rKzszJhq/qMVWiTjKD4n1QBfoitMxSrTJb0ieo3BzHmZiYuPbaa4UQMzMzrVaLaAqy0jakNimTnW1b0KExAKSdw9Zkx0ai6zpqlquYvzpLTWFLsT3sC12iyW7lTxeXGNP3/dnZWTxrPp/PDw8Pl8vl4eHh8847r1gsItlwoqTESq9fNZD0v/gV7w4inbkVNhmb6Xkewg5XovB0E/TMVtXGouM4xpObEEDIR4W2YKD6WceEIel0Ehuj/ax40Ecp0YndRyBmiki9PDs+XgI4Ozu7b9++V155ZWZmpl6v4+iUnTmq3hi9qSjO0OtBcWkFQUQeog2FjvokND9gJV5xQRo6jJBoWrPsVq1UHCEE1r/RaORyObxfAxGwYcOGsbGxsbGxqakpZFcoyxSGFHT0XwNeutqughpyBpEMZoPNxAtpkWHjPS6400RN/nUaGzzPZoGZ9D2Xr5m8Bnsj+3wshQ/IAqyqWaZAgY5t3ff9QqGwZcuWfD6/uLhYrVbX1tZQpjSbTbwvDgGUdq4zUaqM4lhGF1Ntb4XsTID1JMp3WXGdtPvEG6kpVfogUdiiHWdDvPgJ65Cmqed5eI9rpVIZHBwMgkB52RPNXmXYGpRMVJ9IlpmUalZ76DBvnShqaoIgKxaLqv9x3wCOH9UtCjqZrCWTrOprr08Gkgy2Z7w/s6Rji+1eeOyTtZ4Qmzo0NFQsFicnJ/F4PvTwbLfbrVYLb4RraaHdbkedgDMy5b4iNFc7NYsRmuVCwU71jkqo9AbR8S2G7uGkA0u9V9uHPM+rVCoTExMbN24cHh4uFArFYjEIAmVcoH2DYlHqrwEsu8PVuNVxZtDbGNXEUmGN5vQiIlggO5ck+nuDMYExK7Q5kAE4QwgazzqTw4AWAZT3ai1J8RWlTKC/HsKu1WqFYdhut/GmLh12CEoFOEyF00Ci3eJENZsNElLtrKfdvpqqL4i2aEg0B0Cc/Q0MDExOTo6Pj09NTSGqUO6gYtQfUqpuBp5o96qi/pBJDqEZUKi2wCo7qiTmZoCvF7aIJWF0ZGS+1//aLBCyxGvXFnuFev2vHltPr0NK/6rLGtKZwerCSB9bOlNRa8xpmqIBDJGEgFN8TuEP75HDl6i9oQKHBvpUuy1McSkFbkUkpZkpakHnaAm8An1qamrz5s0bNmwYGBhAhR0NB6hg6bhROCOaZFQZUms9xyZh5ghXkXuRX8eTwTz6sBnoZjB2NfTIBl6NOAbfxQdzi72diz2GjMrZoDRq3KunjD6S2gxRmQkwoExERoW/SrAqVtdutxFq6BWI1/6ibE3TFK1Z+n4vxT/0FqmzQ1zX3bhx4/T09OTk5IYNGwqFQj6fV0YgvH/KFnC6jLMVqV5IskmeSelefWvkfO4hM1Umx8oEU5+2UFTeDXGG3wwE9KpKn6+ZDcaXhjagfyIdJqdYmv6rOJxSgPBZsbc4jhWkdDWu2WxGUYTIU3DETHBXD+ksQRJC8vn8li1bNm7cODIyUqlUUPCpdRsUr3ibC8mSdEZb9DeZxDBUnD7BUE4Mwhv9nClJetGu18vMv5nlGs/mLh0dW8RStvT0fbqDaKuSdmSjCP29jma9X6CH5NUzVAxJ7biPOwFlK4ZWq4W7xNQd5uq+ArS44n2ZuKkrn89zznFNBnkVBpzz2jwJNAD1GmaZI8pQW40H6BZt+ktVtJ4KusGUWR+DWAZbIVnSyWiIkbPBsc4YDA0a96pHf+GtQpIka2tr9kCxYQHd0DHqkIlIm+H1ylZ2n/6IGphicqI7gHY9onFPp7pdUWELD8bJBFMfRtKrP8/lay+ugx5KNgv4zYJd+V6N6h+BZ9Le7pGzYt9Opfo9EweZg8yoSX+23Ksm0I02pt3AC5ouqIKxIUdKiad8Q7c3nw4s0jG19+mQ/iNTldVrPGcOYCOy/tL+PXd4ZUo0/eGsEYyaEPsCAZ2vGuzUfuhfV3WzptDcgOx+AYtjnVU5sEu3ZYcCkBHH4DE6pvElCke1qKz4FtEs5rSzcE6776TtxekzdQmj53t9MiLYeCLakqtddP/QBzHGp3PH1jrH0nvEYCeZ7VFfDbXA+AodBVyvkx7NTpLZlX1Ep/43c4xmMkWiBejGIgCgzwKadg3PY+hYDRQt9bT6X5uvGIzErq0Bu0yRZzzonzLLPRe+ZQy2PmVlYqsP2rjRZjuG0eCzflUtoZorvtFUBU2D6n06InNQ9k9i9KDdTfobqrke4Loy7TgtGcDC5LTbLRaydCBbAbC7t1dt+zcnUw7+BsHmNJAFl3NEnv6cIWj68/Ne7w39QHZWc0FDmGJUHfWm6xKUXvxMLyizPplNsOupB32U6yBDuOAOfYSRsVeiv15ld1Sf7jXAAdYoPWu22O2qn+0Se8kB42UmbvrAqFcEvbguUdin9vbXs/4lnW2rpFuwQhZ0SLcOZ8TsNQDszA2q9JIpxkjVgUUI0ZUq9WwkzORGRt1UBOOv8aBqntlMO6Fdf9rtbnrW5mfWpBcrzfzap73r+oYOdsjSSzLZeOZoM77qws5urY2YTLQJbetfn04X3UcmnSOFMvmWlBJXOY3Z31kZlQpG0f1521lz65OQaM6MmXn+WjA9d2DplDU4wplotneKqhPp1ighC146J8vEllFXAz16LY0HG3b9O1fBIhPNmR0KFsfCN+i2SrqX/DLHulGZ3zhkSqheX1Xf4hsDWH0q06uUPsAy8GSzq8zcpJTmdO+sjdRj9q+u0f5e7cyUaJkw6tNfmU09K7FVhxqiBHfr2ws1Rv6QNaKMimWOt/7hrCAzPhnNN7r9rEwrk3XZ/dmnVplSK9uDtE9isKCQCan+wgisTteT9MJWr5e/VuSz8nnR2ZShr9jo8Xt1+m/wNXP8ZHZL/799YNQfWHZudhf1SgvdSDDgZR7doSLZ5ekwyhw3dhmZX89aafj1sfWbcSzoASxpmbsyM7cL+p/5+huHsw7gPl/7c6Ben84FGxkcC7JwA33hCRZ0ehWcKTt6qQhnZeagDQa7R/pwrF7CSzEA6MYc6aFh6Kn+V33V+0HvkEyCnsvXXhWw69Dna0+bHvQAh/6pT6o+X3sRVY9pj/JeGdrhXOB4jlW1o/Uiyf/Cv7oo/J/P2WhyH2L1/5oxK7R1gkxgZfKVXhA0OEqftHBumDjHYHNKsAZMryR6Sw0hrmf1v8/Xc+mBc+q1HqF/ccbXM9do6zEwqL/6g/qk/0I3LIw40AM0Z/1q1MRIon81iuvfbP3BfmnbF4wK9Gra/w5f7Y4y3tgdZbf03HPu8/X/B0OMErEJt/zIAAAAAElFTkSuQmCC" }, "Event": "nodeQueriesComplete", "TimeStamp": 1597143009, "NodeManufacturerName": "FIBARO System", "NodeProductName": "FGRGBWM441 RGBW Controller", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Multilevel Switch", "NodeGeneric": 17, "NodeSpecificString": "Multilevel Power Switch", "NodeSpecific": 1, "NodeManufacturerID": "0x010f", "NodeProductType": "0x0900", "NodeProductID": "0x1000", "NodeBaudRate": 40000, "NodeVersion": 4, "NodeGroups": 5, "NodeName": "Kitchen RGB Strip", "NodeLocation": "", "NodeDeviceTypeString": "Unknown Type (0x0000)", "NodeDeviceType": 0, "NodeRole": 0, "NodeRoleString": "Central Controller", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 3, 6, 8, 12, 13, 14, 18, 22, 26, 27, 28 ], "Neighbors": [ 1, 3, 6, 8, 12, 13, 14, 18, 22, 26, 27, 28 ]} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} +OpenZWave/1/node/7/,{ "NodeID": 7, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/010F:1000:0900", "ZWAProductURL": "", "ProductPic": "images/fibaro/fgrgbwm441.png", "Description": "RGBW Controller", "ProductManualURL": "", "ProductPageURL": "", "InclusionHelp": "", "ExclusionHelp": "", "ResetHelp": "", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "FIBARO RGBW Dimmer", "ProductPicBase64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAAChCAIAAAANwWdbAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nLy9eZwcV3Uvfs69t5bee3pWjSTPjDZrX2x5xRvGMsaAjZ2EJBiIkxCWQEyIEx4Bs2SF5AFhCbxH2AwYSAAHsMHxhi28yKssW7ZlybJGI81oNJqtp/da7r3n/dFWu1RV3ZLJ75f7R3+q69zl3HO+93vOraquRq01IkKbQkTtpEQEAL+xFBGDnUeP/7+Vhj5buv0PSJtqBI9b1YiIMdaq2VL7pGZvdQUAvu/7vi+EEEIwxhCxg0NDmgRPxs4oqMyrkoqgAyAChZCTgtPrbIJTkYaG/p+URt3z35G2OwMBqzYXcNMgLfWUUlprAPB937IspRQRcc5d1xVCcM5DdosCAgC01r7vu66LiOx4aeGMMXYqnbSbUawlT0UaA952o3ZeB79x6dzz/3/S/5kSRJjW2nEcIYTWWkqZSCS01k3GklKapgnHde5MV6HieV69Xm81CZJfE2HNT855k8+iHYZWY+y4r1aKrVDYmZk6zPM3i4bRUdqN9d+Xdh73vy/tsDibhAQAnuc1WaTp9dhWTUiFYmhQGoRpa3TXdRuNRkgU8mnzDGOsCS/OefMgymevdu7tpOEJnOIqb5fi/AZfIRA34UQgdpDG+rJD22ha0K6HDtJYO7RT5lQm2CyMsWCghICVggehblv1m8AKDheaUXS4IM6aBQMlOlD0+KTS+EUQUjFKCf9N6alw5EmlwfPt/BFSKeTgk2YbsWlTh/pwnBvaieDUKDxW7dDJVs+e5zUZK+TjqBFaugXjZhNhrYNWeVV6QsSeIsiWoXoQh7Zg5VgmOBVp0LUhZ5+iNDSfEBQ6jBsLmuAET0pCQfvAyVYCnIjmdk1il347aUiB6Nd2qAp11TrTjNeI2DqIgiyWy6PYeOWz84RjS7sFEa3TTnrqNdtJg/btDIJWlhP7tV2fITSc+vI9ac1XywQnLY7jtEJhtIRWeyxjtU6GMBc8CIEsOlAIOSIqg5NN/lQo/aTmi6W3U5e2DoIkFDwZrRzSLRaO7VZ5u1m8Kp2jNmlnpQ6pxasqr0rz2OyiVbTWRKSUCqKwlftHIfEKsE6dY0Kq/2a8FeK8UMiDiFGaE2udP8WAFTsWtd/Sd0ZSq2HLEyHy1lo3Lz61C+Ltxo1Vo93XkIYYSB6iqsKJXjiVwB2NBkFWa/XTiqHN8yEyE9FR22kZNVZopA4r8qTS0FelFATQ0yHJCHkrVvlYzWM1iX4NoZ8xppQSQkgpW7s5OA6pIFlGdT4VDjtFaYd+ogYJ9dMyQpBiWweh+p01DB63TNHsTYRWUgcXhpRuJw0NGTvDk+YuQUU7k1MUEyEknRTcIeIJgS/qFcZYE1VRxoXAKm+N2PoaumiEHaPPqRBbSDGIAA7i7B/bcztpLGpjFzOc6CMRO3ZwbidFSVQaq31Ujw5qQcS1seQftV20SSi8Ni0SHTRIKkFoRlmnmcOGtHUcxzAMRPR937btWq3WaDTGx8dLpZKU8qKLLjJNM4SzWCt1Lu3mG1oSIeU7k19Ik5C52ukQOo72L+BkWI6uxZZvQvEl2E9nKZyI/aB1IJKSt4tT0TXaOdCEhutsazyembZwxhhzHKder3ue19/fPz8/n0gkTNM8fPiwEOK+++4zTfP0009/8MEHP/ShD+3YsWNsbMyyrMsvvzyZTJqmicdvF8ZOJ2qNYDmpKWJhdNKZtqOi2ArtFIiulmYRIQB2iE3txmiZvt2sotLo1xCRdLZysPNonWaFYAIUDUathk1RM0NyXdcUxvT0dLlcXr5ixZNPPjE9Pd3f3z83N3fJJZfc/+vtQoh77rnnTW96U39//3PPPbdkyZKRkZEDBw4U54vXvuWasUOHdjz88NTk0Wq1Oj4+3t3dXS6XJyYmOOdbtmyB45e82xm2XfiOdUfUEe0oFgKw6+DfDswCJwLoVNYnNnOsDhOLahzCRGzvpwj8zgux3erscCYKUDgevJRSUspGo1Eul03TrFQqs7OzhUJhyZIlnPM9e/Zs3br1ySee6C50v7B3L2Nsenp67NChSy65RCl5yy23jCwbmZqauv7668fHxzdt2oSIUsqDBw8eOnTo8OHD+Vw+m8stXbrU8/3Vq1fv3LnTMIxUKlWr1Xp7e5PJ5KkzZahCh8od8rPYM+38+6qkp9JzU7GYXWGoQTtIxjbprNCrqtOOY9utvNjcpVQqvfTSS6VS6bzzztuxY0cul9u5c+fatWur1erIyMj4+Hgqlerq6jp69CgAeJ6/+7lnL7nkknQ6PT4+vnPXU3v37c3n85vP2PLkzp3N6wjNiNZUQCnV19c3MzMjlSSgyaOTiWRixaqVd99997Zt2xzHQcR6vT43N5fL5UzTbHfH9xTN9aqkJyWn30yNzpgLSkWQPGJDdahNlHhjs5nWQVR60ngXSstiQ0MU2dGoR0S1Wu3AgQOPPvro8uXLHcfZtm1bo9FYunTpjh07BgcHW62aoGEMywslrfSTjz9x6NChfDa3ZdNmx3GOHZ0yTfPOu+96xzve0bwO0szfly9fPjQ0VK1WtVQ3f+vbqVTqda+9VEr5xje+ccWKFfV6fcmSJY7jJBIJIUS7abYrr1Ya4owOBNZZ2plH2jWHCBGIdlVPPbpFff8bS9tNMmSvWKSG4BV8MvbSSy99+OGHs9mslLIpLRQKp5122vz8fLOC53mu69br9dOGhg4ceCmdTmuthRBjh8bq9Xo2m92wceP4kQkiWrdunW3bALBp0ybbtoUQZ555pmmY6zdsSCWTwjAQgZ34gF4HLjl1AmgnDQX94Jl2i7ADwqKqwombtnZfQ1QCoVs67caLKtdOGmuOdtLoPKPKQARnUcrs3AoAurq6hBCjo6OtywREtHfv3nq9/uijj2YymSNHjmzfvt2y7TPPPHP//v3CNN589VWVSsX3/f6Bgd7eXsuyrr/+egA455xzmj13d3c3O08kEgBQ6C60yxxCnuhgqOhkTyoNjhjKHE4a8jr4onMPsdXCJ4NX5UPtYzcCseGpHbW8KikElojv++0UCwGx3Qpr7grn5ub27t3b09OzdOnS0dHRjRs3Tk1N5fP5Q4cOaa2XLl1qGAYRJRKJZttWGtR8tjOo5KlMvzV6lE6iU4jdu0TbnlTqOE61WsVIPhr1HQSAGDqILv5Qk1gqbTdTxtipPtEWndtJT3aQxjomqImUMkjpnRmxVQ0C2Ar5g4iazwS3bNQBoLG+DJXoammnXjvF2kEz2ryz1HXdarUKkSUX0jY0wVhgYYBcY1WFjsZpmYIxFpNjtasdBHKIwKJT6iAN9RDUPmqRllfaOSw0XHDE1mfrRl4rFDbrNEmxebJFckGiCoEmROFRJaOmh4ibQ/wXa+RQw87SDgaJ7acDTGPbthu6xWTRKUP0lk6sliEYQcCRoeGDmrWTtiOwdo5pR5An9gkArVmg9Jv4IEBkjI5fLAUiYgDIGRGRJkQAJA3q5d4AGOfUZDWlgTQwIAKpFEfOiICIAAkBAUirlpP0yyBsmhiQmS+rTaSJCKipICISMEBEBMaa2iJATD4e5I9Tl7ZzX8iSHWJOLIxifRE9CLWKeR4rxCtBpTGSQEQnGWL+qDQ6vWC12Jm0s0gIW0ppranp3GbU45yRJg2AyIg0IijdfLgRifTxxYsAQFpDsyfSSmsiDQAMAEELAiJfNXeaAAiggAAIEJAhNB8epOP6IGpTIoA+/qgPAidADQTIEBkgMsYBBGMcMd6YIVd1lkZFnUtstXY9dBg0WjMoFbHeiu06dhG0G6CDNDpKh0gXy20hBm5ZnzQoRdSEBQEyFFwgaz5lCwQaATRwBGAMSTcfJ0LQihFprVAqpjWg1qQ9zyHAhJVAAmAakJCQK197HipJQBoRGCJjwJGa+wwCwmZgtZFx0pq0BgQNQMi0boIIARkJA+Hle5GhyBhLGJ2lsUaLXcAnLR0qR6NEtH5QKjorERslozU7R/EOQTA4SmgIjHsAJhptERGg1RBJge9rrVRTpARwBkIwwmZkbAYlAgRG2nMd0JpppbTW0kPf8xp11NJA4NIF6XuJtMh3MVMgSVAgSHmlIvgOA0LGiDMUnBlcIyCAfhnNDLw0Exy0Bq1RcMU5IEfNAJCAAeMEpBkncUIeAhF6aLfCY6UQQVXUZbGt2rkj6K9Q9hZNWkIBGkKMFcsi0SXVbs7B+UTtFTvbkGbBMNpZb4jsdACACKTUTsOXnm6GQsaUxbUpGOcvA4tpYkCafAPBX1gw6g3SCpUPvkeeg/UqNerK97j00SnJwcV8zVrtMAEgAYh8Xp9nTp2UhwKJM+KMmQINgZxxIgJAxkjNoRBMStAazSRZeQCOmmliihtEJjFBpDoYJFbUQRpNvKLSzpCKrRN1IpzIDlESPYGxIMIlQY6BCJ46AwgC7j8VaWisk5ZXAt8rtkAkUERSac+FyoKrXKUlAqcU85GhNoRhMo6AWpHvoZQaJFkgDh1TlbJQHvPqWjrg1axyTXpVUS8zWdMMzYECNsoGcUCOytEMmWEAJqjSgHoFDEDBQBlICIyBZoBAFgcCkIDSRwJmFlAjMFMBY4q4bzjItJEFDdw3EQQzERAIoLkliKXtkF+j0qBlojRxKoZtxykQt4BjW4WKiK3RgW+jE/iNpVG8RkePIj5WN41aA3lS1WpOecHxSj42NDDtM20nMJEQAFp5DUFacx+VEiRBaDo4xmemmfaYX7c9l6TDGjXu1bBRI7/Blw/rTJpqHqFLpJjySEtCINQMGuSVyPHAZJRJIYCUknHOTUNLhYBkgEIpDIukB+igyHBCTXXlN1ClmFgueA6Eq7lHKAAMJAYEyDpFxljXtLN27JI+xRILzQ6QCI1F7W7pQITuoA2ioxWiWVSsNJoNNJdFKBRCBFuxNiIgAvKlch3pVGW17Dfm61hqgC9d0jmOyEmphtCuaVBCeAK0DYpsrcaPiMOHNbikGlp7qHx0a8qrc1cpBvx0watVrHqOnLZ8DUDoVIg8tA0wAesLzGtAUpCZJztvIGglQdqMCUAfTI7K19JBn4NyQdQQgOkq6AbzUFcdbRAYI8g4agHAkBQDAjxhnYciQ2x+eVI2Omk+3rl5J7N3bP7yBVJogzuIg22IYCDg/g6M3ZJ2IKHYaXRgzdZApFC76JZVddarTztywcGSY9elnizVporKdbPcSaYwmRVm2sVGjYoLjHm8VtGyYpAE7SpygREJoHxa92X8xX1qeKnICDF+lB07KjMp1C4vT/nFo8xEyBhceFo10Dcg3Y1WPzke+gq6enW2wJII2uXga89HQMASKY0CkBT367ZfrNeeLC88b/dfmuw+nRkZCaYGyRhwwND7FKJLsZ1roziLZlpw4kKNGjbq9NBYISKA9rTyyhKJzdZje4c2aI2O2k4anHy0t1hzxH49fhKUpyvF+uxkafpwaWF0ARbclEZnomS+OGMv1BLSS+tKgjmWpRO2Q1yBXyXTYyiZoSHLYbAbexOYSSEolhaYyWB/j2Gm1WRRo8f7e6i3n9XmlTsnkjair1QZEsQsrRMN7U8xp0S1GvqA2RpLemQl9UKFe4oBgHRJlxXWGHDUBjTmuTOfJgN0uTHlzi8cznSvTeaHgNkETKOOeivk6Q77p2i+FarQrqt2uVS7jeSpxFkRrRHLw9GWsQl46MypS0OLILh0Wnl6cOiWskQoparVvdkZd3bCKY6WGgdLNNcgV2Yn3OSMn/MhKf0E+pZ2LSTgLlpEqLklaTjjr+tlNme1EozPq4UxXJwDJFrWbYwdoskpUD7kk74nccNKtmatTOatVWu08sEpwuw+EiVmuMAlQY3lPQ1SWS8xPEyScU9qT3FDKa/MeEMYiqRASoAmZSIXZopNO94LTHZxsYmQ0PcQODKB9Mr972goDLmmcyiMXfyxqIrGmRZ2Q7u3aM/tFAhfSglpEOKw6J4leBBCQAfpqQS76BwgslaIQErleX61qmoLfn3a0SVl+iLpMPOQmyuxtMMSWhlEBAyZqbVP5DJBNJKBLasUOnxyGkljsUYDOTWUMgzJckm5Z4obDEjTQJ9iTGR9duBFVTpqmqDmjuDyRZS1aPHrkM3oo8+jOaO9IphSdCWkcInKJBFAauYzkwE0tHa5CQAeSNK2LbLnK0wqbyZvZQoDF7PcGkmMaXD9BinFGGMQb5zYPKRdAtOyXsiJ7VwT+k1zO0eEANdCRbRVzJX3aOmgWWjgdkE6JA1OuDMvRvU+XpkRodZaKU0EdoKnk5DNUX551lqcYY4v9i+Iu0YTqi7RMRlxElp7Htf2skEczlJ1xp2YNc5ZrtPMq5TNM4Zgeo6Vi5S16PA0W6irvhSrMn/3jJ1Ow9YeShHzOKVI12f4ERLQB/6Yb+TE6Rer6d1cjEKqrA0fhGDchIUaOIpzJKnQtjSTxFB6jrC7WGIbZa5C7DO8F6my1zMsUzAOxBRnZIJWL5N14H55dKMDJ3JJ0MIho3UIjk1pkx1DdAgR5MU2b9dzs8RceQ8lQx3SqQ5Tiq2JkQwsyGEd4BXSrVlNa5K+BgKDCzR0T0+6cGZKuz41fOl5mTXdMweOsGdqhhYmeYLISzF71SIw0X9xnC3PGVuWggNwkJm+QX0cG5IJk/bPyWpdHyuatgVHKny6DCtIPTvHhIMXbKXuAaO2AKVDVKpifgnjWh7bb+SWo9Ej8XlGBzkDIkHMZiaAqSnBUDDGTQACI0N8I4rzlbkIzSE0e5RhiEQfAWpVV65WhEhca42cIXRy26mkOB1Ky86c8+bdzODPh0LrP+SpzjR0QvIe5aHYaAXtfR9ERpQbo9LQ9DqM2EGr5mrTWnJuIDCftOO4mXSa91gcAOfdmnssv2YA5ziNzySImSmVOmvYK1Zgz2GxMustzePMAhQ9uP8Q1y574oDe1KMajvVsQ1sIvCCLJl2wkpcr2imT70IuQ1ZeWdx99mkrpbDi0TEUI6sk6/doynTQWHQu1WxJY4IY5GySABahbSAIkA5oKXg/sPUguhlmADOU6OXJdQgaNAMtqrVpYoKLFHDRvLsd3cFAm0UOkcgYSoxi0wnXdfc8//yGDRsN04DjDza2jByydjRitINES3ry92u1g1Gwo3a5V6z01Etsw+OdE2PYfFyAccYMMFIsmTcTCQOV0r7EmjY9bbiupRCzxC9YBmNzllJ862mwbtBIpQyP+L5JWNerF+X8epoer5k9Q36hH+qC1020TWO+zLCBhku9WRxe4lUW8NiEPTjIBoaUAlKOllok+i1r2GNaOs9Qcj1LbdB2Eo0M2AUQXchzALb2GXkcVDdhAYwCiiwiA/ARXAAABAAETkRSK19qpRXpNjdzOlsmhCc8vu+J1mSMVSqVT37iE1NHj3JkzTviHWAUBG4sDUWlbZ95j42PUTIMHZ+iNJgBRHc9oebtYisRGYaBgAgoOOUGUoKhnHX5gnIIU5T0yEyQMAqWOHMxqxTVhkHo58oiKpbZ88f8YzNi8zIhq7gs4T3R0NPlxp5jybNPk087upDiK0zdb0MakTyDOBmWWTvCFkratIhlfAttwahyQKcyuLDPWASgu5WzkycvU4bB6AgCEQggRlhFngA0ifeB2UtmF4okEYB0AF0QBUAC5iNXCJxAAykCajJW1Fax1oj1XchQoYZa62w2u3HDxm9985vX/tZv5XK53r7eVDrdISiFMrxYBYLHbR+b6QDMaAlVbgevWIVipbGdB9siIgBxLgBQ+tKw0GKpyrEqzUlVkqwu/YpWXLD+DCzrV2PTdMlKP1Mz5z3OG9pDqCu2dogvLciax3pSbOywOLLAjjhqg+13A9uUogGfezXaX9ICxYZhqlbZ2GGSNV3oYr1Z0+wHbjJWg/qoMitQRjDykEnA/GN88DUkq8BrCCYAkiIGDIw8mL0kcsCTwCwkBqCRfK0dIFCq6PvzoG3GDan85qNajL8y72h+E2uraJ7ULlYQkeu6YwcP7tu3745f/pIL8d73ve8P/vD6aO4b+hr1b9TRzUFFcDUEdQq26Zzdxw7fQRq1S+fEsD0LMiklEUhfmjZrzPuNI5LNK2+qwmacRJ0Jn3DjInxmAi5cqbO+va+KNc+Vc8Zc3e9KW4u7dLkCR3y471lhE65Nixd9VXUNy+bSIy1o/KC34jQz2a2efxGgpJMmE1nWRWx+gU5bJt1pfrSGS3w2tBVhRpePgnkayCmEQyAuIngOqAh+jbwyAAFHQAuZgObzWQyBM1KAVAPtkz8r3WmkLEvmtFYKfKUFMv5ymDzRl6GDoME7uyB4BhHT6fRnP/e5H3zvlkw2ky8UtmzZwiIM0tmhHXwEsY8mR/EUy17RYdrhPQKIcIm1TjM/wONvYQg8rs6kVFqS66paxQdfm5wrIRolV9UUlR1WIlXUNFvBxT24bxa6UsbRqmogTXowtWAkiAay4oxe5dWgqJmr/UV5URDgVlH3M9+m5WmvUTKntLbzom6pDKAAwDQCVxmF7pxDvo0awEJbacyy5FLi3eDPc63BTIOaB79b8hQXRWJZzuYAXWAeMAloA9pADsgZQAcgBag01rnRncymfZ8nUgVErjRwwQgolFCHdj9RG8ZiLuT7pg05Msdxv/iFL+za+dT5rzn/4NjYxo0bIcA3wQf/g/+dcVLXt8oJ9wo7E0Ysr7RLBTpI2xFh62RL1+b7gJVSyWTSsix4+c3VBACNulctem6JVM1LJuw6uVBRasbhC1pXpa67mLI0Rx9RLMlyICq6ZCWp24M+gyeQjtb07hdpIMNPXywaZc3qaNpsc8p7YJI/NYOXLfZrU0YiCQtlSpHWWiQNxaookkzkrJTw6rOUK0D9GPEhUBWUk8AUqSJYBVJzQIcNY6OUs9w2QGeRa2JZAKX1DKoMA0Y0DVgGMAHTDGxNmUSiJ5VOIZhN72ogrRRizE/yO28MQ6lYaOtNxy+EaqK52dkXX3zxIx/96DO7n9kyMnLb7bevWbeOcRaq32rVelVO6D9ygkPE51gnTbaCJ4NfO8S4dtLgViCoVvO81npycnL37t0HDx6cn59v/nx5/fr1V155ZfMFG81nzGWDqMqhLnxXmmluKz43W0z6NrhEHKAnwxfqsishui08UsapBvSm0ZRgSiJSL80yO+unkmgL37DslK0NH0mLS4foSNmYWnDXFHDsoPRd9G3ozsv5okim9ewUJUFbWSNpANWody0DRx17TOQFQJ38eRRDSDXFyoySzFpJNIF2D4EAKCAUGCDoaYAi0mGCGUIHqQtYP5BQ/lIAgzEBBICMtCZNhDGJR5SHQh6FAPdHXdCszJElk8mEnaiUSqQ1IFYqFYAT0vDmm1QYY4ZhKKWaF72klM1f0cX2HBxXhLwe8nQHmmkHtVBpJ43SdfN4fn7+7rvv/s///E/TNAcGBpo/OB4dHd2+ffsjjzzyZ392w9DQMBEhB9LSczwCZWUsK42GaWaX5PWM5xb9ZG/e9T3NVXpR3k8arKbRV4b2UNtgI80tsFSeoWOt6NPasft6lazSZBGEqfKOiXNgo7XgEbd1EgVxzBaICd+WRvdiT4DIZbSvlG2bhV6/ss9QJaAUWRylASKppYmmTTDO+DDosiKbgwDIEKaBZQBSQFUAAlAIFaA66RrobuWh7yoUKak5Q4tzGxkivnLTMNZ6HdATe4zHfwmHgL29vW+77m3/9OnPEIJt23/793/PhQh22MQQET300ENTU1Pd3d0bN27s6upqoSrWua9E20996lNRHETjeohU4UQIRifTThq1RZC99+3b97nPfe6ee+5ZtGjRlVdeec0111xwwQWbNm3q7e09cODA3Nzc2NjYxo2bksmkIQQj7nt+75J8sttMpu26cjgXJjNq86VEJi0dN9OVbxSrcsE15+piaV7Va1TI0kKFPMUsrgcsneGs4dPMPB6bhVKZZxku1CFlUlorUmhyYorKdZ7Py7QwlILx/ZBKsNVbcHg998rgHCXuoE2QTEIStZSQWYpKcrMAfAZwBKiEkECyEBkxQcwAxghnCScRF4hXkZWBl6Ty6rVGveqU69JtuFIiogGAjLfeGNuMkGFjNl0jpfR9PwSv2EDRTFg5Y/fee++Oh3fYttXf11foLlxxxRUXXXyxaVnB/psDNI+f3rXLcZw1a9Y0//CnnU+D508AVrtsPTSTV8aOi3QtLHaOkiEINlfG3/zN30xOTp5zzjkf+MAHzjvvvEwmY1lWKpVatmzZqlWr7r333lJpYd++va973aVIzE7z3CIz2WWCQOkSl4w80MAQJBNcV0keLeNhD6f9uicNYfC+HBmEk1XWY0oLzOFFWGtAw9XlKjJCoQl8Va9w2yDfhf4MQYPPVvWiLPccrJVhdopMA3t6sLubgFRtFLnPuQEZg9IGgxqZGW4BqQWW3gw0j7xHswKDKgIjQEQDOEcEZA1iVWCIrBd1H1BKS3ScdN0xGo7nuaiVqYkjcs5bfxMHTd/FxjWlVAtYGJfjn+BTRM74rf/xo3+/5Zb77rvvqZ07jxwef/zxx81k4owzzmjl6QjIkCEAAqRT6RXLVzTqjUw6k8lmWgMFxwrBAxHDOVZoX9bSLHqhocVJQVYLRswO0lCp1+s/+MEPbrnllp6enuuvv/7KK69sRvRgb5s3b37Pe97zjW9847HHHvv5z3/+5jdfTUQISD4pqbUtsYba12Wnkcrl5VhVe1DXmEiYql4TqaTM592pY1apZg0PwqoEMJc4+dNHRTojDPSxzH0AC4ylA+RU2YImMBiZygOj5gPzmFNysWYPrdS5BFSnvPEpc6AAXX1aANhVEEor4HZeygqaXYrmEADIZywPlABWBuYCMxATwJYCrmZwHkAdqEx4SMGch64WXcIqoEQiJpV2XY9xwQUTRvNnINDEVsjC7eIdxLHIy1IAIr1p8+Z0OmUnEik7QUr7Sq3dvKHVZ9P4moghAwTf92+++WbTNDdv2YJxTzGFFGiWV35iH42a7RqHAmIUPbEbSYyLfQAwPz//la985aGHHhoaGgk2CGoAACAASURBVLrhhhs2btzYDO3NWN76dbxS6rLLLtu1a1epVPra17629eyz+7r7tQva9UGDYYv6lFM/0mC2AAfkRMWTioPRMJ3EYJJZ3AVHppnIFypzC+l5jgtFX2gu0lSpQVeaez6QIkZUdPXqAaM/p8tFpXzGgLhmris5mmvPJCvBTOVXJ0zTYabwLYs5LwEnBBstJCGJXGHapB2ELACA5qAZiQZgFYBA5zTMMQDAHKANVCU2rVVRSs91ueMwz+1RSiK6ns+AMSGEZZuGEfZjaOsXD6B2iS+B4GLvi/vuvvturRQqjQS+kteJP9i69SyttO/7nDEhBHJGQABYrlSGR0Ys0/RcN/oa1XY6xDzKEntBocPm8VWV0HDT09Of/vSn9+zZMzIy8pGPfGRkZKQpbXJ7c5fbPNMMCnv27Pn6178+Pj5+6aWX/dE73l2b892Sywm5FlP3HcESZUYK9bmiMS3NgZw3XhY5y5+vEUJGa2aSpZRRIixIY3aOJRFMF2wJ3BXYAHC0W1W2YmlkjSqOTmizrkDxtE3ch4LJuCThEXN87turVipTkwlgVnkXUCKHyZxmDseCsnKESYQMMhMgraGIvIpYJhSaehH6GGYRTEKfsAYwRsr3XV5vpF0v7XgpJVMAOWHYlmWlkslUOiEEB9DBi6Uh1zTfNhPyXWxezxARkAAaTqNer9cr1Qd+df9CsbhsxfINZ25ZsXzF008//ZOf/GT16tWIaFnWm9/8ZjuRKM7P33bbbVrr1WvWnHveucH3XwRhExz0lZeCxCoNJ0ukgtPrgKQgSwVP7tu378tf/vKePXsuueSS97///YVCoVWh2WHrpR3Nk1rrFStWDA4Ouq77s5/+9A2XXm34aeUoTeCUpZ0ypydn7VrKq7vaJ65kQ5Zzdl4LX0ntj3TXS3M93bmF0ZlUj51aNCi5m0gACN93q5jlul4iWxkCVNaQtQW2abkStjAtYghAmLYJFKY0GGjbGZnqY0xwVQXh6VQGOSLnSHWiPmK+YAwgCaiBDMY8AhfRBfAROQICSAAPUANZjBYr8jRqzThxQxNKqQElgRTcaD5qBkBNewRDYazLQiUkRUQiACRATKVSpmXe/M1v/egHP0wkEtl87uvf+pYmvXrN6ktee8mRiSObN28eGRmxbAsR9u/fr6S0bDudSkUTnnbwiv8jzBBLQZsVEIVOrLRZQq+bevzxx2+++ebR0dGrrrrquuuuy2QyQY5tZaNBGzU72b9//0033bT/pX0ffN+Hr7ny97WjkbQydMpAZ56ogU65ohuacUMTSaUNTcJmVtbWTCKpJCTIczzhW1mTGg1Da2QKLIXKI68GJikO3EB068Q0A/QYGPkc+YpIcsGAc80QBdPK4YYG0wAjRRwREJAD9wHrSD0ACIhECKCbv3XVJAFdhpLAA/ABNBEx3dDgOa6s1QzHMauO8BwTIctZ0jKTmWyq0J0xDE4EiDE3W1qMVavVQp6O/YqB2xhTU1Pvf+/7/vc//XO+K//9739fA9z4V38JAAxZtVp96IEHRkdH3/Xe95imOTN17Be3355Kp69845XZXK71cp6gg0LAYq2nB6N4D4IxyF4hnAU3ku2yKAz8mw8AaK2fe+65H//4x2NjY9ddd9273vWubDbbUqvzKtRar1y5sr+/3zDFL++6jdmap5BMQi0cIp5jUviaCWUaErVhCsEILaYNcKTDDRQJwwHpAyFxqJMgSyogSZqYBgbchIY0qg1WqlDDoVqDHMfUmsplXVngTgWkA7qBqg6yxLFKuobMA3SaHEOsQlQhLYgEESc6vnwJAAQQZ5ggzVEDkkbyUDcUNHxV91RdkefLhufXpXJ933Uc13Fc6SulXvm/mpDNY0NB6Ew0Y27+y9LLN4sAe3p6evr61m3YMDc313T0/Pz8rqeeKhaLw8PDzcek+/r6Lr/88v7+/lq11sJDCwxRkDXLCf9XGDqOklCwR2i/PkKZe+u4aaNHH330pz/96QsvvHDDDTds27at9Z7+5uXddpAKjnX55ZdPHDm8Z++eY3OTfYUBgULXwGtozZWVNhfmKqYQSEhIGrQCQzsaXSV930qaCsBAjgpIgiF9I20BM4TrETGybPA8XS6hCZCyGAMiXy8scJIAWuW6uSWQFOg6yYY2GKbSGhWAz9AmvqChDLqbUQGYD4BACMgRhef6xWI5mbAr1XL/orxgWoMHoIhJpT1PO77WruSuFL6HCTNpWXnf49IDKTURaE2cU+vSUrvtXjvwhVzesnZPT89ZZ5113XVvG1m2bM+ePX/+F3/BGd/97O4vfeGLW7duXb9+fU93d/NdS7V67bbbblu8ZEkqnQ4NFErkgwrwT37yk6GI0zpuNYsCP3p8UmkTOvfcc8+tt946Ojr6mc985txzzw3+HKU1ZzieXQUVC6I5kUjs2/vSwZdGV688fcXy05VSnna9qk/K87Ce6+r2ayS1PjZ3bPTwQQ4ia6dKM9MzR48cO3a06hctzg4e3F+tlnN2Yt/BA0/ue3aZlTs6P3vXIw8Nn7YUtMcssXP3c1+89fZ7n9yZtOzBbIbZJqS7WCIhZY28IuMSbAsTaRK2Ivb4Y7tcfzafG+DYA7yGTDZhBcgBxK0/+dlf3PBXppn+7D9/8Y1vutJOGoQugae05yvXcV1fketyjlnbWpS088lUl20npSQlwbYNw+TBP9ZqRQYIkJDnebEGD3kTjtMEY6xYLN59510bN24koquvvvot11zDBV9YWDjrrLO68nmGODExsWhwkAt++89uG1m2rF6vr1i5wrLtEDZiUYWI8W9Nbod3iETT0DSC/BSsT0Se5z3wwAO33nprpVL5xCc+sX79+hDDhZQLahI8RsSBgYG1a9c899zzOx5/4qILX2+AefvP/uPWW7+NKDxfXbHtijde8O6nnn3wq9//53qtOlBY9tH3feL+++659ZHvCTMpdXXrmrMHcgPTMxP/+N7P3nrfz+/e9avX/N33H9u3+4vf/j+v37IllS08c2j/+770r2nD9hluf/y57/39X67qX8I4eN4xrh1mo07nMJ1RpBmKeqP++c99+6prX7N8+XkAvtKeJFdgxqnXDKPXMNFXXq3GSAMok0D5vjk761u2SmV1teE2HN1wawtzdNrilZboUdLQGqX0LZtxJpQk0qy5JYzmslHjh7zWbuUTgGlbC5XyG9/8pnPPP8+ybWCMARbni8NDQ67jDA0PW7Zdq1QNYSSTycu2bfv19u2zs7PpTKZd7hQq8X/S1EGt6A4xiLZQ0GyhSkr50EMP/fCHP6zVah/72MfWr18ffCljsAlE+Dw6umVZixb1Lx1avOeF53zpMmTF4ky5Vrnm6utmZ+e+c8s3hwc23/LDfxtZsvrCc7f9+Pav//TeWwxKd+VyN1z/oZdGj9523/cHLxo+uOtouVo9cPSlYrU0VS2/dPTowOCSpJnySs4td/9yINX1T+9/h+b0/k//6707Hl48kHrgV7uPVKYueu3Z/SP5u375dG9f6rHHdm3aslkY/Ohk6b57ds9Of6evP+fUIdMt3Vryrjvv7+rOffjDHyZSBIBMIUPf01/4/HfuuP1XPT3iE3/37vvvf3zv8/OO56Qzyb/68NkKFZEg0giYSCYQmn99eIK1Q8ZvF4mi1jsBdgAMWdpO/P3HP7l+44Z0Ov36K6+87PJtnDFN1Mzsmp2nUqlsNvudm282DGPz5s1RLohlJQjehI5FUrTE1onOFgPZpdb6/vvv//GPf1yv17/0pS/19fVF20ZRFSotumpWGFw02NPTtW/fPs9rJBIpBVAoDF533fWlUvUXd9y6a98jM6Xxmz7y2aX9yxb3njY5MzF6+HnLsBbn1x3LzOXTXVtWb/3F7T8dq03M1IvZdOG5I/v2Hxnfsnyznlcz9fld+/e+8+pr1qxZB37l/378RtOS/3LzT+/cfWjjxt7vf/hfP/rRP/6bm766ev36nt7C97772Rv/6v1KMdOGX/96xwvPj61dt6J/oOvxJ5/7k/f91mMP7f/zG268ZNtZwJSnXMb4T378s5/99Gcf/8Qn7r/v3v/14a9sWrd8+72PvuHN52678lxNc26dE2hDcESO0LRcM9ltuyWMNQ5GUvuXpQDNe0MMGWpKJpOve/3l9Ubj2PSxI5NHkDHX9267/bZKpbL/wEuVSuWaa65BhhddcrHnuradACLC8JXIdsgWUbLpXKKziq3Tqial/PWvf/3DH/4QAL7whS/09PTg8Wd6gl216zP2pO/7uXzBMCzf96anZ62+vFPz52anv/H1r00cGV+/blO+N0+m0dO1+M77/vOppx/o6l/iunLi2MGbPnvDQu3YqlXrlvWPpFL84ed2GIZ13sZNT7/49OSxI2/Z+loAqyqpPl/u7cqRyHNhLF9CE7NTDz21/yMfuPa1l6/5k/f8n7vvfsawxQc+dN3KVYNvfv0Dp6/uGlxSuPT1W596YrS04H7m83/5+c/cfN5FW373+ku3bLz4fX98w/LTlwAi56Lhyh0PPZ/LdT/x5ANTs0cOjh7r7+4dWJJ/1weuEIZFxJX0gZghNGdCKSWEIYTRLpwF12E0hsTnGIiAyADmZmYe2/HI2NjBD914ozCNRx59ZHL6mNbaNq3XXnxJT28vEEkle7p7PNf79re/LX1/cHBw27Zt6WwmpEZUt5cZK3Qq9AkncklsEOzANL7vP/LIIz/4wQ845x/72Mf6+vqCGXosmcdmD6HzCJjNZJv/PFJaKC8uMESjXi8/s/vx+fnikqWLhYnA0ZOmR97YzIFnx/edu+n8we6RD73/M7NHJ778w7979sCegZ4lTzz2QKGrZ8uqLf/+i296vrt0eJnMZ9J+OplJTx04CIuXeGbtW3fde3C+WJPekrUb0l1L0j250SNHkSXSWWnZlDDzpBqI5LvAmOpfnO5flC2V54cGhzjPpTLzno+OqxCF1qgkNOoVBtpxa6cN961cPXJw30Qqkxa24bg+N6xEwpa+IK01aKWJM40vv8j0lS1OECitDPqk6/yVmkCI7MV9+77wL/9SXij9rw/dyDl3PPc9730vQ3Qcp1KpIKJpmolEgnHGOb/6qqueeuop3/ebt9qadxJfAWvANUEdWCzwo7WjaIv6O4QtrfWOHTu+973vNRqNv/3bvx0eHg6iJHi9NGiCEKoCXxEREDUAac1MK8m5WSzOHRzd7/vaSsNA3/An/uprH3zP5599drdQ3arhPPfCrtdd8NbXnf9WJrWnlG2mFnctWz603uSJmdmZ4UXr9xx+dmjgtNXL1x2cPyRMq7dnmIORHVy6bPGK/3rk4ZnZYwcPT3z7rgeT2bRtp6aPjVd8PjdbXL+mV0lJ5HOuNEjNfNC+V1eafKmlYu7wstP37R1tlNXuZ3Zn83Z3b5/SikBbCVy9eqVhsj+74e0Xvuas6cmpfN4i0FqT0lapPl33HOLMU9BwpeeRLxXjBKBJYzOaRY0TclzIgK3QEfAKaaJNW7Z8/stfOveiC77wlS9/5nP/+wtf+dff+f3fa17SKJVKhw8f3rNnz84nn/Sl1EAzMzOe6yaTyebmtPV/4xC3RW2VmHc3xDJQbJ3oDIMnd+zY8d3vfrdarX7+859ftGhRMN7F89ApXKc5vj8ChmLJ4LAv5f4D+y48x/ElOI4zN62Um3PqZd1Irjv9NV/4xgeXLRs5PHnMzHIrZ+8/uuemf3l3vTwpTPP0tWemUj3ikR+sWLmiu6+QMNNDS5YlU+mGI82S96cX/+6Hvv13v/1PXya20NfV9SdXXdKTNz7zt9/qWXQHMHblVVf8+D8eRkQFUlhCmInexbkv/etPkBtrNg4o8q774yv++s/3/N5VH6j7pfd98H3IWTKbYCalstY7/viNn7xp/7VX/SVy/ac3/M6Lew8lMylfC0VQrVXLxclcFkDZSjLTsIQQuomM41eXgwCKOiXWU60SbJJMJlevXj0yPPzXf/3XpmkS4tvf/vbrrrvOsq2tZ21duvS0SqVsCMM0Tdf17t9+fzqZYs3/c7StFmMFiSYEegAIP8LcThWIo+IoOJpFa71///4vfvGLc3NzH/vYx9atW9e6XhW61h8b+IgodEsHAAAYoibSRCB9pjx44MEdN374PWtWrf3z93768MTeZx4dvezC3/N9fff2f9u64Q3pfNdDT95SdSpnbbm4Viklrb6Dh58EhGQiv/nMrWuXbzl2ZHrX8/e/Zu35Pdm+O3fcMdg1sH5onSCWbHg21sbKB+7eeW867V56xuqhkVS5235s956KK7eet3lgcXLHgwc2nHVaMmHs3LVv1drhSqW2d+/hZMruKnSvWrHJZMlj45W9L+xJ9FqnrRiSHi3MOl15cGo4tDRfKfkHDr5gJcRpI7nifL1UdowU1urkOmajbFo8l7J7SQqGvFDI9fTm7IQwTYOzE8wVzFhc1w3e0gnZLZTANJswwJmZmev/4A8uuvDCXC7n+v7ZZ5990cUXH5k88utf//rCCy+871f3JROJt/7e7xLC3uf2PPfss6ZlXXHFFcl0KhYkofLyX55ErxHErozocQgZreaHDh365je/uXPnzn/4h39o/rlosG27aQd7iLtXiEQakbQG3wNZhxf3H/7Ah94ulX/TjV9dvWrN5Eta+3Ui4fsqkTI9l7r7UsC1W0XlerLB7Yw0klxK0MoXiKZgJKWphfaVj8BcLZRrG4YpRIY7ab3AxSwkqtqYNQZt3WWyLNcGRyOpyUO0FHoKXAWeIpsBEAnBkgIySHmCqiJZU/Wq7wBykyUFppSctUTWNiRqG1jD8xsNf9aXnuvpSk3VHKpWmFO1yLdTRk/CyFrCTmfsnt5MNmtbtgEnMkTQXK3/0okCKxQNWp8c2cLCwp9/8INf//rXc7kcICqltNbbt29ftmzZxMREoVAolUqLFg8OLl585x3/JT2vt69v69atiVQyyETRjByO08crobADIQWl7divdXJycvLmm2++5557vva1r23YsCG2ftAKUViH4H9cq5c3y4hMa11v+KaRHhgYfPa5pw4dPjQ0sIG050sg1J5vJIXw3eLEuMpmRa1RJwUcLK/B0pZBuu7VuPRVNqtqZWmYmgOXUgtgQjOfqYZbHuxKc1FIat8A8lTR4SKVzpBFwEkiQ8goqJTntJXMMwMABCOhJSOWq/hFV+0DFBKENpCYZVISJJoJA42cQQkGVWSerx3OwVBJCVzpBjChtIdcIEflsbrje04ln2G+xy3TNAyjwyKM2iomrzrBjAAAWmvTMtOp1Nvf9rbh4REm+LZt217/hisIiHE+MTGxZu3aubk5LrgwxJo1a57ZtWtyclJr3fodXpQ7QyoJipBnlFeC7UPYb4maQK5Wqz/60Y8efPDBG2+8ccOGDaFqHRK1UG8QKcdPImnme9J1Qfqip2sxqZ0Hx/afsUp6UrmK20lUDtYd2XAFB/HEUw/PzI1blsU4IYhKtZjP5zOJxSuHz33g8V/0FZanrPyTT9+eSiVMkTpn62uefv6p+YUFwfG1a85ensg03L3b92yXp9n5pT3A5Juuea1T8++88z5uulNH5wFyg4v6F2ZnJyery1cv2rn7hf6l/dwy0mlz787R69/7+4VF3ZWjte9+4zt/9qH3FPI5JM4gC6gYmgiuaZDQBEIq1xPAUDefj+FAyA22aFGvISCRFKzN7dOQ9Vp+bBkwtGWDwNJlnPtSjgyPIEFxfh45a9TrSqmRZct++ctfnHba0NTRowdHR8+74HzP9Z5++ulKubx67VphiNDLlULgCarEgv4O1j7FEqzsed6dd955++23v+ENb7j66qtPvR86Xk46CiIwDobBXFfV6yqb7WOcHx4fHR8vzxfrrnQM0/a1Rww8hT7BbHFyZOiczZuvaPhusVxesfyCM868dHLuxbpfnpo/MHb0mZIrs71LNp9xbffAsjsf/vnR0sKZW65dvuzcu3fdP2cnd40urDlj5Pfe9ZZSvVar8f17p154/pDrqfmS+9Y/fN2Wi1aKlL7yiksHl/a88S2vT3fZl2w7503XXnruOedPT5XGx2YMZAf275s5WtQ+RzI4moqEIgEgEAQnO8mNtBApzpKICQQDQSCYBuvvKxS6s719XVwgoELUiK/QfGdDBeNAu+xeKZXNZt9yzVsAoN5orF+//rJt2/DlF6Dys886a2ho6Hd+93cZMqVVo1FPpVJnnnGGZVqtblsl6L6gYuH/0omiG07kkg5s/Oijj95yyy1r1qx597vf3STwEHMGP6E9KcaaDJEBEJFmjAyTGybTCmwrg4hzxSlfasZNT9YVMQmeK0Uql6lVGxL9w8f21fyG1Myl0uHpZ13oN1M4V55eMnjWTPFQ2SmVvdJ0cW5ibmy+Mnv+2W9eUGAkeo829L4KFDPLn3r+Prl+4NrfuVwDfPur93DTe+Nbz/63r996x88zZ194+srVucasVswFVI4jn35mT7Yrk8DM8GkjYy8dfs0FZ4+PTg4NjTDkHknlOxKlr2pa1RE8JI2eS0jCNJImKZcamgb6enOZgWwuA+BxkQTQQNC88B50Wyy2QuaNrdB8HotzXq/X/+Ef/zGTTm89+6y77r576dDQtb/9W0uWLLn66qvvuOOOgYGBXC636YwtSinDMIRhHBwbW758uWEaoVS73XDhC6QQCXNBDMWiqtlq165d3/3ud4no4x//eDabbUKq+bh6FJchuj4FVAUDuSbSXHhdXYlMOuP7frkyX/OKKXuQNK9UG5l8qlRyrJTs6csrImIKGHGTGcAMyy4uuA2HRo88hiTq7lFR7hobf66y4FQbR3PJRENLNHlPlps5dsxG44xzl3b3/uixr+Xvffb9f/rbXQUr19XV19/3++99y84n937rq3due915I6tNAG0wm1NKObZb44k09vZ2gfZnp0oMUpmCLDrz4HmCG4xZngIHfJ88z/cNIO16TBIokyNLJ8yB3h7bzgCSYQiE5t85Nc39sm1iE5rO+UPQiS2zz83NVarV79x8M3J+xpln3nXP3W+59hrOebVaJaJ0Om1ZFmPMtuzh4eFDB8d2PfVUT3d3d29PKFdul5qzIImFInRshAoCopnNEdH4+Ph3vvOdsbGxT33qU729vRB4nrgVlaNYgRO5MDjzuDyveZ4BIKDOdolFS5LZfFqTqtbn6m5FASgyy3VPWFwj1CqsVncVYt/g6cOnr9FMey51pZYOLTu3VtVHJw939Q2aqdPK1UPLl12yZet1S4fPd/z0wem90qDR6dlpV8wl+NPPb5e9iy99+59BIbVrdG/3UE/vokWlOW/n4y+e+/p1b/3Dy/bu3Ksk+kAKFpI2O+/8Dee9dsPpq1daprFq5en/deuv+5Z3O+AhMxSyaVmccuZqQLOOO+e4JSnnXb/qs7KCCmkFiWymx7IMzsE0DMF56wcJCK8s4HYYiqWDkOOCbVPpFEP8+U9/tnvnru333dfX3yc4P3Rw7OGHHrrqqqs2bty4evVqgwuD8bO2nlWtVjnnmeP3c1pbhJA+4VAY5aEgJKMHLVQ1QVOtVr/61a8++uijN91006ZNm9qFdojwX7Tn1j3E4LMPgXdjvNxKCGHbfL6sPc93HZK+J6XUJDlKzkFDrdBD9VoDudPVnUpmRLXSkF7OldWdzz8ALz3gunMDA2sLfRuzPeVHHvzP3CA20DdyqcHc+hde3DFXLJbqx0bOfkOjt7fbWfTAN77Rva7XnagYV/dNzr4gwMAE7X7iJcc1547MbD59jYOW7xtI6UrJuPV7DxYKOZOhq/nAqpEvf/X7n/39v3l+z3OeUmXplrVLjptKpRvK06BIE2iNHmkt0Yc+O9nXvci0rOZv+o6vJWwiqwWvKFxOeibkuKYN8/muP/yjP/rY//oIab10eOj/fv3fgMCyLCKq1Wp33HGHaZrvfOc7kTHP89atX99oNBzHNUyz9R6HWJC8cjL0873Qcbvw1AJstVr90pe+9L3vfe+jH/3oddddF307Smw6FZz8iWHulVvXENhIlkqlycnJiYmJ5vPdpVIJAEklH3hg+z2/+nlPT98fvP1Plw1v6O63c7mMnWKmYQKQ73uu6zYavlO35+ZV3ZGVitau7emkj5IxQuK+agjT7B7Icws8qerKnJ9bEOkE9g8kUrKQEz4eRafatVxkcT5FC3kh0wmPPHPf8xOFXH7dqoGicr0GClNWSymv4lo+S9m5ku17KU+4yZ6k3XBcSvJj1TlXe2kriUiVRlGR4oSKFDqoSVMNhzIrF3ctFcIU3EI0OGPHf68ag4ygxZp/3Rs6GXJBMNFmjAnGD7z0EjKWSiXv+uV/XbrtsqGRYUDcvn378uXLJyYmBgYGZmdn+/v7lyxZ8u///u+Vcpkhe8c732kn7ODLcEMubn1ljJ3wctsowqKoCmKiXq9/61vfuvXWW9/2trdde+21oR8GRUk7GHCDXQXHBYDWlYtisTgxMXHw4MF6vZ5KpZRS6XQ6k8kUCgVhoNZg2OcQK9drdV/NvDj6kLN3TpPIZrKFQv+igb5kMqk1kiZAlso0rLTM5BKeyyoVXqtl6g3bI1SQIMfzputocdvG9FKRKPQhgpX0GHdtZgwV+qWx2FIemWmmSUGl7qvk/2vuzaPtPKo70V3DN53xnjvpTrqSbEmWLdvYlgdwwMQ2HnA/XgKmkxCGbkhMSLvpRx52MJ3QCeatrPDPs1dwICYvDIvFAscNpI3TGEzbEgZP2AhjWZZkyZIsXd17dcdzz/hNVe+PfU+pTtV3jgT9R3f9cdZ3vq/m/au9d+3aVZWTO3dtdqUTQVTg+UUvThw3GCSjoyMOcVaa9Thq57wAfEdIGrmw1F5pydijLmGs2lyRDGQCiQQpgEiQkkjien6BMxcPjSQggKxP2JVqBT0EiD3sDfDp/U85o5K8+MILf/2Z//K+D33g9ttv3/PUTx559PtfevDB8YkJSogU4tTJme3bth07enRqcsphCWmD6AAAIABJREFU/N/cdttP9vxkw4YNuSAg7MyxMzqh7cpwpdwYMLRfGpUWQjzyyCPf+MY3Lr/88k9+8pO5XM5upAEgsIaRrsNh5DiOFxYWfvazn73++uvLy8vVanVpaandCVEUEUI8zyOEMgblcjkIiqVS/uSJ2ShKw2R1aHCaymh1+cjhQ695rju5cXygUqLgpWkqZURksyWaLuE0kISX49ZgFOdoGtRbK2nsFYQbzxPhC8/xPQ8O/fjhuamp9LpbUrq6gXt+XBDSrbUjL8cgTfKcxESkwgOREiJZxB0pgMJqOz5QO72huMFP8nXWqqfxStyspi0ioeLlao2qpJBKCZQSIYkkAFSkqUO4w30hgDECQCRIvS8z6WJ0tS0BM6QEIUIIhzv/9eGHJ8bG33HjO7jr/PmnPvVfPvOZf3300T/64z++cMeF3//+9ycmJuZm52ZOztx4w43NZrNULN16661JmgJZZ3iGZMskdNetQJlcSr3UnSXQ/P+3f/u3V1555ec///l8Pt+L52U2VQerihmG4XPPPffMM8+8/PLLp06dqlar9Xq93W7j1FIlUQJeoq+jlGma+r4/Ojo6Pj7ebh07fLjluu7k5CTj5eeffyGO4y1btuAqOMbHTES6nJPHHF6O0vGZRemVip5fWDn2+onjBze9+WY/zB9++dmLL/3kiWp7/788eN2/eff4xOgvf7g3nD/SrC1dcOkUOCRw3bSVPvvUi//pL/7olb0vtxrJ2256y1e//h0W+Fumz7/y+l2S8F/t3UenyjnX2VAaWQoXW060tv+N/OigVw58Rib8TTVWa8WhiClh67dWEwKUUsc5c1ZkL7ZkdKw+r9L7uYu4QKSQBw4d+uAHPjA+MR4lyYU7d976znceePVVkHJ4ePiOO+6glDabTVzh3b179xNPPHHzzTe/9a1vZYynIjWoZitb+LfLg7SXRqWS4YFJUsrdu3d/5jOf2blz57333js8PGwn1/Mx+J/eeCRzq9V64YUXvv71rx89erRarcZxjMxpHQFC6EnWL1omRA1iznkcxzMzM6dOnWKMVSqV0dHRgwcPpmk6PT09ODj4xhtvHD58eHp6enh4WO3cF0KIVKTJCpDX3XZrYSZNp9986sSrjeYclb/lra4yXswXi9WDPx8tlI++/OL+J07JdvuSiy85+tovVxfqrsda0MgHhSQVxw/PHzp47LwtW188+OqmbZuvecfbHvp/v8LyRIbR//iX/zFw3qSbiIHBIXcyXxqp/ODrj/zWzde/45abeCzGnQ0j3lAtqs/XFmWUpCTlDnNdl2rm9kyWYKhTRvdmkvJMPoRMTEy8+OKLt912WxAES6cXnnv6mV27dgkpCaWzc7MHDx4KfP/Ciy4sFoo33XzTRTt3/vd//dfHH3/8sssu+4M/fJ++k88oV68A++u//msDEL0ULCVcX3rppb/5m79pNBoPPvjg1NQUdLst9Jf6BuyklK+//vp999335S9/+eDBg0tLS/V6PYoiXBNVIFC8SmHRGIu6d1e73V5aWkqSJJ/Pr6ysnDhxolAo5PP5+fn5er3uuq4QIo7jOI6TOElTGUex5ziBQ1g+36rV8vnh0W1XLxx4Ggql8W3bjz/zo9rS0vDE4JHnnp7cvn12/vR5Wzc3mg2I0iQRbuC3wraIUuo4vOhHPnGZU5we2ff0L9xiXjRT6ftBMbjysivfmD22trrs+qXmyeXr3n79tqnzB3iFgZOkUggKwAihebfg+b7jugCEUEK61VAjKKzgaTNnVXrUUKSEMMa+9c1vPvvMs6++8srXv/a1EydO/Nkn/+/K0NDS8tJjP/zhxumNYRj9/IUXLr7kYkrp4GDlmmuuueTiSxrNxvSmTYyxM6qfBhgDOWe2f2WqWdDN9ADg4MGD99xzz9LS0he/+MXt27djZN0QqrfEyNbAeBzHjz322F133fXss88intCpATvLyNDWMHQtTXY8GzEhAIRhuLq6GscxpfTIkSOrq6uVSkVKOTs76zgOJonisF5fq9UbR4+9fvzEawcPPEdoUh4aGpoYP/DzR7a+6a2FwfyGzZMkaUfNdm5oYHFh+aob3ja2ZWJxftErOAmIXCGXimRttTpx3uR8a3XTeee99NwvatV6ay0MKDs1eyr1uAgjl/lvzL8OcSSA1VaWt79px/jgqAteSlIpgVM3FxRc7g0WKw53CSCosgcndAs76N7+pRNLj6z+UkqlEOeff/7E+PiLL774q5d/tWF8/JN333XJZZcxSl/4+QtXXLHrgm3bN23atFZdIwClUrleq60sr4RheMGOHZ7n6WDQualeQ2Js/zLEp81d9+7de8899zQajS9+8YtXXHGFgdlM7dIWf/hpfn7+/vvvf+SRR5rNZpqmChDK6IqbV9VLBV+9niiaVQTZ2cifpil2YqPRaDQanufNzc3NzMzkcjkhxK9+9auNGzcWi8WlxeXTcwv15mqSxhIYl/LkkROri4vjGyfikJXHx/NE8tLg6NXXJtDijhclzYJL4jjddOkled72GBQ9h8qLXc6pT1ZIUvKKpXfeMrdavfrq6xbnZqbCtsjnDj/7y2aSvvWWW8K0Va/VL7r8vMgJw7SVithhjkMdlhAGrhcE3SPwjN0uE2HGwNPHrU2X9U/rGjwQSm555ztveeetURS7nks5S9KEUjdOErfjTJGmqeu6BGDv3r2PP/749PT0v/2938uRHHRzKaPQM6X3d/RDP1TM6Nlnn7333ntrtdrf/d3fXX755br3uj6GDJGnxJZe8PHjx+++++69e/fiUENUKeUJNOmmtDr8FUK4rssYC8MwSRIVGbNFcVwul8MwjOMYjWGYD56o2Wg0kiTBtdZ8Pi+lDMMQawgAUgKjQAgpj06943f/cOvVNxY9woIcc1KHUUpCxiQjkhEhZATQ8pnIudRlnLiCMNKAuOR4TQ6+4xMAyakDdE62mvMr0+XxFTdKoO6kUkJcYcUKVASDEiu70nGl43Kfc5dRV5d00M0MbOrg10x/LIOrredDiATJKQvD0HUczjkQIoUEAMooIWRubm7P7t1vvubN1Wr1tddeu/322ymlSZL88pe/nJubu/qaq8fGx6GbUdmCBdRpM0Zddcaj7vHZv3//vffeu7CwoHhVL8GX+Ukjnjxy5Mhdd9310ksvYc5GNH0JSGqu+2maDg4Ojo2NjY6OhmG4uLg4MzODnpOKVyVJMjExcf311ydJEobhsWPH9u/fTzRTfrFYbDQaqMPV63Xl6rQ+uihLRcpJujz3xn9/6Cu3l8obd16dC1PKRT4KwU0cSamQFACImxJCRCRiSRICESMu812XEoeK1AXaYNITXAoogDs8OlkGtw4xFyxkSSqhTQTzvFzi8ZRRIIwy1/WAMiLPDD9dltl92+slZDEzFSgQKcFh/B+/+uDpufk3X/uWy6+4Ynh4GM+rlRRGRkaKxdLjjz/OOS+Uiu0wLBQKaRSuVleTJOGMK5lgyB+dI0h9VmjowtDRx7HH9+/f/4lPfKLdbn/5y1++4oorCCGZmNAxawTMXAjx8ssv/9mf/dnRo0eRnRhSUp3dgNoSfioUChMTE8PDw57nVavV48ePA0C5XL744osXFxdPnjyJxi0pJXbQnj17CCGc84GBgSAI1GhGLlgsFlutFp7yjezwTE/h2cASHEZaq6e//cW/HXvPHzevvjVfywWFRLZpkaVFGuZJNEzEIMQONFzScknqkDjh0su7kYilSzzurnngAwNGqeuP0NLzyZrDQZK4JDhNIeKJEGTa2djmDSd1CAFXBlyeEf3GThMdMQbm7H7WCWHISiEFmkAHK5XdTzzx2GM/SNL0ggsuuPLKK9/1rndtv+jCAwcOJElcLJUYpbuu2BUEQZomhJD5+dMEAA8gNU4/0MvVX57hWJmDgBCyb9++z33uc61W6wtf+MJll12m+Iqhh/X6qzJPkuTEiRN33333kSNHlCKlw1xXkhAZlUplaGioXC63Wq2FhQXsccdxCCGLi4uEkFKptHPnzvn5+dnZWUzOGHMcB/lTGIaITlUEjpMgCIQQ7XZbSXnVI0KkjDuJlJwT2mzPfOdrOTqyeM0722nOYY2KK4uMBlJUQE5Rd0DEgWwk0HDoai2u8QZpklAmsJaTa2kSMEd4fF+jOuGU84S8Ep9mQhDZpkQ4jfZw7F7lX3wx2ZJv+9sHtm2RDoCUnGMbGWM4xjLFQuZfvTNtVKn4KQCI9L1/8Pvv+t3fmZs5tefJ3f/88MMPfukf8kFu24U71tbWrr766tPzp0ul0rZt27BL16prI8PDhJBjx49f+qZLbbQY5Z4BlkKDrg/h89zc3H333Xfo0KH777//sssu0/WqTMD2+gQAy8vLn/70pw8cOKDmbkoZx1RqsTkIguHh4SAIAKDdbs/PzzuOk8vlHMdxHKfdblNKC4VCs9nETXDDw8MTExOHDh3Cv9Ax59odLTs2C9/3AaDZbOr0o5Qgg6CUpEImjLntZvyd++kIy5131VY3Fi4fko5PCgXq5AVzCQtlU0KDSj8maVmkS7SdE6ydRAEHCSHIlDvxMZi7Baaf4zXCEz9NWMpCJx4V7t74YEE6Y3LIlUGUJowDkYIxagxaAzq95J3BFDI1E7l+kjs59vrRp3/60+eee+7YsWPlysCHb/yj337HjYQQkYq1tdpatZrE8al8fmRkhHNeKpWOHTvWarUGBwf1jrV5pxqoYF/SpD+HYfj5z3/++eef//SnP33dddfZUl+Pf6b2WVPCdrt9//33P/XUU7oqLTq3MKJY9DxvYGAAz8pqtVorKyue5+VyuVwu5/t+FEWrq6urq6vValUIMTg4uGnTplwuV6/XFxcXOec7duxIkqRarbbbbQBQqFW1MmS3Ai7nHKUkSkYCQLCPZEg4F9EC+a9fH/73ldnNmwuteNB18jxIBaOUp+BHELDEFVy4MmrJhCbSY0zEKUvTmKRUiEpEz/dGZ7wwSdoFQWOSFONkAoZ/f+jWubW5gnDr7ap00yRNQtZ2hcc5M0ZCJmlUowzM9YLXelaEMEI8x/3//vEfH/rWt7dt2/of7rzzure/fXxiIknTVKTj42M/feqnuSBglO7es+ejH/3owMBArVbbuHHjzp07p6am9E2FRjUMrLO/+qu/sisthGi1Wv/wD//w1a9+9e677zbcFozImfnqcYQQDz300Je+9CUFJt0rxnGcgYGB8fHxgYEBIQRaHwqFwuDg4ODgoO/7yLROnDixsrLSaDSwuGazubi46Pv+0NBQLpeL43hxcTFJEillFEWUUs654zjValVNHuEMZ1oHN2MsSRI8KV/NPc80kwoJNHWov7jYWJhZueiSHPMTJxE04ZRGLKzLaos1JEQBobW0nWO0IcMBwavQdqmsJW1CyMV8FCj9EbzqszCGeAByd5bedRVs3+JMXOxu9Sibqb5RlF7F2UBAejxgjFFKdYOoPkSNhuBvph3L+NvJCm03slwqjY6OtFvtJ5/c/d/+2788+eSTAwMD05umByqVC7Zf0Go29+3bt+uqK7dfcAEAtNqtn/3sZ4sLi5u3bMbRCN3MSS9CvVk/592ASxzH3/3ud++7774PfOADH/vYx3ztVCR9iKj4WW04Ew4cOPCXf/mXy8vL+vhzHKdSqQwPDyO/RTz5vj8wMDA0NFQqlZIkWVpaOnny5OzsbK1Wk52VQQULIcTq6mq9Xs/lcoODg5zzMAxrtRoiBrGytraGahbWSqEHOrYJzjnCETSVudMExqgHKUkD5i2teEDYju0eBUmikIQxbdahtgbVWLYD5hQkC0XbEyLHnDXZZESuiVaOskleOCWW18hyg0eciJ3yvHd714y6gy7k2rQJrXQgP+xEUHA3lL2c5+YopXh0QK/u1Qkpuy3vtqw0BzkmBDk9venKXbsuvfRNvuf96lcv7du3b9u2bbuu3HXijRPf/va3x8bGHMe55eZbKCFJmjzzs6ejKMrn81u3bnNd1zjyU6+M/rfLu0Gxnz179tx3332XXHLJPffc4/u+zZP0Bqhnu0mI0QceeODkyZN6bdI03bx5c7FYPH369Orqquu6lUoFF15Q5C0uLqLNCSW6moSigNOXqxBbY2Njk5OTuVyuWCy2223ECpqySA+9BF86joMzR9d1LdEDCQkD4sgECAndPd93L7xM7thCqRCMs5RK6kQpT0nQErLAvBLkUhBCRIHD23GT+tSNZaNdE17tQpJ7vSVqbtyiS6dby2VvKCDcTYOg6IyJ4Xo0SwMnYLnOFDVDM+mvcqjm6MIRugMh66fNcMd58oknvvfd77768ivVtbXLLr/sY3/6pzfccIMEcF23XC7Pz8/XajVMwii79tprB4eGpBBJmkBnvbgLr1lzBaIGtML7vn37/uRP/qRYLH7ta1+bmJgw6meIcyNfQ+oLIR5//PGPfOQjOFNT9vEkSS644AJcWsnn80EQEEIWFxcXFxdrtZqayun+FLqzlyoL32M7C4XCxo0bK5VKrVZDS9Xa2prjOPV6HQ/YBG2sA7IFSgkhSZI0Go0wDD3PM+YTqgcJZRxSsmnT4L/7eLqhXHBh3K+Uib/qVAO5MCW5R+ogV1JRT8Rq5IaLpA40bEBtJDdZJ6daXivxQnD4Zrn5t/PXb2BDJag44FFJiICwHo2UJ4v+gEM8QgguxtkYslUo/IrOj8aozsYfAZDgud7/c++9J46/ceNN7/it695WW1srFkuTk5MSJOccJFRXV7/3ve+tLC39h//0cdd1f/Lk7oXTp8enpq6+6irX93So2AySKEc/oxInT5783Oc+lyTJZz/72fHxcb0xNnMGiw3og15KOT8//8ADD6ie0qGQpim6sqB6hCwKPRqUoo056/xJvVGMV9EgSZLZ2dlmszk2NhYEQaPRKBQKaZoODAzkcrnl5WU8DB1FJADglB4JiViPokgJfcNVnxLJJRPHTq3tfaby27fElERRGLqSCjLMJjwSpzKOIsq5x3iuLGEuWpn2gwOympN0UcQNkhZS8IEMBOURZ6QoBgow4Ds5KWKSykKB5p0CB2ed/XdYljH5AE36GHMRW8c35Ml6LwGRRKZJ8t73vveb3/jGG8eOL5w+/e1vfzufzz/44IObt56/tLT0L9/7XqlYKhQKUbuNOVx8ySW/ePHFN44fv+yyy7zA748BVZMuD9LV1dUHH3zwxRdf/Pu///urrrpKh5QhJvQGWBKEKAA99thjr776KmhWNSQY57xer8/PzyuKhmE4MTExNTUVhuHKykqSJGhZUIszSZIoHUtxPtzJpIAbhqGaM5bLZdd1Eaye501OTlar1ZWVFUppEASILcdx8BrEKIpc111YWBBC4GE9GBR2pRBtxrlI0+d+Fl10aTw5UiUtcCRNmZRFQtuUBAJcKVIQJKTEpR6RlFA6nPDDTIyF7JTbKFCXxC2eQskb9WKHk9BjpVSk3OUu9RjlkgIQAuTMye6GfNERY2tdOtoMLrCeFoAAoYw9uXv37j17RodHVldX7/ijP37iiSd+snvPedu3HT169Nprf+vI4cPXvPnNRw8fRu/kPXt2L5xeuGLXLrT/GfofZMlBUOYGIUQYhl/5ylcefvjhe+65Bw8z1tUxo65Gpe1PhJCFhYVvfetbcRzr55NgtVChabVaruvidXh41eXMzEwQBEEQMMY8z0MEpGmKdgfaOeNV3XeFs9d2u431bzQa9XodrVnomFUul9fW1nDSFATB+Pj43NxcsVjM5XI4/0LbWJqmeCLc7OwsFoSQVQuUAJJQEbtubnGh9vPd+ZHfSahLYsd3vITyNtCE0DqRTCQ5RqJUlom/mtRHfL+ZRpfwgZfk7E5ZOhWHsbMyG+73qByhG7ksOtzjIDljlFCA9R1IBDqyvhtJmVSwOZkOOx1n6y8ZpZQePXr0Qx/60K23vvM//8V//v33/6EgsLS6AlJGUTQ5MTk8POw4DuvcS+g4zk0333z48OETb7xx3tbzlX7SBVlrKW/dQCqE+M53vvNP//RPt9122wc/+EHDmctoRqZwtHP/0Y9+NDMzgxZIpdBwzhE0juO4rouih3OOu9g8z1OeDrgOiH4vajZHO6fXI0Zx9oe/lUplbGwMZevs7Cy6npZKpeHh4Xq93mq1cB5QKBSCIPB9HwGKAELYAUC1WkUOp/iiGgwgJI8ACER7fzG2663eZNGJmCdZLOMlp9EWjTXZIml9iCQloFSwmJOBxAGX0lhsL5QWkqXtwbAj/GZcm48P+TnuOkXmOoSu8xFCzuzDsVVvo58zB7kNO9AE1vqzkACSEsIoC+MolWJubm5peZk5HAhhlP74x4/X1mqvHToUhuG/3f57nLGB8sDzzz9fKpXGxseV5m4D1yhune0//fTTDzzwwI4dO+666y7lc9O/3oYsN9qQpumjjz5KCMnn8wpVuN6iWAXnHGGBbAMtBWcIibOSziq4XpBuQVD1aTabrVYL0Tk6OoqMCs0QQRDEcYzw8n2/VCphVRHECDjO+fj4+JYtW1577TU98/WBDkAkCBpHjEK9KX75i9pwJWUidvNSxJGsx6IZMlEgjKSMS1knkBcsTljF8xZjEYRixM0nEd3uDI27m4bo1kFv3BOeTCWlnOK94kq36t3nuv6aiSRj5IOFUfScAUIefvifH/vhD48cOfKnd3x0ZWXlgx/+91LIjRunT5446TAu0rQ8MOA6LkgYHBoKoyiXy0nNlVeXVHatAHWsw4cP33///a1W67Of/ezExATp3FyQWVGb+2WK9vn5+f3795dKJfQgUOtfusqMAaVhPp8fGBhQfdERQOt5Kks9vuSdNTV8ryqcpinnPIoiIUSlUqlWqyMjI7i2iBPpXC6HaRUjxFSYJ2Ns69atCwsL1WoVB4BqaSoEkUApoRIYpYv79u246ooSd8GVhEccvJiWVtKWy6jgciFt84TH0ucsCIQngbZbackpuLKQ1F0KXjHweUyIw+MkkZw4lFFKpMgWApmdr3qpC/0asbNJtj5gYHx8/MTQ0PDw8IUXXlgpl4vF4q5rrpYgV1dXR0dHPdcdHBwslkt+EFBGT83Ojo6MTExOIsfRKa7LXF0WAwCvVqtf+MIX9u3bd9999+3cuZNovisGSzAkYKbUV/nOzs6Ojo6iHUHBCAmJz0g5xbHwV+dYlNJ2u728vIw+CyjjRkdHkdO02+04jhX4EARo1HBdF+cEvu/XajXchIn1dF1X+QDKzpq0ztsrlcr555//0ksvmQqKaqYQABAvzIWvvdoulV+LkpAKErU3OnwCKgNEFgEkcRb4EiVBPsfmmvMRjYoOrzfSCSc/7E8WYJRGrgAgjoMTUiFSQggQChpW7EFr4Ez/ajAnA3A67aWEdjv8+Mc/fujmmz3Pe+wHj720d2+j0SgNVnZddeXy8tL5559/7NixQqEwNjYGAGma3nLrLQdefVUKwRhLhcCRadTKfsO/+c1vPvHEE+9///tvvvlm2n2Ah94AYs0KbWam/0UC+76P+goCCBGmwIQgQ+RhHD0H2bGGO46DypCUEr38MAlyJsWuJicnca2w2Wwigj3PUwZS1XjGmNqro48iLItzPjU1NTc3t7S0pKqhdDtGKSMUAEQS1fYfLl14uSBJkTmzND2URCdla1PibJcBEL5M4QS0yPLyVirqxKnHYpwPeDDiOUMkyTWF9DwqE6CMpKlMZUoJw2oaqDIGrU0Ue2xDllRRv5QQytkvfvGLv/rMZ/6vT3zi6WeebjeapxcWpqenAYBRVqvVamtrp0+fjpJkcmKCc16rrr1x/I32aHt0dJRxpivvRsX0v/TQoUM7duy48847cZpNug2DekUNRmW0xHiPM/lisVgsFsvlcqlUwgf1jHZR13U9z3NdF1eCZSfonaL6Ua0zIsIQB6qqeBUP2q7U8rYuUvXM1QRTPagmBEGAZuFMThwncRiF7bi1cnKWrVR3xslUyMI4jtsyHxVp5B5LWq8ka69GjTfCJo14GrlBFCy26EyLsXY5F+dJKsM4jsIkkSJM4yiJkjQRQgohke56ibpuoI9tm0UZnxSBu1IhL2Hsnx96aPv27W+77jog5E//4503vuPGZ59+WkoZ5HM/eeqp04sLhw6/duDAq6lI0zTds2dPuVw+duzY3Nyc4W9sMBed9fA77rjD87zh4WGD6/TCow4m1Tx7xORyuZGRkUKhoCSgUuEVLfXiFL2RxyATQgcbtfcBOiZ4fC86bBk6Zk9lIyCdVSDU8PSmSS3YJMFKDg0NFQqFVqulIAsAKH+FELlcrlgYGJ2YAJqSsF5PYhkm3M35lHlMFjlpCDIs85LSJG4sUZmXdJGEcUpjj7ejlEQRo0RKQqRshwkICUC5EJRIiu+72ZDND8DiSWCJDls8AQABIABpmh4+cuT973tfoZD/1D2f2rJ5y4EDB2ZnTgkh3vSmN+3atQu7cd27KRVSyrm5uVarpSbL1DroxS6XX3TRRdDxhQJrmBoSUG+PGg02Q8Yk6O6i8KTHN6StSoXWUWyAlBLXVo0qoTMCui0onhQEAeZTq9XiOMbFGQQlaFsR9ZYbrFHlj0aKsbGx48ePKxHseZ7neaVSaf2XOaXhCd6KA5eN+NyLi/tkc4CJ6VxQE/FKRFPJisJZAicA1kgbMfUaqThBVjf7G/Mp8bmbClFbq6VEcMZBEko4Z4xzoJQDdF2eozffpo4uRmzBZ3AUQggQwjgbGxt76qc/ffvb375l0+ZX9u179NFH77zzTuwo9A05Y3fk/G1ve9v3v/993/ddz7U5JfQIXGpKhiG8MxmV3cLMgMRAAae/NEhrZBWGYRiGAIB2JmQ/+t4yfFhYWECnBuwLXEjGOePi4iJ60KIihZq70TqDS+nPaKdhjA0NDZ08eRLnB9u3b5+YmBCd4LoudRyZY45IWAwBFxskmUvEWI7HcRoKuZTShLs1KcbSUjuNF1i7mQZtIn+8fCps+bduKeVovg1hQ0boY8KMAAAdcElEQVScUM91iaQgqcM5YwEAkes3SICUXfqA3e0GgQ1ZaXMBWPf5Fu95z3vu/exnP/zhD+fz+VOzs9svuOD6G24gmkn2zOAHCIJg3YECzqikNqSMocuVC6WBJ1tU9RKImV91S5rRPFWW6N7iDACUUtS3kIOiFoXRlEELUYW2eyFEHMeVSgVJPjs7SwjBJEII3/dxGmiAW8e0DjvU0lTawcFBdOmZmppyXReXlTArh5ACI4OOuxqLNRYC5y3ZzCclJ4HTnAWRF8UkIWkjjAIelIlbbdeaVHCAJxszQILbpi8dzFMBpJIvce5QwoQQUgBWSmdXNoB6dXgmqgyZgG+SOL7++utLxeIPf/CDarX6u+9+99uuu67RauJR24pAikYHDx7EpX21C8EWZXr3rpMyU9exeZ2uoBlfjdGv/ipdW6+uEALlizJ76gF1LFyxwS0SqgFozER/GOSFCrhzc3P4i1oXThvRgK58rVQmUpsEKEmnLGS6/cL3/fHx8bGxMQQxohz9B6nLhcPTNMnJtFlP21HzRulvzzkOwFpNknZ6UVKZqObCyF+LHBJ5rizkWn4+yUNS+fH80t+/+Nyrs/MO93OO7zs+o9R1Xd93cYskgOjwDhMlNjmNv72GUBcpKanXas8+/cz8qbnf+T/+z6d/8tTvvef27z70zxhBkWY9B4DF5aVmqzW9eVOxXBLdl53otNMpLqXsOjVZ9hCCOryMsZI5hno1SdVGZxV6wGkdLksDQJIkqC2pplJK8/k8rjQjgDzPazQaCwsLvu+3Wi10CkUQzM7OTk5OIiwUqnQWpdcWupm06JwnoJak1BtKKWdUSNKKpMM5pCk0KGUOS+Hi4aFis/Fyw1luNFbayTKhbeaJJL1ydHTHZHnKyZ1u1E+ureyc3HjxhslBz+MOp5QIKSkhhGabEmz02P2WKWQMOhJCJAClhFP2yCOPPPzww5s2bfrUX3x60+bNH/rIh2+66SY7KxznN9xww6FDh3AKRbSzYXpVCdNyfRBkCjvZLTvPsak68TIRpmipE5UQks/nMQKuK4dhqHRJnASg4yEq6YitXC5XrVanpqbQAwLJj+uPy8vLY2Njaps1dINeZ8OGtqtDGTrWf+iY+zmjAKzelg6kFCh3WDuB5VpzJJfbmfcuzBdfqS6/dXTj3oXTrzWWt3nBB7desqHgRGGyY2RCghwcKOUYpy6jeK29kEClJP3kna2rZI5wvVcNykopgUCaSoc7+/fvv/29t//Hj3/83e95z11//ue7rtxFKEmT1ChLSokDdevWrfhJaHu/+mCA4BZ7Q5br9bZ5rzE4DHyoN8YCn3GzjwE4I5ViDKomykSJ+aDXw+rqqsqKMdZqtcrlMh5WQzteMbVarV6v5/N5oZ0vYnSQXqguEA0ZRDqTCUoppxADW6lFrCkGKiWHAklJqymqtXBoMKgU+Y2FScb868fG20mYy7Mg5yQE8vmyTCWT4BEmGZWcE0kpLqYTTqRpJc9UmzL7XKeLkYkuJc4cvyzlyMhosVgsFgqu49TW1gBIsVQ0RIq+sqcYFe3eUZzJdKQuCvvH7tMk4ysGNYfSuYKqpdSkoc0yVWTSfSiXslyomWCr1VJ3nYVhiKZXFJTI51zXrdVqigsqVmRwKfuv6kTFq5R0oJQCAUoYCJKkMmnFRYcXXLfA/TSlrQg28AA4Awk5SnI04IQA8AC4TIHydTMbJZSmABwQWXittyHCDDD151iZ4DOZccd9ggDZ/eSTR48cOX702F/c8+kwCv/gfe/7dx/5sA5HHQOyYz5UKoGqp8FQzgALLCYEGmYN6BjywmZmOmLQwmac7aGPIZk1d1V/VSa6pq9mi0LzyCMd/0HkZ67rtlotfO84DrodK59j5fesQ0rHrq6B6r2hcy9BGaOyxJOUBERAEksXIO8DJbEjfUoEA0IZw3FEKKEgUppyyikQCmT9iAhCQAoASghIKeDczq41yGQ8G3U2wKdOX/Y895WX9x07enRqampkdGRwcHDHBRfoxRtpTc7XXXrmQ8bBa5l10gszBpbxVdFPaqu8dtkYDE1Q53BCC+rkPuggQ0oZhqHRYHz2PA9N5GrBu16vF4tFgyephkiN84M2LVKYU6JZPTNCCBGMCM7Bd6Dg0ILDy7nAoYQDkZJoppUzMl3J38wBaQ8we8BnvpeanNLzBIu3Yf5xktzx0Y9+7GMfy5eKfi6H67ZJkkghpKYh2Alt8hloVn1L9UNB1GddWhtdD1nBHjegMQbZ2baVmdaANdW2FknNLoAvXddV24/q9Xq1WkVFHuOo1UbsKTS0SinxGYEF1lg0GKQaDwbDRxmquUFTLl0HOJXcJ07BczwmaRoRYGkcR2HquqRj1+1ieAaD1CtvmP36dHsvNpZJCDMmECHE6NgYAAgQEmQq0qSdEEIodAkcyBoDvWpiR+s6H8tIoPK1h4j+KZPn2ScT6dVV3aE6XXWx3jDDyMQYQzAxxuI4Rv8ILAu1LgURzjnuhwYASin6QYCmleMnw5YG2hCUlvjrYtWQeh4QBoSKQiA9FzgDIMILcq7jCQFCIN8SQhD97j69K/SR3Ist2SQ0uu6sNM5kDRKkAMkIxXXpdZssyY4MPTgf9IAyNi3jyhP7WU+pd7ddgIqsz8J0RUpVGq1TpGNhQlVMaLsI8UHJQSwXz05eN8Hh4TsdxUutVZPOxhs9f9FZOrUBpDdBxxnV3KBt2vs+L7qB43iB7/q+E+R4EHiFYs5zPRCSIKgAOdYZDzCjOD1nacmHszIMvdpgQcooSAeiBAAJQgpKKKzvscjO1mAKdhH2+zPA0lmI+pwp4I1gA1yRQaelXSGqeX4iyNR6s67g4xKV2uarjA7Qsd1RSuM4xj1eoMFCX2HEtLh0bbAKA0zqWYdyZsOFJIQwzqjrUN93XZd6DvNcB0SaxCHnXMg0SfEco/U8VBF2b+tVUn/taLa8A0utNmCkt1THFupSQIhcv0jGLNQONu6NPjQKyhCFvf72KtX+Kjvu5KSjFOu9kyQJUh23TqhUOqNCorqui+cySClxD6oSlGpdj1KKO72UaVTpZFiQ6A4GYYyxKLvFtyKSkqEdAuBVZMoMxThHjovmOgFAAQSyJDXdyxzABrh18OlV7cW9jMr3emkn7/8mk2Xq+WeWqD9nXDauPp87fu2mInnU0Kfath/EVrPZJJ2jE4rFouu6a2traPlEYwGyK/QENEirW19xY6CSoXoH2R5/GjJMVdrWKqQVtJZKQoAQCSABBD5ImQIwZagnhEHHVaEXGQwE6PXpQ2Cjq8/9pcHS7GhG23tF6NUQ0PDAjQ+94KInswe0nkqvtKK0bvLGr0mSLCwsYOT5+XmcFlFK0WEGAIrFIq4i4+0E6GgmhEBNHBkYLkuTjmOgXhB6/iu9HmdzOvLUANAbAh2mJbSJt9Eo/SV0Twg6MdczIWTdhqcPcT1AFrZ6SbRM0QbdSFJvdDlrk1V/MEhsiznVXUaedlq99K6bKexn+++5RAAAzrk6iNFgEhgBvXxkR2giJeI4VtaptbU13dSLPA93/7mui3sD0zTN5/PKXQJ5Ia4YonO9vvCsEw+6ASEtHmYnhCwS6jSDM7gRUmJyQNLoORsY0lL1VGL0InTqGpTOfND/ZjKqXvKuT+b9X64Dy6iuHWQP/pT51UgoO9Zw9VJxAkJIPp9HPyec6AEAavEqIXTsYaLji6zcYFT+ysqgtvrghgv0byHa8h8CDuWsYVgHjZwGmOxeznTMlZpFlxAi5ZklNn0Go2NLL1rPhHZfVtNLbOkcqxeeMuOf9WWf90ar7UJV75kGUmPQ2H91zgzd49UeuzqMVPtRQtVqNdxU6HlerVYrFothGAZBgNM3ACgUChgTNzHjqX8ILwBA307S2avDGEOfWn3GoOaGCDj0pmo0GuhUQzrO7CoIbV1StT1ztIB2YqAORAOXsjv0iayCrVSdFWQ6sAxK91KqzgU3NseygzFO9DecZPEbo08z/+p5GWmxqSiPFBz1GqA+lKZps9ms1+sICLwPhxAyODi4urqKG6PxyH+c32E+SZKUSqUoitRhyQgIZGaKP6k6YBJ1jk0cx3izsG7XUNqSepDdSozKFlukpgVgWVlV0C1nBp6gB6oMiOiYsNUPHXm9aN/nkxGMUnoh1c7fjoDPGY5+srdQNyphg0ZFRsrp7gwqrf7G9300L0FHymBCJRPRgB7HMZ6sCgB4gAyirVwuY0LG2IYNG9DvtN1u61RUVUVRiFqasqKBxWiV5q6Sqzd6nq1Wq9FooGM0buXQ56oGkjJRBRa8dAavBnMmUaXGUPWczwUNvUIfZas/sDKL5sZQyExsCwW7MB2aAKAkl5KDOlcgHYMnni2ztramqkg6BneM6boumkBRRKLwQogoaZimab1eX1tbAwA0maKmhbVCr1QpJV6jLTUjiF4oaDDSR4LQlhCgs52JUnry5ElCiOd5zWazVCoZuNFzQ16biS2Dq9lEkp2hq2POpoJeYYPq+nMvsP5aGpX9XnZzIgym8i4tuQbdXd9LIKqvUptVgabtqlJVJmma4jVdjuPgoaNGN6ltEYqrKSMnIQSN8ro/jOwc8d1oNHDBuFqtSilXV1cV18FTmYm2cGTzj8yxKztr5KJziEgQBGr/PmRhxaZKJv6gGxn6e1sOSkss2mnt+hsSCbJklA0dGy42xUFDgt6ZnFiCzC6mD5iMeshulmN3sSpCiTzcloPrM4ZbOnq+i84pTUpViqIIV6DVthnMTZEZV4EwuTJYoJdtvV4vFApCM7eqB71uqiZgyUr8nZycxF1AeFSEnZtNEgO++l/R8b02OJPsVk56UdcozoZCf3D0KqJ/uX2ylcZmCgyG4Dtrq+y00NmtRbQJmpFEdHbKq93MtgxC9Z8x1mw28bwr5GobNmyo1+tCiCiK1FI0ABQKBTy9iHZ2SxNCcEcGyiw8WVnB0eYcoKnqOnUV+VXn4NFLmLPeYwZu7FLsQg3A6d2bSQVpcSC9rLOyGSOQbo1Zr3MfEdn/EyHkzGYKG0CyeyE2E2E2w4OOZgMWVlSponPvkpo5io6fgmJ4AIAeMnjqldJ1KKXKdhqGIU4bsW54kocQAk+5QRSSziEzaJIAgCRJ8DpMm/YqEC1Ize6l6q/8pHUfssxBb3e6EaEXsHTi2TqJHkFP1Ydv/VralV6iXZDsFmsqsuoWLrulrIHZcwyZNRba2rPRETjiEX+6Ki263apQE1cZou+esqAiA1P2fdxDQQhBvR7zRwVfRVb1UfM+9aCqp5iTsFasVVv0HlQOoqRH0LtePfcSApklZnYvdFO3F730Mf9rsR98o5ZZM9uijz3ZuWIyTVPXdbsOtzVaSyzRm8ktM78q3/vMpqrjskCbcKkN0OpNs9nEInAlUR3AVygU0OKgu98oHR85EzIt9PKTUiL3Um7NQnP80uumGKfOSMCadmFLlUMpFqQa2wtemc96z/fqbXvw6wPVZnVGb2fislduehzdSUl027rVA/YVHncgpVTTrJ73FaoG6601hp3RF/pXRUKw2KnCgRr6snNognI1RkO5sowrA7qSQY7jKCcItaNVRVCWCMWr8IBuvZ7KoqGzd1vP04Glk4F2Nkbj3mvafRySHnTG1ieaAbJfK/QHlvHGxpleqE59ZZfGPseBTTsXsEHnoEbQDpVJO+facQOkBk+yEUosrmanlVIqb2D1VUcqshDoyDs8E0atKmZOjlBK6sfJqUNHVMAKoEUeAUc6x0wiqtAYRrp3lUG3ZFQam8Kf1CS1apHaqUE7x8rRTjBQpQejK4yetOF1jlDrIw0N+mZGINqY1+uAA1Xts0JrNvatSqWoZtgsuw7R00sCDdo2xtVfHYXqq65oG9JNla1nq6ZvauEZF5IxNzzXn3OO+hbp8CG1FAgAvu8r6wba1jEaQhbv5yWEMMbQf8tgPzrUQJsYYjTFxgg5c5EH7RzlrZ95qYKBJwVx40F1oI450i0BDGzp3agjxpDUdkI1XCFLzhiVwQiBdk21TimD+qRj7gYN4qbPO+kWDUbx0M0qQcO7Qid2HF7lpcNIly9G43WGoZu+SEf7xkslKKU4mwMAfHAcR3mWqoKQUaed2wbU6Q+kM4MT2uqeTgkl40THPVAZ+vVpKemcj6rkMhpNMCgRqbM0AFDiUmlmSj9T7EGnhXrIRICBJ6M5elBdarBbowhCiDq7Vl8qMMCgI0l2azhG5blRUbAGhxGMl73+4pVJURQpR2RVIb2FStFRb9QszyA/6UwhSWdLBWOs0Wgo40Kq3VsBGmR18aQ4kCrO7j6pqfZ4KYa+0UN2tmgr0ZAkCR5GgvBCTobTUuSXaES1ZSXVDoNQlFZ9Zcx7bKLomDNaRDrzEtpxftQFsej4fejo15GkQKOKk71VoMxnoswNep3sZ6PS0CNITRcZHR3dtWvXM888Q7rv5FQdoYxVtHOsLXZEFEW6DUwhTK3eEELUcbc4zlSGCEpEGBKVEBKGIfI2jEM1x0MFKfVGVUxKiYhBlKgBoHxW0d8LFyWRPOoIe6XRY8UajQae7406IsJRbwv+Km6qgHuOJDSooACqyybFNfHqBqpdj6CPJejoVXqf9KJ4fzBkLOnYbej/slcj3/KWt8zMzBw9elRnV3p/qVErNUmKv2o+iLvjRccxRh/H6BGPCKDaUq5OG2U+UH7MuDitV0kf93p3V6vVWq1WqVQqlUq9Xm82m0IIvH9genp6cHDQcRw04er6u1KziHaWPbIxxaL0fsNzxVnn9KV8Pq94iewsfdrUlVlaiupDvMQFDyRXh3tBR9rKzszJhq/qMVWiTjKD4n1QBfoitMxSrTJb0ieo3BzHmZiYuPbaa4UQMzMzrVaLaAqy0jakNimTnW1b0KExAKSdw9Zkx0ai6zpqlquYvzpLTWFLsT3sC12iyW7lTxeXGNP3/dnZWTxrPp/PDw8Pl8vl4eHh8847r1gsItlwoqTESq9fNZD0v/gV7w4inbkVNhmb6Xkewg5XovB0E/TMVtXGouM4xpObEEDIR4W2YKD6WceEIel0Ehuj/ax40Ecp0YndRyBmiki9PDs+XgI4Ozu7b9++V155ZWZmpl6v4+iUnTmq3hi9qSjO0OtBcWkFQUQeog2FjvokND9gJV5xQRo6jJBoWrPsVq1UHCEE1r/RaORyObxfAxGwYcOGsbGxsbGxqakpZFcoyxSGFHT0XwNeutqughpyBpEMZoPNxAtpkWHjPS6400RN/nUaGzzPZoGZ9D2Xr5m8Bnsj+3wshQ/IAqyqWaZAgY5t3ff9QqGwZcuWfD6/uLhYrVbX1tZQpjSbTbwvDgGUdq4zUaqM4lhGF1Ntb4XsTID1JMp3WXGdtPvEG6kpVfogUdiiHWdDvPgJ65Cmqed5eI9rpVIZHBwMgkB52RPNXmXYGpRMVJ9IlpmUalZ76DBvnShqaoIgKxaLqv9x3wCOH9UtCjqZrCWTrOprr08Gkgy2Z7w/s6Rji+1eeOyTtZ4Qmzo0NFQsFicnJ/F4PvTwbLfbrVYLb4RraaHdbkedgDMy5b4iNFc7NYsRmuVCwU71jkqo9AbR8S2G7uGkA0u9V9uHPM+rVCoTExMbN24cHh4uFArFYjEIAmVcoH2DYlHqrwEsu8PVuNVxZtDbGNXEUmGN5vQiIlggO5ck+nuDMYExK7Q5kAE4QwgazzqTw4AWAZT3ai1J8RWlTKC/HsKu1WqFYdhut/GmLh12CEoFOEyF00Ci3eJENZsNElLtrKfdvpqqL4i2aEg0B0Cc/Q0MDExOTo6Pj09NTSGqUO6gYtQfUqpuBp5o96qi/pBJDqEZUKi2wCo7qiTmZoCvF7aIJWF0ZGS+1//aLBCyxGvXFnuFev2vHltPr0NK/6rLGtKZwerCSB9bOlNRa8xpmqIBDJGEgFN8TuEP75HDl6i9oQKHBvpUuy1McSkFbkUkpZkpakHnaAm8An1qamrz5s0bNmwYGBhAhR0NB6hg6bhROCOaZFQZUms9xyZh5ghXkXuRX8eTwTz6sBnoZjB2NfTIBl6NOAbfxQdzi72diz2GjMrZoDRq3KunjD6S2gxRmQkwoExERoW/SrAqVtdutxFq6BWI1/6ibE3TFK1Z+n4vxT/0FqmzQ1zX3bhx4/T09OTk5IYNGwqFQj6fV0YgvH/KFnC6jLMVqV5IskmeSelefWvkfO4hM1Umx8oEU5+2UFTeDXGG3wwE9KpKn6+ZDcaXhjagfyIdJqdYmv6rOJxSgPBZsbc4jhWkdDWu2WxGUYTIU3DETHBXD+ksQRJC8vn8li1bNm7cODIyUqlUUPCpdRsUr3ibC8mSdEZb9DeZxDBUnD7BUE4Mwhv9nClJetGu18vMv5nlGs/mLh0dW8RStvT0fbqDaKuSdmSjCP29jma9X6CH5NUzVAxJ7biPOwFlK4ZWq4W7xNQd5uq+ArS44n2ZuKkrn89zznFNBnkVBpzz2jwJNAD1GmaZI8pQW40H6BZt+ktVtJ4KusGUWR+DWAZbIVnSyWiIkbPBsc4YDA0a96pHf+GtQpIka2tr9kCxYQHd0DHqkIlIm+H1ylZ2n/6IGphicqI7gHY9onFPp7pdUWELD8bJBFMfRtKrP8/lay+ugx5KNgv4zYJd+V6N6h+BZ9Le7pGzYt9Opfo9EweZg8yoSX+23Ksm0I02pt3AC5ouqIKxIUdKiad8Q7c3nw4s0jG19+mQ/iNTldVrPGcOYCOy/tL+PXd4ZUo0/eGsEYyaEPsCAZ2vGuzUfuhfV3WzptDcgOx+AYtjnVU5sEu3ZYcCkBHH4DE6pvElCke1qKz4FtEs5rSzcE6776TtxekzdQmj53t9MiLYeCLakqtddP/QBzHGp3PH1jrH0nvEYCeZ7VFfDbXA+AodBVyvkx7NTpLZlX1Ep/43c4xmMkWiBejGIgCgzwKadg3PY+hYDRQt9bT6X5uvGIzErq0Bu0yRZzzonzLLPRe+ZQy2PmVlYqsP2rjRZjuG0eCzflUtoZorvtFUBU2D6n06InNQ9k9i9KDdTfobqrke4Loy7TgtGcDC5LTbLRaydCBbAbC7t1dt+zcnUw7+BsHmNJAFl3NEnv6cIWj68/Ne7w39QHZWc0FDmGJUHfWm6xKUXvxMLyizPplNsOupB32U6yBDuOAOfYSRsVeiv15ld1Sf7jXAAdYoPWu22O2qn+0Se8kB42UmbvrAqFcEvbguUdin9vbXs/4lnW2rpFuwQhZ0SLcOZ8TsNQDszA2q9JIpxkjVgUUI0ZUq9WwkzORGRt1UBOOv8aBqntlMO6Fdf9rtbnrW5mfWpBcrzfzap73r+oYOdsjSSzLZeOZoM77qws5urY2YTLQJbetfn04X3UcmnSOFMvmWlBJXOY3Z31kZlQpG0f1521lz65OQaM6MmXn+WjA9d2DplDU4wplotneKqhPp1ighC146J8vEllFXAz16LY0HG3b9O1fBIhPNmR0KFsfCN+i2SrqX/DLHulGZ3zhkSqheX1Xf4hsDWH0q06uUPsAy8GSzq8zcpJTmdO+sjdRj9q+u0f5e7cyUaJkw6tNfmU09K7FVhxqiBHfr2ws1Rv6QNaKMimWOt/7hrCAzPhnNN7r9rEwrk3XZ/dmnVplSK9uDtE9isKCQCan+wgisTteT9MJWr5e/VuSz8nnR2ZShr9jo8Xt1+m/wNXP8ZHZL/799YNQfWHZudhf1SgvdSDDgZR7doSLZ5ekwyhw3dhmZX89aafj1sfWbcSzoASxpmbsyM7cL+p/5+huHsw7gPl/7c6Ben84FGxkcC7JwA33hCRZ0ehWcKTt6qQhnZeagDQa7R/pwrF7CSzEA6MYc6aFh6Kn+V33V+0HvkEyCnsvXXhWw69Dna0+bHvQAh/6pT6o+X3sRVY9pj/JeGdrhXOB4jlW1o/Uiyf/Cv7oo/J/P2WhyH2L1/5oxK7R1gkxgZfKVXhA0OEqftHBumDjHYHNKsAZMryR6Sw0hrmf1v8/Xc+mBc+q1HqF/ccbXM9do6zEwqL/6g/qk/0I3LIw40AM0Z/1q1MRIon81iuvfbP3BfmnbF4wK9Gra/w5f7Y4y3tgdZbf03HPu8/X/B0OMErEJt/zIAAAAAElFTkSuQmCC" }, "Event": "nodeQueriesComplete", "TimeStamp": 1597143009, "NodeManufacturerName": "FIBARO System", "NodeProductName": "FGRGBWM441 RGBW Controller", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Multilevel Switch", "NodeGeneric": 17, "NodeSpecificString": "Multilevel Power Switch", "NodeSpecific": 1, "NodeManufacturerID": "0x010f", "NodeProductType": "0x0900", "NodeProductID": "0x1000", "NodeBaudRate": 40000, "NodeVersion": 4, "NodeGroups": 5, "NodeName": "Kitchen RGB Strip", "NodeLocation": "", "NodeDeviceTypeString": "Unknown Type (0x0000)", "NodeDeviceType": 0, "NodeRole": 0, "NodeRoleString": "Central Controller", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 3, 6, 8, 12, 13, 14, 18, 22, 26, 27, 28 ], "Neighbors": [ 1, 3, 6, 8, 12, 13, 14, 18, 22, 26, 27, 28 ]} +OpenZWave/1/node/7/instance/1/,{ "Instance": 1, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/51/,{ "Instance": 1, "CommandClassId": 51, "CommandClass": "COMMAND_CLASS_COLOR", "CommandClassVersion": 0, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/51/value/122470423/,{ "Label": "Color", "Value": "#FFFFFF00", "Units": "#RRGGBBWW", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 0, "Node": 7, "Genre": "User", "Help": "Color (in RGB format)", "ValueIDKey": 122470423, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/51/value/281475099181076/,{ "Label": "Color Index", "Value": { "List": [ { "Value": 0, "Label": "Off" }, { "Value": 1, "Label": "Cool White" }, { "Value": 2, "Label": "Warm White" }, { "Value": 3, "Label": "Red" }, { "Value": 4, "Label": "Lime" }, { "Value": 5, "Label": "Blue" }, { "Value": 6, "Label": "Yellow" }, { "Value": 7, "Label": "Cyan" }, { "Value": 8, "Label": "Magenta" }, { "Value": 9, "Label": "Silver" }, { "Value": 10, "Label": "Gray" }, { "Value": 11, "Label": "Maroon" }, { "Value": 12, "Label": "Olive" }, { "Value": 13, "Label": "Green" }, { "Value": 14, "Label": "Purple" }, { "Value": 15, "Label": "Teal" }, { "Value": 16, "Label": "Navy" }, { "Value": 17, "Label": "Custom" } ], "Selected": "Cool White", "Selected_id": 1 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 1, "Node": 7, "Genre": "User", "Help": "Preset Color", "ValueIDKey": 281475099181076, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "CommandClassVersion": 1, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/112/value/281475104374804/,{ "Label": "Enable/Disable ALL ON/OFF", "Value": { "List": [ { "Value": 0, "Label": "ALL ON disabled/ ALL OFF disabled" }, { "Value": 1, "Label": "ALL ON disabled/ ALL OFF active" }, { "Value": 2, "Label": "ALL ON active / ALL OFF disabled" }, { "Value": 255, "Label": "ALL ON active / ALL OFF active" } ], "Selected": "ALL ON active / ALL OFF active", "Selected_id": 255 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 7, "Genre": "Config", "Help": "Enable/Disable ALL ON/OFF", "ValueIDKey": 281475104374804, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143008} +OpenZWave/1/node/7/instance/1/commandclass/112/value/1688849987928084/,{ "Label": "Associations command class choice", "Value": { "List": [ { "Value": 0, "Label": "Normal (Dimmer) - BASIC SET/SWITCH_MULTILEVEL_START/STOP" }, { "Value": 1, "Label": "Normal (RGBW) - COLOR_CONTROL_SET/START/STOP_STATE_CHANGE" }, { "Value": 2, "Label": "Normal (RGBW) - COLOR_CONTROL_SET" }, { "Value": 3, "Label": "Brightness - BASIC SET/SWITCH_MULTILEVEL_START/STOP" }, { "Value": 4, "Label": "Rainbow (RGBW) - COLOR_CONTROL_SET" } ], "Selected": "Normal (Dimmer) - BASIC SET/SWITCH_MULTILEVEL_START/STOP", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 6, "Node": 7, "Genre": "Config", "Help": "Choose which command classes are sent to associated devices.", "ValueIDKey": 1688849987928084, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143008} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2251799941349396/,{ "Label": "Outputs state change mode", "Value": { "List": [ { "Value": 0, "Label": "MODE 1 - Constant Speed (speed is defined by parameters 9 and 10)" }, { "Value": 1, "Label": "MODE 2 - Constant Time (RGB/RBGW only. Time is defined by parameter 11)" } ], "Selected": "MODE 1 - Constant Speed (speed is defined by parameters 9 and 10)", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 8, "Node": 7, "Genre": "Config", "Help": "Choose the behaviour of transitions between different levels.", "ValueIDKey": 2251799941349396, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143008} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2533274918060049/,{ "Label": "Dimming step value (for MODE 1)", "Value": 1, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 99, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 9, "Node": 7, "Genre": "Config", "Help": "Size of the step for each change in level during the transition.", "ValueIDKey": 2533274918060049, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2814749894770710/,{ "Label": "Time between dimming steps (for MODE 1)", "Value": 10, "Units": "ms", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 60000, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 7, "Genre": "Config", "Help": "Time between each step in a transition between levels. Setting this to zero means an instantaneous change.", "ValueIDKey": 2814749894770710, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3096224871481361/,{ "Label": "Time to complete the entire transition (for MODE 2)", "Value": 67, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 11, "Node": 7, "Genre": "Config", "Help": "0 - immediate change; 1->63: 20ms->126ms (value*20ms); 65->127: 1s->63s (value-64)*1s; 129->191: 10s->630s (value-128)*10s; 193->255: 1min->63min (value-192)*1min. Default setting: 67 (3s)", "ValueIDKey": 3096224871481361, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3377699848192017/,{ "Label": "Maximum dimmer level", "Value": 255, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 3, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 12, "Node": 7, "Genre": "Config", "Help": "Maximum brightness level for the dimmer", "ValueIDKey": 3377699848192017, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3659174824902673/,{ "Label": "Minimum dimmer level", "Value": 2, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 2, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 13, "Node": 7, "Genre": "Config", "Help": "Minimum brightness level for the dimmer", "ValueIDKey": 3659174824902673, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3940649801613334/,{ "Label": "Inputs / Outputs configuration", "Value": 4369, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 14, "Node": 7, "Genre": "Config", "Help": "This is too complex to describe here, since this value is built up from 4-bits for each of the 4 channels. Refer to the table in the product manual. Default value is 4369 (1111 in hex).", "ValueIDKey": 3940649801613334, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/4222124778323988/,{ "Label": "Option double click", "Value": { "List": [ { "Value": 0, "Label": "Double click disabled" }, { "Value": 1, "Label": "Double click enabled" } ], "Selected": "Double click enabled", "Selected_id": 1 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 15, "Node": 7, "Genre": "Config", "Help": "Option double click (lighting set at 100%). 0 - Double click disabled, 1 - Double click enabled. Default setting 1", "ValueIDKey": 4222124778323988, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/4503599755034644/,{ "Label": "Saving state before power failure", "Value": { "List": [ { "Value": 0, "Label": "State NOT saved at power failure, all outputs are set to OFF upon power restore" }, { "Value": 1, "Label": "State saved at power failure, all outputs are set to previous state upon power restore" } ], "Selected": "State saved at power failure, all outputs are set to previous state upon power restore", "Selected_id": 1 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 16, "Node": 7, "Genre": "Config", "Help": "Saving state before power failure", "ValueIDKey": 4503599755034644, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/8444249428983828/,{ "Label": "Alarm", "Value": { "List": [ { "Value": 0, "Label": "INACTIVE - no response to alarm frames" }, { "Value": 1, "Label": "ALARM ON - the device turns on once alarm is detected (all channels set to 99%)" }, { "Value": 2, "Label": "ALARM OFF - the device turns off once alarm is detected (all channels set to 0%)" }, { "Value": 3, "Label": "ALARM PROGRAM - alarm sequence turns on (program selected in parameter 38)" } ], "Selected": "INACTIVE - no response to alarm frames", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 30, "Node": 7, "Genre": "Config", "Help": "Alarm of any type (general alarm, water flooding alarm, smoke alarm: CO, CO2, temperature alarm). Default setting 0 (Inactive)", "ValueIDKey": 8444249428983828, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/10696049242669073/,{ "Label": "Alarm sequence program", "Value": 10, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 10, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 38, "Node": 7, "Genre": "Config", "Help": "Program number selected from the 10 available.", "ValueIDKey": 10696049242669073, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/10977524219379734/,{ "Label": "Active PROGRAM alarm time", "Value": 600, "Units": "s", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 65534, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 39, "Node": 7, "Genre": "Config", "Help": "In ALARM PROGRAM mode (see parameter 30), this defines the time in seconds the program lasts (1s->65534s)", "ValueIDKey": 10977524219379734, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/11821949149511700/,{ "Label": "Command class reporting Outputs status change", "Value": { "List": [ { "Value": 0, "Label": "Reporting as a result of inputs and controllers actions (SWITCH MULTILEVEL)" }, { "Value": 1, "Label": "Reporting as a result inputs actions (SWITCH MULTILEVEL)" }, { "Value": 2, "Label": "Reporting as a result inputs actions (COLOUR_CONTROL)" } ], "Selected": "Reporting as a result of inputs and controllers actions (SWITCH MULTILEVEL)", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 42, "Node": 7, "Genre": "Config", "Help": "Specify which command class is used to report output status changes", "ValueIDKey": 11821949149511700, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12103424126222353/,{ "Label": "Reporting 0-10v analog inputs change threshold", "Value": 5, "Units": "*0.1V", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 100, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 43, "Node": 7, "Genre": "Config", "Help": "Parameter defines a value by which input voltage must change in order to be reported to the main controller. New value is calculated based on last reported value: Default setting: 5 (0.5V). Range: 1->100 - (0.1V->10V).", "ValueIDKey": 12103424126222353, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12384899102933014/,{ "Label": "Power load reporting frequency", "Value": 30, "Units": "s", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 65534, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 44, "Node": 7, "Genre": "Config", "Help": "Sent if last reported value differs from the current value. Reports will also be sent in case of polling. Default setting: 30 (30s). Range: 1->65534 (1s->65534s) - interval between reports. Zero means reports are only sent in the case of polling, or at turning OFF the device", "ValueIDKey": 12384899102933014, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/12666374079643665/,{ "Label": "Reporting changes in energy consumed by controlled devices", "Value": 10, "Units": "*0.01kWh", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 254, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 45, "Node": 7, "Genre": "Config", "Help": "Interval between energy consumption reports (in kWh). New reported energy consumption value is calculated based on last reported value. 1->254 (0.01kWh->2.54kWh). Zero means changes in consumed energy will not be reported, except in case of polling.", "ValueIDKey": 12666374079643665, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/19984723474120724/,{ "Label": "Response to BRIGHTNESS set to 0%", "Value": { "List": [ { "Value": 0, "Label": "Illumination colour set to white (all channels controlled together)" }, { "Value": 1, "Label": "Last set colour is memorized" } ], "Selected": "Last set colour is memorized", "Selected_id": 1 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 71, "Node": 7, "Genre": "Config", "Help": "Set whether to remember the previous RGB mix after the brightness has fallen to zero (black)", "ValueIDKey": 19984723474120724, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/20266198450831377/,{ "Label": "Starting predefined program", "Value": 1, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 10, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 72, "Node": 7, "Genre": "Config", "Help": "First predefined program to use when device is set to work in RGB/RGBW mode (parameter 14)", "ValueIDKey": 20266198450831377, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/112/value/20547673427542036/,{ "Label": "Triple Click Action", "Value": { "List": [ { "Value": 0, "Label": "NODE INFO control frame is sent" }, { "Value": 1, "Label": "Start favourite program" } ], "Selected": "NODE INFO control frame is sent", "Selected_id": 0 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 73, "Node": 7, "Genre": "Config", "Help": "Behaviour when an input is triple-clicked", "ValueIDKey": 20547673427542036, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597143009} +OpenZWave/1/node/7/instance/1/commandclass/38/,{ "Instance": 1, "CommandClassId": 38, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "CommandClassVersion": 0, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/value/122257425/,{ "Label": "Level", "Value": 99, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 0, "Node": 7, "Genre": "User", "Help": "The Current Level of the Device", "ValueIDKey": 122257425, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597142969} +OpenZWave/1/node/7/instance/1/commandclass/38/value/281475098968088/,{ "Label": "Bright", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 1, "Node": 7, "Genre": "User", "Help": "Increase the Brightness of the Device", "ValueIDKey": 281475098968088, "ReadOnly": false, "WriteOnly": true, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/value/562950075678744/,{ "Label": "Dim", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 2, "Node": 7, "Genre": "User", "Help": "Decrease the Brightness of the Device", "ValueIDKey": 562950075678744, "ReadOnly": false, "WriteOnly": true, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/value/844425060778000/,{ "Label": "Ignore Start Level", "Value": true, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 3, "Node": 7, "Genre": "System", "Help": "Ignore the Start Level of the Device when increasing/decreasing brightness", "ValueIDKey": 844425060778000, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/38/value/1125900037488657/,{ "Label": "Start Level", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 4, "Node": 7, "Genre": "System", "Help": "Start Level when Changing the Brightness of a Device", "ValueIDKey": 1125900037488657, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/39/,{ "Instance": 1, "CommandClassId": 39, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "CommandClassVersion": 1, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/39/value/130662420/,{ "Label": "Switch All", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Off Enabled" }, { "Value": 2, "Label": "On Enabled" }, { "Value": 255, "Label": "On and Off Enabled" } ], "Selected": "On and Off Enabled", "Selected_id": 255 }, "Units": "", "ValueSet": true, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "Index": 0, "Node": 7, "Genre": "System", "Help": "Switch All Devices On/Off", "ValueIDKey": 130662420, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1597142969} +OpenZWave/1/node/7/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "CommandClassVersion": 0, "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/114/value/131891219/,{ "Label": "Loaded Config Revision", "Value": 5, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 7, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 131891219, "ReadOnly": true, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/114/value/281475108601875/,{ "Label": "Config File Revision", "Value": 5, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 7, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475108601875, "ReadOnly": true, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/instance/1/commandclass/114/value/562950085312531/,{ "Label": "Latest Available Config File Revision", "Value": 5, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 7, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950085312531, "ReadOnly": true, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1597142799} +OpenZWave/1/node/7/association/1/,{ "Name": "Input 1", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1597142866} +OpenZWave/1/node/7/association/2/,{ "Name": "Input 2", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1597142866} +OpenZWave/1/node/7/association/3/,{ "Name": "Input 3", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1597142866} +OpenZWave/1/node/7/association/4/,{ "Name": "Input 4", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1597142866} +OpenZWave/1/node/7/association/5/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 1, "Members": [ "1.0" ], "TimeStamp": 1597142799} +OpenZWave/1/node/7/statistics/,{ "sendCount": 30, "sentFailed": 0, "retries": 0, "receivedPackets": 29, "receivedDupPackets": 1, "receivedUnsolicited": 0, "lastSentTimeStamp": 1597143009, "lastReceivedTimeStamp": 1597143009, "lastRequestRTT": 26, "averageRequestRTT": 25, "lastResponseRTT": 37, "averageResponseRTT": 37, "quality": 0, "extendedTXSupported": false, "txTime": 0, "hops": 0, "rssi_1": "", "rssi_2": "", "rssi_3": "", "rssi_4": "", "rssi_5": "", "route_1": 0, "route_2": 0, "route_3": 0, "route_4": 0, "ackChannel": 0, "lastTXChannel": 0, "routeScheme": "Idle", "routeUsed": "", "routeSpeed": "Auto", "routeTries": 0, "lastFailedLinkFrom": 0, "lastFailedLinkTo": 0} From 588fb283cc82be28d3fb28bb6a8896c42d1a7eee Mon Sep 17 00:00:00 2001 From: automaton82 Date: Sun, 16 Aug 2020 10:28:40 -0400 Subject: [PATCH 61/77] Fix the CONF_LOOP check to use the config (#38890) --- homeassistant/components/environment_canada/camera.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index c0565988c65..9ebaa37f678 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -52,18 +52,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): coordinates=(lat, lon), precip_type=config.get(CONF_PRECIP_TYPE) ) - add_devices([ECCamera(radar_object, config.get(CONF_NAME))], True) + add_devices( + [ECCamera(radar_object, config.get(CONF_NAME), config[CONF_LOOP])], True + ) class ECCamera(Camera): """Implementation of an Environment Canada radar camera.""" - def __init__(self, radar_object, camera_name): + def __init__(self, radar_object, camera_name, is_loop): """Initialize the camera.""" super().__init__() self.radar_object = radar_object self.camera_name = camera_name + self.is_loop = is_loop self.content_type = "image/gif" self.image = None self.timestamp = None @@ -90,7 +93,7 @@ class ECCamera(Camera): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Update radar image.""" - if CONF_LOOP: + if self.is_loop: self.image = self.radar_object.get_loop() else: self.image = self.radar_object.get_latest_frame() From 5fa14aae7d3e20ad11e188cb544afdcb1c8a2d80 Mon Sep 17 00:00:00 2001 From: Oncleben31 Date: Sat, 15 Aug 2020 11:54:42 +0200 Subject: [PATCH 62/77] Fix error in meteo_france for overseas France cities (#38895) --- homeassistant/components/meteo_france/__init__.py | 5 +++-- homeassistant/components/meteo_france/manifest.json | 12 +++++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 276aac188d9..e2049543ef3 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -4,6 +4,7 @@ from datetime import timedelta import logging from meteofrance.client import MeteoFranceClient +from meteofrance.helpers import is_valid_warning_department import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -131,7 +132,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool _LOGGER.debug( "Department corresponding to %s is %s", entry.title, department, ) - if department: + if is_valid_warning_department(department): if not hass.data[DOMAIN].get(department): coordinator_alert = DataUpdateCoordinator( hass, @@ -155,7 +156,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool ) else: _LOGGER.warning( - "Weather alert not available: The city %s is not in France or Andorre.", + "Weather alert not available: The city %s is not in metropolitan France or Andorre.", entry.title, ) diff --git a/homeassistant/components/meteo_france/manifest.json b/homeassistant/components/meteo_france/manifest.json index cd6f09246a6..97c9b589c48 100644 --- a/homeassistant/components/meteo_france/manifest.json +++ b/homeassistant/components/meteo_france/manifest.json @@ -3,6 +3,12 @@ "name": "Météo-France", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/meteo_france", - "requirements": ["meteofrance-api==0.1.0"], - "codeowners": ["@hacf-fr", "@oncleben31", "@Quentame"] -} + "requirements": [ + "meteofrance-api==0.1.1" + ], + "codeowners": [ + "@hacf-fr", + "@oncleben31", + "@Quentame" + ] +} \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 1b55cde4269..80785d00943 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -902,7 +902,7 @@ messagebird==1.2.0 meteoalertapi==0.1.6 # homeassistant.components.meteo_france -meteofrance-api==0.1.0 +meteofrance-api==0.1.1 # homeassistant.components.mfi mficlient==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5a0a2c11485..6f7f0b3f710 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -424,7 +424,7 @@ mbddns==0.1.2 mcstatus==2.3.0 # homeassistant.components.meteo_france -meteofrance-api==0.1.0 +meteofrance-api==0.1.1 # homeassistant.components.mfi mficlient==0.3.0 From 79924fcc7a07d7d72ebac88bb3adeb991ee7521c Mon Sep 17 00:00:00 2001 From: cgtobi Date: Sun, 16 Aug 2020 12:18:58 +0200 Subject: [PATCH 63/77] Fix Netatmo climate boost/heat event handling (#38923) * Fix boost event handling * Replace strings with vars --- homeassistant/components/netatmo/climate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 459f005695b..b3fc8b3f00a 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -262,8 +262,11 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): for room in home["rooms"]: if data["event_type"] == "set_point": if self._id == room["id"]: - if room["therm_setpoint_mode"] == "off": + if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: self._hvac_mode = HVAC_MODE_OFF + elif room["therm_setpoint_mode"] == STATE_NETATMO_MAX: + self._hvac_mode = HVAC_MODE_HEAT + self._target_temperature = DEFAULT_MAX_TEMP else: self._target_temperature = room["therm_setpoint_temperature"] self.async_write_ha_state() From 7fb879f19114577be48d0f1ce2cf3b40979bdf27 Mon Sep 17 00:00:00 2001 From: pbalogh77 Date: Mon, 17 Aug 2020 08:19:34 +0200 Subject: [PATCH 64/77] Fix HC3 compatibility further (#38931) * Update __init__.py Further fixes for HC3 compatibility. * Update homeassistant/components/fibaro/__init__.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- homeassistant/components/fibaro/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 4233953ca8c..c62dd2b2a8f 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -235,10 +235,10 @@ class FibaroController: scenes = self._client.scenes.list() self._scene_map = {} for device in scenes: - if "visible" in device and not device.visible: + if "name" not in device or "id" not in device: continue device.fibaro_controller = self - if device.roomID == 0: + if "roomID" not in device or device.roomID == 0: room_name = "Unknown" else: room_name = self._room_map[device.roomID].name @@ -250,6 +250,7 @@ class FibaroController: device.unique_id_str = f"{self.hub_serial}.scene.{device.id}" self._scene_map[device.id] = device self.fibaro_devices["scene"].append(device) + _LOGGER.debug("%s scene -> %s", device.ha_id, device) def _read_devices(self): """Read and process the device list.""" @@ -259,8 +260,10 @@ class FibaroController: last_climate_parent = None for device in devices: try: + if "name" not in device or "id" not in device: + continue device.fibaro_controller = self - if device.roomID == 0: + if "roomID" not in device or device.roomID == 0: room_name = "Unknown" else: room_name = self._room_map[device.roomID].name From 68c83ea629ccbed37782be067f2bb49558a203ce Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 17 Aug 2020 02:47:50 -0500 Subject: [PATCH 65/77] Accommodate systems with very large databases and slow disk/cpu (#38947) On startup we run an sqlite3 quick_check to verify the database integrity. In the majority of cases, the quick_check takes under 10 seconds. On systems with very large databases and very slow disk/cpu, this can take much longer so we freeze the timeout. --- homeassistant/components/recorder/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index d0c18256377..8798b213ec9 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -534,7 +534,15 @@ class Recorder(threading.Thread): if self.db_url != SQLITE_URL_PREFIX and self.db_url.startswith( SQLITE_URL_PREFIX ): - validate_or_move_away_sqlite_database(self.db_url) + with self.hass.timeout.freeze(DOMAIN): + # + # Here we run an sqlite3 quick_check. In the majority + # of cases, the quick_check takes under 10 seconds. + # + # On systems with very large databases and + # very slow disk or cpus, this can take a while. + # + validate_or_move_away_sqlite_database(self.db_url) if self.engine is not None: self.engine.dispose() From 3d66065fe6b6b52def8b535ca7c9847422b01072 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 17 Aug 2020 09:29:00 +0000 Subject: [PATCH 66/77] Bumped version to 0.114.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 908f849d3aa..6fedf47ff1a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 53e6c7f0c4e58d8a1cc79b2dd9d0a1cd48de4600 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 17 Aug 2020 15:52:54 -0500 Subject: [PATCH 67/77] Update zeroconf to fix ServiceBrowser leak on cancelation (#38933) --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index b93e47f8a65..883188b31cf 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.28.0"], + "requirements": ["zeroconf==0.28.1"], "dependencies": ["api"], "codeowners": ["@Kane610"], "quality_scale": "internal" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e709bf68aa4..f3b862341d4 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -28,7 +28,7 @@ sqlalchemy==1.3.18 voluptuous-serialize==2.4.0 voluptuous==0.11.7 yarl==1.4.2 -zeroconf==0.28.0 +zeroconf==0.28.1 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 80785d00943..88d24850e2e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2269,7 +2269,7 @@ youtube_dl==2020.07.28 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.28.0 +zeroconf==0.28.1 # homeassistant.components.zha zha-quirks==0.0.43 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6f7f0b3f710..79cff20d811 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1005,7 +1005,7 @@ xmltodict==0.12.0 yeelight==0.5.2 # homeassistant.components.zeroconf -zeroconf==0.28.0 +zeroconf==0.28.1 # homeassistant.components.zha zha-quirks==0.0.43 From f1f09f7156002ed7529de65a62daf30021e84cbd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 17 Aug 2020 02:46:30 -0500 Subject: [PATCH 68/77] Bump netdisco to 2.8.2 to accomodate new zeroconf exception (#38949) --- homeassistant/components/discovery/manifest.json | 2 +- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/discovery/manifest.json b/homeassistant/components/discovery/manifest.json index 232237484d1..5744d406dec 100644 --- a/homeassistant/components/discovery/manifest.json +++ b/homeassistant/components/discovery/manifest.json @@ -2,7 +2,7 @@ "domain": "discovery", "name": "Discovery", "documentation": "https://www.home-assistant.io/integrations/discovery", - "requirements": ["netdisco==2.8.1"], + "requirements": ["netdisco==2.8.2"], "after_dependencies": ["zeroconf"], "codeowners": [], "quality_scale": "internal" diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 85ae4725e93..ed20ae9ead6 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["defusedxml==0.6.0", "netdisco==2.8.1"], + "requirements": ["defusedxml==0.6.0", "netdisco==2.8.2"], "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f3b862341d4..58d3e3be883 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -16,7 +16,7 @@ hass-nabucasa==0.35.0 home-assistant-frontend==20200811.0 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 -netdisco==2.8.1 +netdisco==2.8.2 paho-mqtt==1.5.0 pip>=8.0.3 python-slugify==4.0.1 diff --git a/requirements_all.txt b/requirements_all.txt index 88d24850e2e..63dea043514 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -945,7 +945,7 @@ netdata==0.2.0 # homeassistant.components.discovery # homeassistant.components.ssdp -netdisco==2.8.1 +netdisco==2.8.2 # homeassistant.components.neurio_energy neurio==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 79cff20d811..fb348a8bb35 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -443,7 +443,7 @@ nessclient==0.9.15 # homeassistant.components.discovery # homeassistant.components.ssdp -netdisco==2.8.1 +netdisco==2.8.2 # homeassistant.components.nexia nexia==0.9.3 From 4f42165c8a89d1272ffac5f130f4ad42b5494ca0 Mon Sep 17 00:00:00 2001 From: lawtancool <26829131+lawtancool@users.noreply.github.com> Date: Wed, 19 Aug 2020 11:05:02 -0700 Subject: [PATCH 69/77] Fix Control4 light setup issues (#38952) * catch KeyError and UnboundLocalError in light setup * add check for no dimmer info from control4 * use existence of light_level data to enable dimming * check if light data is not in dimmer and non_dimmer * add item_variables logging * add await to item_variables call * add full item dump * remove full item list * change logging message * fix typo * reduce code inside try and add all entities at once * Apply suggestions from code review Co-authored-by: J. Nick Koston * format with black Co-authored-by: J. Nick Koston --- homeassistant/components/control4/light.py | 84 ++++++++++++++-------- 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/control4/light.py b/homeassistant/components/control4/light.py index d5a681eac09..36727b21ecf 100644 --- a/homeassistant/components/control4/light.py +++ b/homeassistant/components/control4/light.py @@ -73,40 +73,64 @@ async def async_setup_entry( await dimmer_coordinator.async_refresh() items_of_category = await get_items_of_category(hass, entry, CONTROL4_CATEGORY) + + entity_list = [] for item in items_of_category: - if item["type"] == CONTROL4_ENTITY_TYPE: - item_name = item["name"] - item_id = item["id"] - item_parent_id = item["parentId"] - item_is_dimmer = item["capabilities"]["dimmer"] + try: + if item["type"] == CONTROL4_ENTITY_TYPE: + item_name = item["name"] + item_id = item["id"] + item_parent_id = item["parentId"] - if item_is_dimmer: - item_coordinator = dimmer_coordinator + item_manufacturer = None + item_device_name = None + item_model = None + + for parent_item in items_of_category: + if parent_item["id"] == item_parent_id: + item_manufacturer = parent_item["manufacturer"] + item_device_name = parent_item["name"] + item_model = parent_item["model"] else: - item_coordinator = non_dimmer_coordinator - - for parent_item in items_of_category: - if parent_item["id"] == item_parent_id: - item_manufacturer = parent_item["manufacturer"] - item_device_name = parent_item["name"] - item_model = parent_item["model"] - async_add_entities( - [ - Control4Light( - entry_data, - entry, - item_coordinator, - item_name, - item_id, - item_device_name, - item_manufacturer, - item_model, - item_parent_id, - item_is_dimmer, - ) - ], - True, + continue + except KeyError: + _LOGGER.exception( + "Unknown device properties received from Control4: %s", item, ) + continue + + if item_id in dimmer_coordinator.data: + item_is_dimmer = True + item_coordinator = dimmer_coordinator + elif item_id in non_dimmer_coordinator.data: + item_is_dimmer = False + item_coordinator = non_dimmer_coordinator + else: + director = entry_data[CONF_DIRECTOR] + item_variables = await director.getItemVariables(item_id) + _LOGGER.warning( + "Couldn't get light state data for %s, skipping setup. Available variables from Control4: %s", + item_name, + item_variables, + ) + continue + + entity_list.append( + Control4Light( + entry_data, + entry, + item_coordinator, + item_name, + item_id, + item_device_name, + item_manufacturer, + item_model, + item_parent_id, + item_is_dimmer, + ) + ) + + async_add_entities(entity_list, True) class Control4Light(Control4Entity, LightEntity): From ecb7d7b992ede12ecfb077831b013b5dd873252d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 18 Aug 2020 23:43:38 +0200 Subject: [PATCH 70/77] Bump pychromecast to 7.2.1 (#39018) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 1187887e864..49ade045518 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==7.2.0"], + "requirements": ["pychromecast==7.2.1"], "after_dependencies": ["cloud","zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 63dea043514..ac9ae596d09 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1256,7 +1256,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==7.2.0 +pychromecast==7.2.1 # homeassistant.components.cmus pycmus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fb348a8bb35..f2f6e0a77ff 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -595,7 +595,7 @@ pyblackbird==0.5 pybotvac==0.0.17 # homeassistant.components.cast -pychromecast==7.2.0 +pychromecast==7.2.1 # homeassistant.components.coolmaster pycoolmasternet==0.0.4 From c8e2af80469f5f42d85896ae92d2f21afc84bee9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Aug 2020 03:27:14 -0500 Subject: [PATCH 71/77] Fix emulated hue on/off devices compatibility with alexa (#39063) --- homeassistant/components/emulated_hue/hue_api.py | 1 + tests/components/emulated_hue/test_hue_api.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index b84e64e6cc6..d979fcbc6e0 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -781,6 +781,7 @@ def entity_to_json(config, entity): retval["type"] = "On/Off light" retval["productname"] = "On/Off light" retval["modelid"] = "HASS321" + retval["state"].update({HUE_API_STATE_BRI: HUE_API_STATE_BRI_MAX}) return retval diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 510aa0ef8ee..fae391ce5ff 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -22,6 +22,7 @@ from homeassistant.components import ( from homeassistant.components.emulated_hue import Config, hue_api from homeassistant.components.emulated_hue.hue_api import ( HUE_API_STATE_BRI, + HUE_API_STATE_BRI_MAX, HUE_API_STATE_CT, HUE_API_STATE_HUE, HUE_API_STATE_ON, @@ -282,6 +283,12 @@ async def test_light_without_brightness_supported(hass_hue, hue_client): assert light_without_brightness_json["state"][HUE_API_STATE_ON] is True assert light_without_brightness_json["type"] == "On/Off light" + # BRI required for alexa compat + assert ( + light_without_brightness_json["state"][HUE_API_STATE_BRI] + == HUE_API_STATE_BRI_MAX + ) + async def test_light_without_brightness_can_be_turned_off(hass_hue, hue_client): """Test that light without brightness can be turned off.""" From fb1c3f7f1cfc4e48e0da016e6017d5e84bf8298d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Thu, 20 Aug 2020 10:26:49 +0200 Subject: [PATCH 72/77] Update met.no library (#39076) --- homeassistant/components/met/manifest.json | 2 +- homeassistant/components/norway_air/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/met/manifest.json b/homeassistant/components/met/manifest.json index a68a8223ba5..570e3df3bf0 100644 --- a/homeassistant/components/met/manifest.json +++ b/homeassistant/components/met/manifest.json @@ -3,6 +3,6 @@ "name": "Meteorologisk institutt (Met.no)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/met", - "requirements": ["pyMetno==0.7.0"], + "requirements": ["pyMetno==0.7.1"], "codeowners": ["@danielhiversen"] } diff --git a/homeassistant/components/norway_air/manifest.json b/homeassistant/components/norway_air/manifest.json index 2898ee6ff64..96ff39bb6dd 100644 --- a/homeassistant/components/norway_air/manifest.json +++ b/homeassistant/components/norway_air/manifest.json @@ -2,6 +2,6 @@ "domain": "norway_air", "name": "Om Luftkvalitet i Norge (Norway Air)", "documentation": "https://www.home-assistant.io/integrations/norway_air", - "requirements": ["pyMetno==0.7.0"], + "requirements": ["pyMetno==0.7.1"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index ac9ae596d09..05998cfa10d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1181,7 +1181,7 @@ pyHS100==0.3.5.1 # homeassistant.components.met # homeassistant.components.norway_air -pyMetno==0.7.0 +pyMetno==0.7.1 # homeassistant.components.rfxtrx pyRFXtrx==0.25 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f2f6e0a77ff..c4c9051c9bf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -559,7 +559,7 @@ pyHS100==0.3.5.1 # homeassistant.components.met # homeassistant.components.norway_air -pyMetno==0.7.0 +pyMetno==0.7.1 # homeassistant.components.rfxtrx pyRFXtrx==0.25 From 052df518d08aa99781ce6b0122f40cfa851a7a00 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 20 Aug 2020 08:30:04 +0000 Subject: [PATCH 73/77] Bumped version to 0.114.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6fedf47ff1a..938e108d58b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 5c9a8a27c8289279316efc1f8a9f2e80833ce904 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 25 Aug 2020 14:23:21 +0200 Subject: [PATCH 74/77] Fix TTS languange characters (#39211) --- homeassistant/components/tts/__init__.py | 11 ++++++-- tests/components/tts/test_init.py | 35 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 39e4702e855..c7c0b986878 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -321,7 +321,9 @@ class SpeechManager: else: options_key = "-" - key = KEY_PATTERN.format(msg_hash, language, options_key, engine).lower() + key = KEY_PATTERN.format( + msg_hash, language.replace("_", "-"), options_key, engine + ).lower() # Is speech already in memory if key in self.mem_cache: @@ -352,9 +354,14 @@ class SpeechManager: # Create file infos filename = f"{key}.{extension}".lower() - data = self.write_tags(filename, data, provider, message, language, options) + # Validate filename + if not _RE_VOICE_FILE.match(filename): + raise HomeAssistantError( + f"TTS filename '{filename}' from {engine} is invalid!" + ) # Save to memory + data = self.write_tags(filename, data, provider, message, language, options) self._async_store_to_memcache(key, filename, data) if cache: diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index b4cb9c67af3..a100ef22c65 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -176,6 +176,41 @@ async def test_setup_component_and_test_service_with_config_language( ).is_file() +async def test_setup_component_and_test_service_with_config_language_special( + hass, empty_cache_dir +): + """Set up the demo platform and call service with extend language.""" + import homeassistant.components.demo.tts as demo_tts + + demo_tts.SUPPORT_LANGUAGES.append("en_US") + calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) + + config = {tts.DOMAIN: {"platform": "demo", "language": "en_US"}} + + with assert_setup_component(1, tts.DOMAIN): + assert await async_setup_component(hass, tts.DOMAIN, config) + + await hass.services.async_call( + tts.DOMAIN, + "demo_say", + { + "entity_id": "media_player.something", + tts.ATTR_MESSAGE: "There is someone at the door.", + }, + blocking=True, + ) + assert len(calls) == 1 + assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC + assert ( + calls[0].data[ATTR_MEDIA_CONTENT_ID] + == "http://example.local:8123/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_demo.mp3" + ) + await hass.async_block_till_done() + assert ( + empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_demo.mp3" + ).is_file() + + async def test_setup_component_and_test_service_with_wrong_conf_language(hass): """Set up the demo platform and call service with wrong config.""" config = {tts.DOMAIN: {"platform": "demo", "language": "ru"}} From 9678cbdde20e9f879732836f3e9a8a2dd2a136a2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 26 Aug 2020 10:50:25 -0500 Subject: [PATCH 75/77] Fix time pattern listener firing a few microseconds early (#39281) --- homeassistant/helpers/event.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 3f0c2db3b2f..be002a1b204 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -620,13 +620,19 @@ def async_track_utc_time_change( calculate_next(now + timedelta(seconds=1)) + # We always get time.time() first to avoid time.time() + # ticking forward after fetching hass.loop.time() + # and callback being scheduled a few microseconds early cancel_callback = hass.loop.call_at( - hass.loop.time() + next_time.timestamp() - time.time(), + -time.time() + hass.loop.time() + next_time.timestamp(), pattern_time_change_listener, ) + # We always get time.time() first to avoid time.time() + # ticking forward after fetching hass.loop.time() + # and callback being scheduled a few microseconds early cancel_callback = hass.loop.call_at( - hass.loop.time() + next_time.timestamp() - time.time(), + -time.time() + hass.loop.time() + next_time.timestamp(), pattern_time_change_listener, ) From 77107e4501d70a368ff2d333a90af6f39a42683b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 26 Aug 2020 15:52:05 +0000 Subject: [PATCH 76/77] Bumped version to 0.114.4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 938e108d58b..9dd74f6709c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 114 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 8fa1a1f600d6822646685058127690e8204fe8b9 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Sun, 9 Aug 2020 22:26:35 -0500 Subject: [PATCH 77/77] Fix async_fire_time_changed in tests/common.py (#38705) --- tests/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common.py b/tests/common.py index bcb66428f6b..16f349de800 100644 --- a/tests/common.py +++ b/tests/common.py @@ -295,8 +295,8 @@ def async_fire_time_changed(hass, datetime_, fire_all=False): if task.cancelled(): continue - future_seconds = task.when() - hass.loop.time() mock_seconds_into_future = datetime_.timestamp() - time.time() + future_seconds = task.when() - hass.loop.time() if fire_all or mock_seconds_into_future >= future_seconds: with patch(