From a93a91dcd1aef780a2d0db788c41a691292e8da5 Mon Sep 17 00:00:00 2001 From: DeerMaximum Date: Wed, 17 Jan 2024 21:46:07 +0100 Subject: [PATCH] Ensure slot order --- custom_components/ta_coe/binary_sensor.py | 6 +++ custom_components/ta_coe/config_flow.py | 44 ++++++++++++++++------ custom_components/ta_coe/const.py | 4 +- custom_components/ta_coe/state_observer.py | 5 ++- tests/test_state_observer.py | 14 +++++-- tests/test_state_sensor.py | 8 ++-- 6 files changed, 58 insertions(+), 23 deletions(-) diff --git a/custom_components/ta_coe/binary_sensor.py b/custom_components/ta_coe/binary_sensor.py index 0812d2b..93037b5 100644 --- a/custom_components/ta_coe/binary_sensor.py +++ b/custom_components/ta_coe/binary_sensor.py @@ -17,6 +17,8 @@ CONF_ENTITIES_TO_SEND, DIGITAL_DOMAINS, DOMAIN, + FREE_SLOT_MARKER_ANALOGE, + FREE_SLOT_MARKER_DIGITAL, TYPE_BINARY, ) @@ -103,11 +105,15 @@ def extra_state_attributes(self) -> dict[str, Any]: if x.split(".")[0] in DIGITAL_DOMAINS: digital[index] = x index += 1 + elif x == FREE_SLOT_MARKER_DIGITAL: + index += 1 index = 1 for x in self._entity.values(): if x.split(".")[0] in ANALOG_DOMAINS: analog[index] = x index += 1 + elif x == FREE_SLOT_MARKER_ANALOGE: + index += 1 return {ATTR_ANALOG_ORDER: analog, ATTR_DIGITAL_ORDER: digital} diff --git a/custom_components/ta_coe/config_flow.py b/custom_components/ta_coe/config_flow.py index ca5f8b9..172f9b8 100644 --- a/custom_components/ta_coe/config_flow.py +++ b/custom_components/ta_coe/config_flow.py @@ -24,8 +24,11 @@ CONF_ENTITIES_TO_SEND, CONF_SCAN_INTERVAL, CONF_SLOT_COUNT, + DIGITAL_DOMAINS, DOMAIN, - FREE_SLOT_MARKER, + FREE_SLOT_MARKER_ANALOGE, + FREE_SLOT_MARKER_DIGITAL, + FREE_SLOT_MARKERS, SCAN_INTERVAL, ) @@ -227,11 +230,16 @@ async def async_step_init( ], ) - def get_new_slot_index(self) -> tuple[int, bool]: + def get_new_slot_index(self, is_digital: bool) -> tuple[int, bool]: """Get the first index of an empty slot.""" - if FREE_SLOT_MARKER in self.data[CONF_ENTITIES_TO_SEND].values(): - index = list(self.data[CONF_ENTITIES_TO_SEND]).keys()[ - list(self.data[CONF_ENTITIES_TO_SEND].values()).index(FREE_SLOT_MARKER) + search_marker = FREE_SLOT_MARKER_ANALOGE + + if is_digital: + search_marker = FREE_SLOT_MARKER_DIGITAL + + if search_marker in self.data[CONF_ENTITIES_TO_SEND].values(): + index = list(self.data[CONF_ENTITIES_TO_SEND].keys())[ + list(self.data[CONF_ENTITIES_TO_SEND].values()).index(search_marker) ] return index, False else: @@ -252,11 +260,12 @@ async def async_step_add_send_values( self.data[CONF_ENTITIES_TO_SEND] = {} if new_id not in self.data[CONF_ENTITIES_TO_SEND].values(): - index, used_slot = self.get_new_slot_index() + is_digital = new_id.split(".")[0] in DIGITAL_DOMAINS + index, new_slot = self.get_new_slot_index(is_digital) self.data[CONF_ENTITIES_TO_SEND][str(index)] = new_id - if not used_slot: + if new_slot: self.data[CONF_SLOT_COUNT] = index + 1 return self.async_create_entry(title="", data=self.data) @@ -280,9 +289,13 @@ async def async_step_change_send_values( old_id = user_input["old_value"] if ( - old_id in self.data[CONF_ENTITIES_TO_SEND].values() - or new_id not in self.data[CONF_ENTITIES_TO_SEND].values() - ) and old_id is not FREE_SLOT_MARKER: + ( + old_id in self.data[CONF_ENTITIES_TO_SEND].values() + or new_id not in self.data[CONF_ENTITIES_TO_SEND].values() + ) + and old_id not in FREE_SLOT_MARKERS + and new_id not in FREE_SLOT_MARKERS + ): index = [ k for k, v in self.data[CONF_ENTITIES_TO_SEND].items() @@ -312,14 +325,21 @@ async def async_step_delete_send_values(self, user_input=None): if user_input is not None: for index in user_input[CONF_ENTITIES_TO_SEND]: - self.data[CONF_ENTITIES_TO_SEND][index] = FREE_SLOT_MARKER + marker = FREE_SLOT_MARKER_ANALOGE + if ( + self.data[CONF_ENTITIES_TO_SEND][index].split(".")[0] + in DIGITAL_DOMAINS + ): + marker = FREE_SLOT_MARKER_DIGITAL + + self.data[CONF_ENTITIES_TO_SEND][index] = marker return self.async_create_entry(title="", data=self.data) entities_without_marker = { key: value for key, value in self.data.get(CONF_ENTITIES_TO_SEND, {}).items() - if value != FREE_SLOT_MARKER + if value not in FREE_SLOT_MARKERS } return self.async_show_form( diff --git a/custom_components/ta_coe/const.py b/custom_components/ta_coe/const.py index 5238d04..0f1328d 100644 --- a/custom_components/ta_coe/const.py +++ b/custom_components/ta_coe/const.py @@ -20,7 +20,9 @@ CONF_ENTITIES_TO_SEND = "entities_to_send" CONF_SLOT_COUNT = "slot_count" -FREE_SLOT_MARKER = "--FREE_SLOT_MARKER--" +FREE_SLOT_MARKER_ANALOGE = "--FREE_SLOT_MARKER_A--" +FREE_SLOT_MARKER_DIGITAL = "--FREE_SLOT_MARKER_D--" +FREE_SLOT_MARKERS = [FREE_SLOT_MARKER_ANALOGE, FREE_SLOT_MARKER_DIGITAL] ALLOWED_DOMAINS = ("sensor", "binary_sensor", "number", "input_boolean") diff --git a/custom_components/ta_coe/state_observer.py b/custom_components/ta_coe/state_observer.py index b9dc7d2..81dfe8d 100644 --- a/custom_components/ta_coe/state_observer.py +++ b/custom_components/ta_coe/state_observer.py @@ -10,7 +10,7 @@ _LOGGER, ANALOG_DOMAINS, DIGITAL_DOMAINS, - FREE_SLOT_MARKER, + FREE_SLOT_MARKERS, TYPE_BINARY, TYPE_SENSOR, ) @@ -57,7 +57,7 @@ async def get_all_states(self) -> None: _LOGGER.debug("Update all states") for entity_id in self._entity_list: - if entity_id == FREE_SLOT_MARKER: + if entity_id in FREE_SLOT_MARKERS: continue state = self._hass.states.get(entity_id) @@ -91,6 +91,7 @@ async def _update_listener(self, event: Event) -> None: new_state is None or not self._is_state_valid(new_state.state) or not self._has_state_changed(new_state) + or new_state.entity_id in FREE_SLOT_MARKERS ): return diff --git a/tests/test_state_observer.py b/tests/test_state_observer.py index 72b1ed5..ffbe65d 100644 --- a/tests/test_state_observer.py +++ b/tests/test_state_observer.py @@ -12,7 +12,9 @@ from custom_components.ta_coe.const import ( ANALOG_DOMAINS, DIGITAL_DOMAINS, - FREE_SLOT_MARKER, + FREE_SLOT_MARKER_ANALOGE, + FREE_SLOT_MARKER_DIGITAL, + FREE_SLOT_MARKERS, ) from custom_components.ta_coe.state_sender import StateSender from tests import ( @@ -56,7 +58,11 @@ async def test_observer_receive_all_states_all_states(hass: HomeAssistant): @pytest.mark.asyncio async def test_observer_receive_all_states_ignore_free_slot(hass: HomeAssistant): """Test if the observer ignores the free slot maker on get all states.""" - entity_list = {"0": FREE_SLOT_MARKER, "1": "binary_sensor.test"} + entity_list = { + "0": FREE_SLOT_MARKER_ANALOGE, + "1": "binary_sensor.test", + "2": FREE_SLOT_MARKER_DIGITAL, + } with patch(STATE_AVAILABLE_PACKAGE) as get_states_mock, patch( STATE_SENDER_UPDATE @@ -67,10 +73,10 @@ async def test_observer_receive_all_states_ignore_free_slot(hass: HomeAssistant) ): await StateObserver(hass, coe, state_sender, entity_list).get_all_states() - assert len(get_states_mock.call_args_list) == len(entity_list) - 1 + assert len(get_states_mock.call_args_list) == len(entity_list) - 2 for called_id in get_states_mock.call_args_list: - assert called_id.args[0] != FREE_SLOT_MARKER + assert called_id.args[0] not in FREE_SLOT_MARKERS @pytest.mark.asyncio diff --git a/tests/test_state_sensor.py b/tests/test_state_sensor.py index 6454543..edde351 100644 --- a/tests/test_state_sensor.py +++ b/tests/test_state_sensor.py @@ -13,6 +13,8 @@ ATTR_ANALOG_ORDER, ATTR_DIGITAL_ORDER, CONF_CAN_IDS, + FREE_SLOT_MARKER_ANALOGE, + FREE_SLOT_MARKER_DIGITAL, ) from tests import ( COE_SEND_ANALOG_VALUES_PACKAGE, @@ -27,8 +29,8 @@ CONF_HOST: "http://192.168.2.101", CONF_CAN_IDS: [1, 20], CONF_ENTITIES_TO_SEND: { - 1: "sensor.coe_analog_1", - 2: "binary_sensor.coe_digital_1", + 1: FREE_SLOT_MARKER_ANALOGE, + 2: FREE_SLOT_MARKER_DIGITAL, 3: "sensor.coe_analog_2", 4: "binary_sensor.coe_digital_2", }, @@ -103,11 +105,9 @@ async def test_state_sensor_on(hass: HomeAssistant) -> None: assert state_a1.state == STATE_ON assert state_a1.attributes.get("friendly_name") == "CoE: Send value state" assert state_a1.attributes.get(ATTR_ANALOG_ORDER) == { - 1: ENTRY_DATA[CONF_ENTITIES_TO_SEND][1], 2: ENTRY_DATA[CONF_ENTITIES_TO_SEND][3], } assert state_a1.attributes.get(ATTR_DIGITAL_ORDER) == { - 1: ENTRY_DATA[CONF_ENTITIES_TO_SEND][2], 2: ENTRY_DATA[CONF_ENTITIES_TO_SEND][4], }