diff --git a/README.md b/README.md index 7046b6da..fd846a92 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,14 @@ Elatko devices are exemplarily mentioned. You can find [here](https://www.eltako * Binary sensor * F6-02-01 ([Rocker switch](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/rocker_switch/readme.md), FTS14EM) * F6-02-02 ([Rocker switch](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/rocker_switch/readme.md)) - * F6-10-00 (Window handle, FTS14EM) + * F6-10-00 (Window handle, classic switches or contacs via FTS14EM, window and door contacts like FTKE, supported states: open, closed) * D5-00-01 ([Contact sensor](https://github.com/grimmpp/home-assistant-eltako/tree/main//docs/window_sensor_setup_FTS14EM.md), FTS14EM) incl. signal inverter * A5-07-01 (Occupancy sensor) * Sensor * A5-04-01 (Temperature and Humidity Sensor) - * A5-04-02 (Temperature and Humidity Sensor e.g.: FLGTF, FLT58) + * A5-04-02 (Temperature and Humidity Sensor e.g.: FLGTF, FLT58, FFT60) + * A5-04-03 (Temperature and Humidity Sensor e.g.: FFT60) + * A5-06-01 (Light - Twilight and Illumination) * A5-07-01 (Occupancy sensor) * A5-08-01 (Light-, Temperature-, Occupancy Sensor e.g.: FABH65S, FBH65, FBH65S, FBH65TF) * A5-09-0C (Air Quality / VOC⁠ (Volatile Organic Compounds) e.g. [FLGTF](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/flgtf_temp_humidity_air_quality/readme.md)) @@ -37,7 +39,7 @@ Elatko devices are exemplarily mentioned. You can find [here](https://www.eltako * A5-12-02 (Automated meter reading - gas, F3Z14D) * A5-12-03 (Automated meter reading - water, F3Z14D) * A5-13-01 (Weather station, FWG14) - * F6-10-00 (Window handle, FTS14EM) + * F6-10-00 (Window handle, classic switches or contacs via FTS14EM, window and door contacts like FTKE, supported states: open, closed, tilt) * [Light](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/lights-tutorial/readme.md) * A5-38-08 (Dimmable Light: Central command - gateway, FUD14) * M5-38-08 (Switchable Light: Eltako relay, FSR14) @@ -59,6 +61,10 @@ Elatko devices are exemplarily mentioned. You can find [here](https://www.eltako * [Climate](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/heating-and-cooling/readme.md) (**Experimental** Feedback is welcome.) * A5-10-06 (Eltako FAE14, FHK14, F4HK14, F2L14, FHK61, FME14) * [Teach-In Buttons](https://github.com/grimmpp/home-assistant-eltako/tree/main/docs/teach_in_buttons/readme.md) + * A5-10-06, A5-10-12 (climate/thermostats) + * 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. [**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 a77b4569..ed795e25 100644 --- a/changes.md +++ b/changes.md @@ -1,5 +1,13 @@ # Changes and Feature List +## Version 1.4.1 Support for sending arbritrary messages +* Added Service for sending arbritrary ESP2 messages +* 🐞 Fix for TargetTemperatureSensor (EEP: A5-10-06 and A5-10-12) +* 🐞 Fix for unknow cover positions. +* 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) + ## Version 1.4.0 ESP3 Support (USB300) * Docs about gateway usage added. * Added EEPs F6-02-01 and F6-02-02 as sender EEP for lights so that regular switch commands can be sent from Home Assistant. diff --git a/custom_components/eltako/button.py b/custom_components/eltako/button.py index 908d5a71..c7251384 100644 --- a/custom_components/eltako/button.py +++ b/custom_components/eltako/button.py @@ -26,6 +26,7 @@ EEP_WITH_TEACH_IN_BUTTONS = { A5_10_06: b'\x40\x30\x0D\x85', # climate + A5_10_12: b'\x40\x90\x0D\x80', # climate A5_38_08: b'\xE0\x40\x0D\x80', # light H5_3F_7F: b'\xFF\xF8\x0D\x80', # cover # F6_02_01 # What button to take? diff --git a/custom_components/eltako/cover.py b/custom_components/eltako/cover.py index ce5b1663..93ad9e28 100644 --- a/custom_components/eltako/cover.py +++ b/custom_components/eltako/cover.py @@ -230,53 +230,6 @@ def stop_cover(self, **kwargs: Any) -> None: self.schedule_update_ha_state() - def value_changed(self, msg): - """Update the internal state of the cover.""" - try: - decoded = self._dev_eep.decode_message(msg) - except Exception as e: - LOGGER.warning("Could not decode message: %s", str(e)) - return - - if self._dev_eep in [G5_3F_7F]: - if decoded.state == 0x02: # down - self._attr_is_closing = True - self._attr_is_opening = False - elif decoded.state == 0x50: # closed - self._attr_is_opening = False - self._attr_is_closing = False - self._attr_is_closed = True - self._attr_current_cover_position = 0 - elif decoded.state == 0x01: # up - self._attr_is_opening = True - self._attr_is_closing = False - self._attr_is_closed = False - elif decoded.state == 0x70: # open - self._attr_is_opening = False - self._attr_is_closing = False - self._attr_is_closed = False - self._attr_current_cover_position = 100 - elif decoded.time is not None and decoded.direction is not None and self._time_closes is not None and self._time_opens is not None: - time_in_seconds = decoded.time / 10.0 - - if decoded.direction == 0x01: # up - self._attr_current_cover_position = min(self._attr_current_cover_position + int(time_in_seconds / self._time_opens * 100.0), 100) - - - else: # down - self._attr_current_cover_position = max(self._attr_current_cover_position - int(time_in_seconds / self._time_closes * 100.0), 0) - - if self._attr_current_cover_position == 0: - self._attr_is_closed = True - - self._attr_is_closing = False - self._attr_is_opening = False - - LOGGER.debug(f"[cover {self.dev_id}] state: {self.state}, opening: {self.is_opening}, closing: {self.is_closing}, closed: {self.is_closed}, position: {self.current_cover_position}") - - self.schedule_update_ha_state() - - def value_changed(self, msg): """Update the internal state of the cover.""" try: @@ -309,19 +262,31 @@ def value_changed(self, msg): elif decoded.time is not None and decoded.direction is not None and self._time_closes is not None and self._time_opens is not None: time_in_seconds = decoded.time / 10.0 - - if decoded.direction == 0x01: # up + + if decoded.direction == 0x01: # up + # If the latest state is unknown, the cover position + # will be set to None, therefore we have to guess + # the initial position. + if self._attr_current_cover_position is None: + self._attr_current_cover_position = 0 + self._attr_current_cover_position = min(self._attr_current_cover_position + int(time_in_seconds / self._time_opens * 100.0), 100) self._attr_is_opening = True self._attr_is_closing = False self._attr_is_closed = None + + else: # down + # If the latest state is unknown, the cover position + # will be set to None, therefore we have to guess + # the initial position. + if self._attr_current_cover_position is None: + self._attr_current_cover_position = 100 - else: # down self._attr_current_cover_position = max(self._attr_current_cover_position - int(time_in_seconds / self._time_closes * 100.0), 0) self._attr_is_opening = False self._attr_is_closing = True self._attr_is_closed = None - + if self._attr_current_cover_position == 0: self._attr_is_closed = True self._attr_is_opening = False diff --git a/custom_components/eltako/gateway.py b/custom_components/eltako/gateway.py index 68f777c2..fab554a7 100644 --- a/custom_components/eltako/gateway.py +++ b/custom_components/eltako/gateway.py @@ -13,6 +13,7 @@ from eltakobus.message import ESP2Message, RPSMessage, Regular1BSMessage, Regular4BSMessage, EltakoPoll, prettify from eltakobus.util import AddressExpression +from eltakobus.eep import EEP from enocean.communicators import SerialCommunicator from enocean.protocol.packet import RadioPacket, RORG, Packet @@ -205,6 +206,64 @@ async def async_setup(self): self.hass, event_id, self._callback_send_message_to_serial_bus ) + # Register home assistant service for sending arbitrary telegrams. + # + # The service will be registered for each gateway, as the user + # might have different gateways that cause the eltako relays + # only to react on them. + service_name = f"gateway_{self._attr_dev_id}_send_message" + self.hass.services.async_register(DOMAIN, service_name, self.async_service_send_message) + + + # Command Section + async def async_service_send_message(self, event) -> None: + """Send an arbitrary message with the provided eep.""" + LOGGER.debug(f"[Service Send Message: {event.service}] Received event data: {event.data}") + + try: + sender_id_str = event.data.get("id", None) + sender_id:AddressExpression = AddressExpression.parse(sender_id_str) + except: + LOGGER.error(f"[Service Send Message: {event.service}] No valid sender id defined. (Given sender id: {sender_id_str})") + return + + try: + sender_eep_str = event.data.get("eep", None) + sender_eep:EEP = EEP.find(sender_eep_str) + except: + LOGGER.error(f"[Service Send Message: {event.service}] No valid sender id defined. (Given sender id: {sender_id_str})") + return + + # prepare all arguements for eep constructor + import inspect + sig = inspect.signature(sender_eep.__init__) + eep_init_args = [param.name for param in sig.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD] + knargs = {filter_key:event.data[filter_key] for filter_key in eep_init_args if filter_key in event.data and filter_key != 'self'} + LOGGER.debug(f"[Service Send Message: {event.service}] Provided EEP ({sender_eep.__name__}) args: {knargs})") + uknargs = {filter_key:0 for filter_key in eep_init_args if filter_key not in event.data and filter_key != 'self'} + LOGGER.debug(f"[Service Send Message: {event.service}] Missing EEP ({sender_eep.__name__}) args: {uknargs})") + eep_args = knargs + eep_args.update(uknargs) + + eep:EEP = sender_eep(**eep_args) + + try: + # create message + 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: + LOGGER.error(f"[Service Send Message: {event.service}] Cannot send message.", exc_info=True, stack_info=True) + + + + def send_message(self, msg: ESP2Message): + """Put message on RS485 bus. First the message is put onto HA event bus so that other automations can react on messages.""" + event_id = config_helpers.get_bus_event_type(self.base_id, SIGNAL_SEND_MESSAGE) + dispatcher_send(self.hass, event_id, msg) + def unload(self): """Disconnect callbacks established at init time.""" diff --git a/custom_components/eltako/manifest.json b/custom_components/eltako/manifest.json index 94b2931b..b147b1b0 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.47","enocean==0.60.1", "StrEnum", "esp2-gateway-adapter==0.1"], - "version": "1.3.9" + "requirements": ["eltako14bus==0.0.48","enocean==0.60.1", "StrEnum", "esp2-gateway-adapter==0.1"], + "version": "1.4.1" } diff --git a/custom_components/eltako/schema.py b/custom_components/eltako/schema.py index 6eae9795..a41df48f 100644 --- a/custom_components/eltako/schema.py +++ b/custom_components/eltako/schema.py @@ -149,6 +149,8 @@ class SensorSchema(EltakoPlatformSchema): CONF_EEP_SUPPORTED = [A5_04_01.eep_string, A5_04_02.eep_string, + A5_04_03.eep_string, + A5_06_01.eep_string, A5_07_01.eep_string, A5_08_01.eep_string, A5_09_0C.eep_string, diff --git a/custom_components/eltako/sensor.py b/custom_components/eltako/sensor.py index bd465435..4a6abeeb 100644 --- a/custom_components/eltako/sensor.py +++ b/custom_components/eltako/sensor.py @@ -344,7 +344,7 @@ async def async_setup_entry( entities.append(EltakoMeterSensor(platform, gateway, dev_conf.id, dev_name, dev_conf.eep, SENSOR_DESC_WATER_CUMULATIVE, tariff=(tariff - 1))) entities.append(EltakoMeterSensor(platform, gateway, dev_conf.id, dev_name, dev_conf.eep, SENSOR_DESC_WATER_CURRENT, tariff=(tariff - 1))) - elif dev_conf.eep in [A5_04_02, A5_10_12, A5_04_01]: + elif dev_conf.eep in [A5_04_01, A5_04_02, A5_04_03, A5_10_12]: entities.append(EltakoTemperatureSensor(platform, gateway, dev_conf.id, dev_name, dev_conf.eep)) entities.append(EltakoHumiditySensor(platform, gateway, dev_conf.id, dev_name, dev_conf.eep)) @@ -371,6 +371,11 @@ async def async_setup_entry( entities.append(EltakoBatteryVoltageSensor(platform, gateway, dev_conf.id, dev_name, dev_conf.eep)) # _pir_status => as binary sensor + elif dev_conf.eep in [A5_06_01]: + entities.append(EltakoIlluminationSensor(platform, gateway, dev_conf.id, dev_name, dev_conf.eep)) + #TODO: add twilight + #TODO: add daylight + # both are currently combined in illumination except Exception as e: LOGGER.warning("[%s] Could not load configuration", platform) @@ -757,7 +762,7 @@ def value_changed(self, msg: ESP2Message): LOGGER.warning("[Target Temperature Sensor %s] Could not decode message: %s", self.dev_id, str(e)) return - self._attr_target_temperature = round( 2*decoded.target_temperature, 0)/2 + self._attr_native_value = round(2 * decoded.target_temperature, 0) / 2 self.schedule_update_ha_state() diff --git a/custom_components/eltako/services.yaml b/custom_components/eltako/services.yaml new file mode 100644 index 00000000..e69de29b diff --git a/docs/service-send-message/readme.md b/docs/service-send-message/readme.md new file mode 100644 index 00000000..109f47cb --- /dev/null +++ b/docs/service-send-message/readme.md @@ -0,0 +1,33 @@ +# Send Message Service + +This service is mainly inteded to combine none-EnOcean and EnOcean devices. Services which cannot communicate nativcely because they are based on different communication protocols, you can use automations in Home Assistant to e.g. receive trigger from an none-EnOcean sender and send a EnOcean message to an relay or sync states of a thermostat. ... + +## Configuration + + + +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. + + + +Values of e.g. other sensers can also be dynamically added by +``` +alias: send message +description: "" +trigger: [] +condition: [] +action: + - service: eltako.gateway_3_send_message + metadata: {} + data: + id: FF-DD-00-01 + eep: A5-10-06 + current_temp: {{state_attr('climate.my_other_brands_smart_thermostat_1293127', 'current_temperature') }} + target_temp: {{state_attr('climate.my_other_brands_smart_thermostat_1293127', 'target_temperature') }} + enabled: true +mode: single +``` + +By triggering this example, from above, manually you will send a message via gateway 3 containing the current and target temperature of the thermostat entity 1293127. \ No newline at end of file diff --git a/docs/service-send-message/send_message_automation_screenshot.png b/docs/service-send-message/send_message_automation_screenshot.png new file mode 100644 index 00000000..cbf51961 Binary files /dev/null and b/docs/service-send-message/send_message_automation_screenshot.png differ diff --git a/docs/service-send-message/send_message_logs_screenshot.png b/docs/service-send-message/send_message_logs_screenshot.png new file mode 100644 index 00000000..aa390992 Binary files /dev/null and b/docs/service-send-message/send_message_logs_screenshot.png differ diff --git a/eltakodevice_discovery/readme.md b/eltakodevice_discovery/readme.md index 6c606d0a..dbd61607 100644 --- a/eltakodevice_discovery/readme.md +++ b/eltakodevice_discovery/readme.md @@ -1,5 +1,7 @@ # Eltako Device and Sensor Discovery +# DEPRECATED: Check out [EnOnocean Device Manager](https://github.com/grimmpp/enocean-device-manager) + Main purpose of this tool is to programmatically prepare the Home Assistance configuration as good as possible. ## Limitation diff --git a/tests/test_sensor_A5_04_01.py b/tests/test_sensor_A5_04_01.py new file mode 100644 index 00000000..633f910e --- /dev/null +++ b/tests/test_sensor_A5_04_01.py @@ -0,0 +1,46 @@ +import unittest +from custom_components.eltako.sensor import * +from mocks import HassMock +from unittest import mock +from mocks import * +from homeassistant.helpers.entity import Entity +from homeassistant.const import Platform +from custom_components.eltako.binary_sensor import EltakoBinarySensor +from eltakobus import * + +# mock update of Home Assistant +Entity.schedule_update_ha_state = mock.Mock(return_value=None) +# EltakoBinarySensor.hass.bus.fire is mocked by class HassMock + + +class TestSensor_A5_04_01(unittest.TestCase): + + msg1 = Regular4BSMessage (address=b'\xFF\xFF\x00\x80', data=b'\xaa\x00\x00\x0d', status=0x00) + + def create_temperature_sensor(self) -> EltakoTemperatureSensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-04-01") + s = EltakoTemperatureSensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def create_humidity_sensor(self) -> EltakoHumiditySensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-04-01") + s = EltakoHumiditySensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def test_temperature_sensor_A5_04_02(self): + s_temp = self.create_temperature_sensor() + + s_temp.value_changed(self.msg1) + self.assertEqual(s_temp.native_value, 0.0) + + def test_humidity_sensor_A5_04_02(self): + s_hum = self.create_humidity_sensor() + + s_hum.value_changed(self.msg1) + self.assertEqual(s_hum.native_value, 0.0) \ No newline at end of file diff --git a/tests/test_sensor_A5_04_02.py b/tests/test_sensor_A5_04_02.py index cf48608e..863f16da 100644 --- a/tests/test_sensor_A5_04_02.py +++ b/tests/test_sensor_A5_04_02.py @@ -15,13 +15,13 @@ class TestSensor_A5_04_02(unittest.TestCase): - msg1 = Regular4BSMessage (address=b'\xFF\xFF\x00\x80', data=b'\xaa\x00\x00\x0d', status=0x00) + msg1 = Regular4BSMessage (address=b'\xFF\xFF\x00\x80', data=b'\x00\x7C\x86\x0F', status=0x00) def create_temperature_sensor(self) -> EltakoTemperatureSensor: gateway = GatewayMock() dev_id = AddressExpression.parse("FF-FF-00-80") dev_name = "device name" - dev_eep = EEP.find("A5-08-01") + dev_eep = EEP.find("A5-04-02") s = EltakoTemperatureSensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) return s @@ -37,10 +37,10 @@ def test_temperature_sensor_A5_04_02(self): s_temp = self.create_temperature_sensor() s_temp.value_changed(self.msg1) - self.assertEqual(s_temp.native_value, 0.0) + self.assertEqual(s_temp.native_value, 22.880000000000003) def test_humidity_sensor_A5_04_02(self): s_hum = self.create_humidity_sensor() s_hum.value_changed(self.msg1) - self.assertEqual(s_hum.native_value, 0.0) \ No newline at end of file + self.assertEqual(s_hum.native_value, 49.6) \ No newline at end of file diff --git a/tests/test_sensor_A5_04_03.py b/tests/test_sensor_A5_04_03.py new file mode 100644 index 00000000..f3ee51d3 --- /dev/null +++ b/tests/test_sensor_A5_04_03.py @@ -0,0 +1,46 @@ +import unittest +from custom_components.eltako.sensor import * +from mocks import HassMock +from unittest import mock +from mocks import * +from homeassistant.helpers.entity import Entity +from homeassistant.const import Platform +from custom_components.eltako.binary_sensor import EltakoBinarySensor +from eltakobus import * + +# mock update of Home Assistant +Entity.schedule_update_ha_state = mock.Mock(return_value=None) +# EltakoBinarySensor.hass.bus.fire is mocked by class HassMock + + +class TestSensor_A5_04_02(unittest.TestCase): + + msg1 = Regular4BSMessage (address=b'\xFF\xFF\x00\x80', data=b'\x99\x02\x12\x09', status=0x00) + + def create_temperature_sensor(self) -> EltakoTemperatureSensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-04-03") + s = EltakoTemperatureSensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def create_humidity_sensor(self) -> EltakoHumiditySensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-04-03") + s = EltakoHumiditySensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def test_temperature_sensor_A5_04_02(self): + s_temp = self.create_temperature_sensor() + + s_temp.value_changed(self.msg1) + self.assertEqual(s_temp.native_value, 22.8125) + + def test_humidity_sensor_A5_04_02(self): + s_hum = self.create_humidity_sensor() + + s_hum.value_changed(self.msg1) + self.assertEqual(s_hum.native_value, 60.0) \ No newline at end of file diff --git a/tests/test_sensor_A5_06_01.py b/tests/test_sensor_A5_06_01.py new file mode 100644 index 00000000..3a26e588 --- /dev/null +++ b/tests/test_sensor_A5_06_01.py @@ -0,0 +1,42 @@ +import unittest +from custom_components.eltako.sensor import * +from mocks import HassMock +from unittest import mock +from mocks import * +from homeassistant.helpers.entity import Entity +from homeassistant.const import Platform +from custom_components.eltako.binary_sensor import EltakoBinarySensor +from eltakobus import * + +# mock update of Home Assistant +Entity.schedule_update_ha_state = mock.Mock(return_value=None) +# EltakoBinarySensor.hass.bus.fire is mocked by class HassMock + + +class TestSensor_A5_06_01(unittest.TestCase): + + + + def create_illumination_sensor(self) -> EltakoIlluminationSensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-06-01") + s = EltakoIlluminationSensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def test_illumincation_sensor(self): + s_ill = self.create_illumination_sensor() + + # check daylight + msg = Regular4BSMessage (address=b'\xFF\xFF\x00\x80', data=b'\x60\xAA\x00\x0F', status=0x00) + s_ill.value_changed(msg) + self.assertEqual(s_ill.native_value, 20100.0) + + # check twilight + msg = Regular4BSMessage (address=b'\xFF\xFF\x00\x80', data=b'\x60\x00\x00\x0F', status=0x00) + s_ill.value_changed(msg) + self.assertEqual(s_ill.native_value, 96.0) + + + \ No newline at end of file diff --git a/tests/test_sensor_A5_10_06.py b/tests/test_sensor_A5_10_06.py new file mode 100644 index 00000000..20194ffe --- /dev/null +++ b/tests/test_sensor_A5_10_06.py @@ -0,0 +1,50 @@ +import unittest +from custom_components.eltako.sensor import * +from mocks import HassMock +from unittest import mock +from mocks import * +from homeassistant.helpers.entity import Entity +from homeassistant.const import Platform +from custom_components.eltako.binary_sensor import EltakoBinarySensor +from eltakobus import * + +# mock update of Home Assistant +Entity.schedule_update_ha_state = mock.Mock(return_value=None) +# EltakoBinarySensor.hass.bus.fire is mocked by class HassMock + + +class TestSensor_A5_10_06(unittest.TestCase): + + msg = Regular4BSMessage (address=b'\xFF\xFF\x00\x80', data=b'\xaa\x80\x76\x0f', status=0x00) + + def create_temp_sensor(self) -> EltakoTemperatureSensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-10-06") + s = EltakoTemperatureSensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def create_target_temp_sensor(self) -> EltakoTargetTemperatureSensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-10-06") + s = EltakoTargetTemperatureSensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def test_temp_sensor(self): + ts = self.create_temp_sensor() + + self.assertEqual(ts.native_value, None) + ts.value_changed(self.msg) + + self.assertEqual(ts.native_value, 21.49019607843137) + + def test_target_temp_sensor(self): + ts = self.create_target_temp_sensor() + + self.assertEqual(ts.native_value, None) + ts.value_changed(self.msg) + + self.assertEqual(ts.native_value, 20) \ No newline at end of file diff --git a/tests/test_sensor_A5_10_12.py b/tests/test_sensor_A5_10_12.py new file mode 100644 index 00000000..47a68901 --- /dev/null +++ b/tests/test_sensor_A5_10_12.py @@ -0,0 +1,68 @@ +import unittest +from custom_components.eltako.sensor import * +from mocks import HassMock +from unittest import mock +from mocks import * +from homeassistant.helpers.entity import Entity +from homeassistant.const import Platform +from custom_components.eltako.binary_sensor import EltakoBinarySensor +from eltakobus import * + +# mock update of Home Assistant +Entity.schedule_update_ha_state = mock.Mock(return_value=None) +# EltakoBinarySensor.hass.bus.fire is mocked by class HassMock + + +class TestSensor_A5_10_12(unittest.TestCase): + + msg = Regular4BSMessage (address=b'\xFF\xFF\x00\x80', data=b'\xaa\x80\x76\x0f', status=0x00) + + def create_temp_sensor(self) -> EltakoTemperatureSensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-10-12") + s = EltakoTemperatureSensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def create_target_temp_sensor(self) -> EltakoTargetTemperatureSensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-10-12") + s = EltakoTargetTemperatureSensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + def create_humidity_sensor(self) -> EltakoHumiditySensor: + gateway = GatewayMock() + dev_id = AddressExpression.parse("FF-FF-00-80") + dev_name = "device name" + dev_eep = EEP.find("A5-10-12") + s = EltakoHumiditySensor(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep) + return s + + + def test_temp_sensor(self): + ts = self.create_temp_sensor() + + self.assertEqual(ts.native_value, None) + ts.value_changed(self.msg) + + self.assertEqual(ts.native_value, 18.88) + + + def test_target_temp_sensor(self): + ts = self.create_target_temp_sensor() + + self.assertEqual(ts.native_value, None) + ts.value_changed(self.msg) + + self.assertEqual(ts.native_value, 27) + + def test_humidity_sensor(self): + hs = self.create_humidity_sensor() + + self.assertEqual(hs.native_value, None) + hs.value_changed(self.msg) + + self.assertEqual(hs.native_value, 51.2) \ No newline at end of file diff --git a/tests/test_sensor.py b/tests/test_sensor_A5_13_01.py similarity index 67% rename from tests/test_sensor.py rename to tests/test_sensor_A5_13_01.py index 45d64cd0..5efcd185 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor_A5_13_01.py @@ -24,14 +24,6 @@ def create_weatherstation_sensor(self, description: EltakoSensorEntityDescriptio return ews - def create_window_handle_sensor(self) -> EltakoWindowHandle: - gateway = GatewayMock() - dev_id = AddressExpression.parse("51-E8-00-01") - dev_name = "dev name" - dev_eep = EEP.find("F6-10-00") - ews = EltakoWindowHandle(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep, SENSOR_DESC_WINDOWHANDLE) - - return ews def test_weatherstation_sensor(self): ews = self.create_weatherstation_sensor(SENSOR_DESC_WEATHER_STATION_ILLUMINANCE_DAWN) @@ -75,25 +67,3 @@ def test_weatherstation_sensor(self): ews.value_changed(msg) self.assertEqual(ews.native_value, 588.2352941176471) - - def test_window_handle(self): - whs = self.create_window_handle_sensor() - - whs.entity_description = SENSOR_DESC_WINDOWHANDLE - whs._attr_native_value = -1 - - msg = RPSMessage(address=b'\x05\x1e\x83\x15', status=b'\x20', data=b'\xF0', outgoing=False) - whs.value_changed(msg) - self.assertEqual(whs._attr_native_value, STATE_CLOSED) - - msg = RPSMessage(address=b'\x05\x1e\x83\x15', status=b'\x20', data=b'\xC0', outgoing=False) - whs.value_changed(msg) - self.assertEqual(whs._attr_native_value, STATE_OPEN) - - msg = RPSMessage(address=b'\x05\x1e\x83\x15', status=b'\x20', data=b'\xE0', outgoing=False) - whs.value_changed(msg) - self.assertEqual(whs._attr_native_value, STATE_OPEN) - - msg = RPSMessage(address=b'\x05\x1e\x83\x15', status=b'\x20', data=b'\xD0', outgoing=False) - whs.value_changed(msg) - self.assertEqual(whs._attr_native_value, 'tilt') \ No newline at end of file diff --git a/tests/test_sensor_F6_10_00.py b/tests/test_sensor_F6_10_00.py new file mode 100644 index 00000000..f8750e38 --- /dev/null +++ b/tests/test_sensor_F6_10_00.py @@ -0,0 +1,48 @@ +import unittest +from custom_components.eltako.sensor import * +from mocks import HassMock +from unittest import mock +from mocks import * +from homeassistant.helpers.entity import Entity +from homeassistant.const import Platform +from custom_components.eltako.binary_sensor import EltakoBinarySensor +from eltakobus import * + +# mock update of Home Assistant +Entity.schedule_update_ha_state = mock.Mock(return_value=None) +# EltakoBinarySensor.hass.bus.fire is mocked by class HassMock + + +class TestSensor(unittest.TestCase): + + def create_window_handle_sensor(self) -> EltakoWindowHandle: + gateway = GatewayMock() + dev_id = AddressExpression.parse("51-E8-00-01") + dev_name = "dev name" + dev_eep = EEP.find("F6-10-00") + ews = EltakoWindowHandle(Platform.SENSOR, gateway, dev_id, dev_name, dev_eep, SENSOR_DESC_WINDOWHANDLE) + + return ews + + + def test_window_handle(self): + whs = self.create_window_handle_sensor() + + whs.entity_description = SENSOR_DESC_WINDOWHANDLE + whs._attr_native_value = -1 + + msg = RPSMessage(address=b'\x05\x1e\x83\x15', status=b'\x20', data=b'\xF0', outgoing=False) + whs.value_changed(msg) + self.assertEqual(whs._attr_native_value, STATE_CLOSED) + + msg = RPSMessage(address=b'\x05\x1e\x83\x15', status=b'\x20', data=b'\xC0', outgoing=False) + whs.value_changed(msg) + self.assertEqual(whs._attr_native_value, STATE_OPEN) + + msg = RPSMessage(address=b'\x05\x1e\x83\x15', status=b'\x20', data=b'\xE0', outgoing=False) + whs.value_changed(msg) + self.assertEqual(whs._attr_native_value, STATE_OPEN) + + msg = RPSMessage(address=b'\x05\x1e\x83\x15', status=b'\x20', data=b'\xD0', outgoing=False) + whs.value_changed(msg) + self.assertEqual(whs._attr_native_value, 'tilt') \ No newline at end of file