diff --git a/homeassistant/components/konnected/.translations/en.json b/homeassistant/components/konnected/.translations/en.json index fd0a8e84e37..bc86a5ca549 100644 --- a/homeassistant/components/konnected/.translations/en.json +++ b/homeassistant/components/konnected/.translations/en.json @@ -33,6 +33,7 @@ "abort": { "not_konn_panel": "Not a recognized Konnected.io device" }, + "error": {}, "step": { "options_binary": { "data": { @@ -91,11 +92,12 @@ "data": { "activation": "Output when on", "momentary": "Pulse duration (ms) (optional)", + "more_states": "Configure additional states for this zone", "name": "Name (optional)", "pause": "Pause between pulses (ms) (optional)", "repeat": "Times to repeat (-1=infinite) (optional)" }, - "description": "Please select the output options for {zone}", + "description": "Please select the output options for {zone}: state {state}", "title": "Configure Switchable Output" } }, diff --git a/homeassistant/components/konnected/config_flow.py b/homeassistant/components/konnected/config_flow.py index cb9004c9efe..172f60cd42d 100644 --- a/homeassistant/components/konnected/config_flow.py +++ b/homeassistant/components/konnected/config_flow.py @@ -57,6 +57,10 @@ CONF_IO_BIN = "Binary Sensor" CONF_IO_DIG = "Digital Sensor" CONF_IO_SWI = "Switchable Output" +CONF_MORE_STATES = "more_states" +CONF_YES = "Yes" +CONF_NO = "No" + KONN_MANUFACTURER = "konnected.io" KONN_PANEL_MODEL_NAMES = { KONN_MODEL: "Konnected Alarm Panel", @@ -117,7 +121,7 @@ SWITCH_SCHEMA = vol.Schema( vol.Required(CONF_ZONE): vol.In(ZONES), vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_ACTIVATION, default=STATE_HIGH): vol.All( - vol.Lower, vol.Any(STATE_HIGH, STATE_LOW) + vol.Lower, vol.In([STATE_HIGH, STATE_LOW]) ), vol.Optional(CONF_MOMENTARY): vol.All(vol.Coerce(int), vol.Range(min=10)), vol.Optional(CONF_PAUSE): vol.All(vol.Coerce(int), vol.Range(min=10)), @@ -361,6 +365,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self.new_opt = {CONF_IO: {}} self.active_cfg = None self.io_cfg = {} + self.current_states = [] + self.current_state = 1 @callback def get_current_cfg(self, io_type, zone): @@ -666,12 +672,21 @@ class OptionsFlowHandler(config_entries.OptionsFlow): if user_input is not None: zone = {"zone": self.active_cfg} zone.update(user_input) + del zone[CONF_MORE_STATES] self.new_opt[CONF_SWITCHES] = self.new_opt.get(CONF_SWITCHES, []) + [zone] - self.io_cfg.pop(self.active_cfg) - self.active_cfg = None + + # iterate through multiple switch states + if self.current_states: + self.current_states.pop(0) + + # only go to next zone if all states are entered + self.current_state += 1 + if user_input[CONF_MORE_STATES] == CONF_NO: + self.io_cfg.pop(self.active_cfg) + self.active_cfg = None if self.active_cfg: - current_cfg = self.get_current_cfg(CONF_SWITCHES, self.active_cfg) + current_cfg = next(iter(self.current_states), {}) return self.async_show_form( step_id="options_switch", data_schema=vol.Schema( @@ -682,7 +697,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): vol.Optional( CONF_ACTIVATION, default=current_cfg.get(CONF_ACTIVATION, STATE_HIGH), - ): vol.All(vol.Lower, vol.Any(STATE_HIGH, STATE_LOW)), + ): vol.All(vol.Lower, vol.In([STATE_HIGH, STATE_LOW])), vol.Optional( CONF_MOMENTARY, default=current_cfg.get(CONF_MOMENTARY, vol.UNDEFINED), @@ -695,12 +710,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow): CONF_REPEAT, default=current_cfg.get(CONF_REPEAT, vol.UNDEFINED), ): vol.All(vol.Coerce(int), vol.Range(min=-1)), + vol.Required( + CONF_MORE_STATES, + default=CONF_YES + if len(self.current_states) > 1 + else CONF_NO, + ): vol.In([CONF_YES, CONF_NO]), } ), description_placeholders={ "zone": f"Zone {self.active_cfg}" if len(self.active_cfg) < 3 - else self.active_cfg.upper() + else self.active_cfg.upper(), + "state": str(self.current_state), }, errors=errors, ) @@ -709,7 +731,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow): for key, value in self.io_cfg.items(): if value == CONF_IO_SWI: self.active_cfg = key - current_cfg = self.get_current_cfg(CONF_SWITCHES, self.active_cfg) + self.current_states = [ + cfg + for cfg in self.current_opt.get(CONF_SWITCHES, []) + if cfg[CONF_ZONE] == self.active_cfg + ] + current_cfg = next(iter(self.current_states), {}) + self.current_state = 1 return self.async_show_form( step_id="options_switch", data_schema=vol.Schema( @@ -720,7 +748,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ): str, vol.Optional( CONF_ACTIVATION, - default=current_cfg.get(CONF_ACTIVATION, "high"), + default=current_cfg.get(CONF_ACTIVATION, STATE_HIGH), ): vol.In(["low", "high"]), vol.Optional( CONF_MOMENTARY, @@ -734,12 +762,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow): CONF_REPEAT, default=current_cfg.get(CONF_REPEAT, vol.UNDEFINED), ): vol.All(vol.Coerce(int), vol.Range(min=-1)), + vol.Required( + CONF_MORE_STATES, + default=CONF_YES + if len(self.current_states) > 1 + else CONF_NO, + ): vol.In([CONF_YES, CONF_NO]), } ), description_placeholders={ "zone": f"Zone {self.active_cfg}" if len(self.active_cfg) < 3 - else self.active_cfg.upper() + else self.active_cfg.upper(), + "state": str(self.current_state), }, errors=errors, ) diff --git a/homeassistant/components/konnected/strings.json b/homeassistant/components/konnected/strings.json index 4d923238df4..f1d7ef43ddc 100644 --- a/homeassistant/components/konnected/strings.json +++ b/homeassistant/components/konnected/strings.json @@ -80,13 +80,14 @@ }, "options_switch": { "title": "Configure Switchable Output", - "description": "Please select the output options for {zone}", + "description": "Please select the output options for {zone}: state {state}", "data": { "name": "Name (optional)", "activation": "Output when on", "momentary": "Pulse duration (ms) (optional)", "pause": "Pause between pulses (ms) (optional)", - "repeat": "Times to repeat (-1=infinite) (optional)" + "repeat": "Times to repeat (-1=infinite) (optional)", + "more_states": "Configure additional states for this zone" } }, "options_misc": { diff --git a/tests/components/konnected/test_config_flow.py b/tests/components/konnected/test_config_flow.py index 3638f40735b..35814154f47 100644 --- a/tests/components/konnected/test_config_flow.py +++ b/tests/components/konnected/test_config_flow.py @@ -403,6 +403,14 @@ async def test_import_existing_config(hass, mock_panel): "pause": 100, "repeat": 4, }, + { + "zone": 8, + "name": "alarm", + "activation": "low", + "momentary": 100, + "pause": 100, + "repeat": -1, + }, {"zone": "out1"}, {"zone": "alarm1"}, ], @@ -463,6 +471,14 @@ async def test_import_existing_config(hass, mock_panel): "pause": 100, "repeat": 4, }, + { + "zone": "8", + "name": "alarm", + "activation": "low", + "momentary": 100, + "pause": 100, + "repeat": -1, + }, {"activation": "high", "zone": "out1"}, {"activation": "high", "zone": "alarm1"}, ], @@ -713,6 +729,7 @@ async def test_option_flow(hass, mock_panel): assert result["step_id"] == "options_switch" assert result["description_placeholders"] == { "zone": "Zone 4", + "state": "1", } # zone 4 @@ -723,6 +740,7 @@ async def test_option_flow(hass, mock_panel): assert result["step_id"] == "options_switch" assert result["description_placeholders"] == { "zone": "OUT", + "state": "1", } # zone out @@ -734,6 +752,27 @@ async def test_option_flow(hass, mock_panel): "momentary": 50, "pause": 100, "repeat": 4, + "more_states": "Yes", + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "options_switch" + assert result["description_placeholders"] == { + "zone": "OUT", + "state": "2", + } + + # zone out - state 2 + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "name": "alarm", + "activation": "low", + "momentary": 100, + "pause": 100, + "repeat": -1, + "more_states": "No", }, ) @@ -768,6 +807,14 @@ async def test_option_flow(hass, mock_panel): "pause": 100, "repeat": 4, }, + { + "zone": "out", + "name": "alarm", + "activation": "low", + "momentary": 100, + "pause": 100, + "repeat": -1, + }, ], } @@ -977,6 +1024,14 @@ async def test_option_flow_import(hass, mock_panel): "pause": 100, "repeat": 4, }, + { + "zone": "3", + "name": "alarm", + "activation": "low", + "momentary": 100, + "pause": 100, + "repeat": -1, + }, ], } ) @@ -1056,8 +1111,9 @@ async def test_option_flow_import(hass, mock_panel): assert schema["momentary"] == 50 assert schema["pause"] == 100 assert schema["repeat"] == 4 + assert schema["more_states"] == "Yes" result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={"activation": "high"} + result["flow_id"], user_input={"activation": "high", "more_states": "No"} ) assert result["type"] == "form" assert result["step_id"] == "options_misc" diff --git a/tests/components/konnected/test_init.py b/tests/components/konnected/test_init.py index e410aa9d60a..a678716bc03 100644 --- a/tests/components/konnected/test_init.py +++ b/tests/components/konnected/test_init.py @@ -124,7 +124,7 @@ async def test_config_schema(hass): } } - # check pin to zone + # check pin to zone and multiple output config = { konnected.DOMAIN: { konnected.CONF_ACCESS_TOKEN: "abcdefgh", @@ -135,6 +135,22 @@ async def test_config_schema(hass): {"pin": 2, "type": "door"}, {"zone": 1, "type": "door"}, ], + "switches": [ + { + "zone": 3, + "name": "Beep Beep", + "momentary": 65, + "pause": 55, + "repeat": 4, + }, + { + "zone": 3, + "name": "Warning", + "momentary": 100, + "pause": 100, + "repeat": -1, + }, + ], } ], } @@ -153,7 +169,7 @@ async def test_config_schema(hass): "11": "Disabled", "12": "Disabled", "2": "Binary Sensor", - "3": "Disabled", + "3": "Switchable Output", "4": "Disabled", "5": "Disabled", "6": "Disabled", @@ -169,6 +185,24 @@ async def test_config_schema(hass): {"inverse": False, "type": "door", "zone": "2"}, {"inverse": False, "type": "door", "zone": "1"}, ], + "switches": [ + { + "zone": "3", + "activation": "high", + "name": "Beep Beep", + "momentary": 65, + "pause": 55, + "repeat": 4, + }, + { + "zone": "3", + "activation": "high", + "name": "Warning", + "momentary": 100, + "pause": 100, + "repeat": -1, + }, + ], }, "id": "aabbccddeeff", }