diff --git a/README.md b/README.md index c62d7290..09b577b0 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Elatko devices are exemplarily mentioned. You can find [here](https://www.eltako * A5-38-08 (light and switch) * H5-3F-7F (cover) * [Send Message Service](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/service-send-message/readme.md) Sends any EnOcean Message. Can be used for [automatinos in Home Assistant](https://www.home-assistant.io/getting-started/automation/) so that none-EnOcean and EnOcean deviecs can be combined. + * Not supported EEPs: A5-09-0C (Air Quality), A5-38-08 (Central Command) [**Gateway**](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/gateways/readme.md) (See also [how to use gateways](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/gateway_usage/readme.md) and [multiple gateway support](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/multiple-gateway-support/readme.md)) * **Eltako FAM14** and Eltako **FGW14-USB** (based on ESP2, rs485 bus and baud rate 57600, uses library [eltako14bus](https://github.com/grimmpp/eltako14bus)) diff --git a/changes.md b/changes.md index 8f5dee90..ca3f2fc3 100644 --- a/changes.md +++ b/changes.md @@ -7,6 +7,7 @@ * Unit-Tests added and improved for EEP A5-04-01, A5-04-02, A5-10-06, A5-10-12, A5-13-01, and F6-10-00. * EEP A5-04-03 added for Eltako FFT60 (temperature and humiditry) * EEP A5-06-01 added for light sensor (currently twilight and daylight are combinded in one illumination sensor/entity) +* Bug fixes in EEPs (in [eltako14bus library](https://github.com/grimmpp/eltako14bus)) ## Version 1.4.0 ESP3 Support (USB300) * Docs about gateway usage added. diff --git a/custom_components/eltako/config_helpers.py b/custom_components/eltako/config_helpers.py index 9a57ccc4..6abad980 100644 --- a/custom_components/eltako/config_helpers.py +++ b/custom_components/eltako/config_helpers.py @@ -123,7 +123,7 @@ def get_device_config(config: dict, id: int) -> dict: return g[CONF_DEVICES] return None -async def async_get_list_of_gateway_descriptions(hass: HomeAssistant, CONFIG_SCHEMA: dict, get_integration_config=async_integration_yaml_config, filter_out: [str]=[]) -> dict: +async def async_get_list_of_gateway_descriptions(hass: HomeAssistant, CONFIG_SCHEMA: dict, get_integration_config=async_integration_yaml_config, filter_out: list[str]=[]) -> dict: config = await async_get_home_assistant_config(hass, CONFIG_SCHEMA, get_integration_config) return get_list_of_gateway_descriptions(config, filter_out) diff --git a/custom_components/eltako/gateway.py b/custom_components/eltako/gateway.py index fab554a7..be1c2133 100644 --- a/custom_components/eltako/gateway.py +++ b/custom_components/eltako/gateway.py @@ -216,7 +216,7 @@ async def async_setup(self): # Command Section - async def async_service_send_message(self, event) -> None: + async def async_service_send_message(self, event, raise_exception=False) -> None: """Send an arbitrary message with the provided eep.""" LOGGER.debug(f"[Service Send Message: {event.service}] Received event data: {event.data}") @@ -252,10 +252,11 @@ async def async_service_send_message(self, event) -> None: msg = eep.encode_message(sender_id[0]) LOGGER.debug(f"[Service Send Message: {event.service}] Generated message: {msg} Serialized: {msg.serialize().hex()}") # send message - event_id = config_helpers.get_bus_event_type(self.base_id, SIGNAL_SEND_MESSAGE) - dispatcher_send(self.hass, event_id, msg) - except: + self.send_message(msg) + except Exception as e: LOGGER.error(f"[Service Send Message: {event.service}] Cannot send message.", exc_info=True, stack_info=True) + if raise_exception: + raise e diff --git a/custom_components/eltako/manifest.json b/custom_components/eltako/manifest.json index b147b1b0..aca7540f 100644 --- a/custom_components/eltako/manifest.json +++ b/custom_components/eltako/manifest.json @@ -9,6 +9,6 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/grimmpp/home-assistant-eltako/issues", "loggers": ["eltako"], - "requirements": ["eltako14bus==0.0.48","enocean==0.60.1", "StrEnum", "esp2-gateway-adapter==0.1"], + "requirements": ["eltako14bus==0.0.49","enocean==0.60.1", "StrEnum", "esp2-gateway-adapter==0.1"], "version": "1.4.1" } diff --git a/docs/service-send-message/eep-params.md b/docs/service-send-message/eep-params.md new file mode 100644 index 00000000..85babf46 --- /dev/null +++ b/docs/service-send-message/eep-params.md @@ -0,0 +1,28 @@ +# Paramters for EEPs in Send Message Events +## Not Supported EEPs +* A5-09-0C +* A5-38-08 + +## Parameters for events: +* A5-04-01: humidity, learn_button, temp_availability, temperature +* A5-04-02: humidity, learn_button, temperature +* A5-04-03: humidity, learn_button, telegram_type, temperature +* A5-06-01: day_light, illumination, twilight +* A5-07-01: learn_button, pir_status, pir_status_on, support_volrage_availability, support_voltage +* A5-08-01: illumination, learn_button, occupancy_button, pir_status, supply_voltage, temperature +* A5-10-06: current_temp, mode, stand_by, target_temp +* A5-10-12: current_temperature, humidity, target_temperature +* A5-12-01: data_type, divisor, learn_button, measurement_channel, meter_reading +* A5-12-02: data_type, divisor, learn_button, measurement_channel, meter_reading +* A5-12-03: data_type, divisor, learn_button, measurement_channel, meter_reading +* A5-13-01: dawn_sensor, day_night, hemisphere, identifier, learn_button, rain_indication, sun_east, sun_south, sun_west, temperature, wind_speed +* D5-00-01: contact, learn_button +* F6-02-01: energy_bow, rocker_first_action, rocker_second_action, second_action +* F6-02-02: energy_bow, rocker_first_action, rocker_second_action, second_action +* F6-10-00: handle_position, movement +* G5-3F-7F: direction, state, time +* H5-3F-7F: command, learn_button, time +* M5-38-08: state + +## References: +Implementation of EEPs can be found [eltako14bus library](https://github.com/grimmpp/eltako14bus/blob/master/eltakobus/eep.py). diff --git a/docs/service-send-message/readme.md b/docs/service-send-message/readme.md index 109f47cb..e12a3560 100644 --- a/docs/service-send-message/readme.md +++ b/docs/service-send-message/readme.md @@ -8,7 +8,7 @@ This service is mainly inteded to combine none-EnOcean and EnOcean devices. Serv Create an [automation](https://www.home-assistant.io/getting-started/automation/) in Home Assistant. Use the upper sections to react on anything you like, dependent on you sensor/sender. -`Add Action` and search for `eltako`. It proposes a service to send messages for every available gateway. In addition you need to enter in the data section what to send. The fields `id` and `eep` must be specified. `id` stands for the sender address. (Keep in mind: bus gateways do not send in wireless network and wireless tranceivers do only have 128 hardcoded addresses to be used.) `eep` is the message format you what to use. Dependent on the eep specific information is put into the messages to be sent. You can either check in the [code](https://github.com/grimmpp/eltako14bus/blob/master/eltakobus/eep.py) or in the logs what attribute need to be set. +`Add Action` and search for `eltako`. It proposes a service to send messages for every available gateway. In addition you need to enter in the data section what to send. The fields `id` and `eep` must be specified. `id` stands for the sender address. (Keep in mind: bus gateways do not send in wireless network and wireless tranceivers do only have 128 hardcoded addresses to be used.) `eep` is the message format you what to use. Dependent on the eep specific information is put into the messages to be sent. You can either check in the [code](https://github.com/grimmpp/eltako14bus/blob/master/eltakobus/eep.py) or in this automatically generated [parameter list for EEPs](eep-params.md). In the logs you can see what parameters has been transferred and which will be set as default to 0. diff --git a/tests/mocks.py b/tests/mocks.py index ba5d02a8..834f7894 100644 --- a/tests/mocks.py +++ b/tests/mocks.py @@ -26,6 +26,7 @@ class HassMock(): def __init__(self) -> None: self.bus = BusMock() + self.loop = asyncio.get_event_loop() # def async_create_task(self, async_call): # asyncio.run( async_call ) @@ -43,6 +44,10 @@ def __init__(self): def is_active(self): return self._is_active +class EventMock(): + def __init__(self, service:str, data:dict) -> None: + self.service = service + self.data = data class GatewayMock(EnOceanGateway): def __init__(self, general_settings:dict=DEFAULT_GENERAL_SETTINGS, dev_id: int=123, base_id:AddressExpression=AddressExpression.parse('FF-AA-80-00')): diff --git a/tests/test_cover_G5_3F_7F.py b/tests/test_cover_G5_3F_7F.py index 4b217595..74b09bd9 100644 --- a/tests/test_cover_G5_3F_7F.py +++ b/tests/test_cover_G5_3F_7F.py @@ -114,7 +114,7 @@ def test_open_cover(self): ec.open_cover() self.assertEqual( self.last_sent_command.body, - b'k\x07\x00\x04\x01\x08\x00\x00\xb1\x06\x00') + b'k\x07\x00\x0b\x01\x08\x00\x00\xb1\x06\x00') def test_close_cover(self): ec = self.create_cover() @@ -122,7 +122,7 @@ def test_close_cover(self): ec.close_cover() self.assertEqual( self.last_sent_command.body, - b'k\x07\x00\x04\x02\x08\x00\x00\xb1\x06\x00') + b'k\x07\x00\x0b\x02\x08\x00\x00\xb1\x06\x00') def test_stop_cover(self): ec = self.create_cover() @@ -139,7 +139,7 @@ def test_set_cover_position(self): ec.set_cover_position(position=50) self.assertEqual( self.last_sent_command.body, - b'k\x07\x00\x01\x02\x08\x00\x00\xb1\x06\x00') + b'k\x07\x00\x05\x02\x08\x00\x00\xb1\x06\x00') self.assertEqual(ec._attr_is_closing, True) self.assertEqual(ec._attr_is_opening, False) self.last_sent_command = None @@ -148,7 +148,7 @@ def test_set_cover_position(self): ec.set_cover_position(position=50) self.assertEqual( self.last_sent_command.body, - b'k\x07\x00\x01\x01\x08\x00\x00\xb1\x06\x00') + b'k\x07\x00\x05\x01\x08\x00\x00\xb1\x06\x00') self.assertEqual(ec._attr_is_closing, False) self.assertEqual(ec._attr_is_opening, True) self.last_sent_command = None @@ -157,7 +157,7 @@ def test_set_cover_position(self): ec.set_cover_position(position=0) self.assertEqual( self.last_sent_command.body, - b'k\x07\x00\x04\x02\x08\x00\x00\xb1\x06\x00') + b'k\x07\x00\x0b\x02\x08\x00\x00\xb1\x06\x00') self.assertEqual(ec._attr_is_closing, True) self.assertEqual(ec._attr_is_opening, False) self.last_sent_command = None @@ -166,7 +166,7 @@ def test_set_cover_position(self): ec.set_cover_position(position=100) self.assertEqual( self.last_sent_command.body, - b'k\x07\x00\x04\x01\x08\x00\x00\xb1\x06\x00') + b'k\x07\x00\x0b\x01\x08\x00\x00\xb1\x06\x00') self.assertEqual(ec._attr_is_closing, False) self.assertEqual(ec._attr_is_opening, True) self.last_sent_command = None diff --git a/tests/test_send_message_service.py b/tests/test_send_message_service.py new file mode 100644 index 00000000..122b8916 --- /dev/null +++ b/tests/test_send_message_service.py @@ -0,0 +1,77 @@ +import unittest +from mocks import * +from unittest import mock +from homeassistant.helpers import dispatcher +import inspect + +dispatcher.dispatcher_send = mock.Mock(return_value=None) + +class TestSendMessageService(unittest.IsolatedAsyncioTestCase): + + def create_gateway(self): + gateway = GatewayMock(dev_id=123) + + return gateway + + def get_all_eep_names(self): + subclasses = set() + work = [EEP] + while work: + parent = work.pop() + for child in parent.__subclasses__(): + if child not in subclasses: + subclasses.add(child) + work.append(child) + return sorted(set([s.__name__.upper().replace('_','-') for s in subclasses if len(s.__name__) == 8 and s.__name__.count('_') == 2])) + + NOT_SUPPORTED_EEPS = ['A5-09-0C', 'A5-38-08'] + + async def test_send_message(self): + g = self.create_gateway() + # Mock send_message + g.send_message = lambda *args: None + + for eep_name in self.get_all_eep_names(): + + if eep_name in self.NOT_SUPPORTED_EEPS: + continue + + event = EventMock('service_name', { + 'id': 'FF-DD-CC-BB', + 'eep': eep_name, + 'command': 1, + 'identifier': 1 + }) + + await g.async_service_send_message(event, True) + + + async def test_write_eep_params_to_docs_file(self): + text = '# Paramters for EEPs in Send Message Events' + text += '\n' + + text += "## Not Supported EEPs \n" + for eep_name in self.NOT_SUPPORTED_EEPS: + text += f"* {eep_name}\n" + text += '\n' + + text += '## Parameters for events: \n' + + for eep_name in self.get_all_eep_names(): + + if eep_name in self.NOT_SUPPORTED_EEPS: + continue + + sig = inspect.signature(EEP.find(eep_name).__init__) + eep_init_args = sorted([param.name for param in sig.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD and param.name != 'self']) + text += f"* {eep_name}: {', '.join(eep_init_args)}\n" + + text += '\n' + text += '## References:\n' + text += 'Implementation of EEPs can be found [eltako14bus library](https://github.com/grimmpp/eltako14bus/blob/master/eltakobus/eep.py).\n' + + file='./docs/service-send-message/eep-params.md' + with open(file, 'w') as filetowrite: + filetowrite.write(text) + +