From 968ae4344106c1906ce571c6379dbf49d0c79a34 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 01:55:26 +0100 Subject: [PATCH 01/25] fix --- devicesnamesconfig.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/devicesnamesconfig.py b/devicesnamesconfig.py index 28bdc47..a8bea73 100644 --- a/devicesnamesconfig.py +++ b/devicesnamesconfig.py @@ -67,6 +67,8 @@ def is_devices_file_empty(self) -> bool: def get_friendly_name(self, short_address_value) -> str: """Retrieve friendly_name.""" - return self._devices_names[short_address_value].get( - "friendly_name", f"{short_address_value}" - ) + if short_address_value in self._devices_names: + return self._devices_names[short_address_value].get( + "friendly_name", f"{short_address_value}" + ) + return str(short_address_value) From 82d1ed17ce1746448bc0dd8e353cc8ca6d887d9d Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 02:19:56 +0100 Subject: [PATCH 02/25] code improvements --- dali_mqtt_daemon.py | 118 +++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 73 deletions(-) diff --git a/dali_mqtt_daemon.py b/dali_mqtt_daemon.py index 96bb3b3..9ae435b 100644 --- a/dali_mqtt_daemon.py +++ b/dali_mqtt_daemon.py @@ -134,6 +134,42 @@ def initialize_lamps(data_object, client): len(lamps), ) + def gen_topics(lamp_object, lamp): + return [ + ( + HA_DISCOVERY_PREFIX.format(ha_prefix, lamp), + lamp_object.gen_ha_config(mqtt_base_topic), + True, + ), + ( + MQTT_BRIGHTNESS_STATE_TOPIC.format(mqtt_base_topic, lamp), + actual_level.value, + False, + ), + ( + MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC.format(mqtt_base_topic, lamp), + max_level.value, + True, + ), + ( + MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC.format(mqtt_base_topic, lamp), + min_level.value, + True, + ), + ( + MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC.format( + mqtt_base_topic, lamp + ), + physical_minimum.value, + True, + ), + ( + MQTT_STATE_TOPIC.format(mqtt_base_topic, lamp), + MQTT_PAYLOAD_ON if actual_level.value > 0 else MQTT_PAYLOAD_OFF, + False, + ), + ] + for lamp in lamps: try: short_address = address.Short(lamp) @@ -160,39 +196,8 @@ def initialize_lamps(data_object, client): data_object["all_lamps"][lamp_object.device_name] = lamp_object lamp = lamp_object.device_name - client.publish( - HA_DISCOVERY_PREFIX.format(ha_prefix, lamp), - lamp_object.gen_ha_config(mqtt_base_topic), - retain=True, - ) - client.publish( - MQTT_BRIGHTNESS_STATE_TOPIC.format(mqtt_base_topic, lamp), - actual_level.value, - retain=False, - ) - - client.publish( - MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC.format(mqtt_base_topic, lamp), - max_level.value, - retain=True, - ) - client.publish( - MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC.format(mqtt_base_topic, lamp), - min_level.value, - retain=True, - ) - client.publish( - MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC.format( - mqtt_base_topic, lamp - ), - physical_minimum.value, - retain=True, - ) - client.publish( - MQTT_STATE_TOPIC.format(mqtt_base_topic, lamp), - MQTT_PAYLOAD_ON if actual_level.value > 0 else MQTT_PAYLOAD_OFF, - retain=False, - ) + for topic, payload, retain in gen_topics(lamp_object, lamp): + client.publish(topic, payload, retain) logger.info( " - short address: %d, actual brightness level: %d (minimum: %d, max: %d, physical minimum: %d)", short_address.address, @@ -237,39 +242,8 @@ def initialize_lamps(data_object, client): data_object["all_lamps"][lamp_object.device_name] = lamp_object group_lamp = lamp_object.device_name - client.publish( - HA_DISCOVERY_PREFIX.format(ha_prefix, group_lamp), - lamp_object.gen_ha_config(mqtt_base_topic), - retain=True, - ) - client.publish( - MQTT_BRIGHTNESS_STATE_TOPIC.format(mqtt_base_topic, group_lamp), - actual_level.value, - retain=False, - ) - - client.publish( - MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC.format(mqtt_base_topic, group_lamp), - max_level.value, - retain=True, - ) - client.publish( - MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC.format(mqtt_base_topic, group_lamp), - min_level.value, - retain=True, - ) - client.publish( - MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC.format( - mqtt_base_topic, group_lamp - ), - physical_minimum.value, - retain=True, - ) - client.publish( - MQTT_STATE_TOPIC.format(mqtt_base_topic, group_lamp), - MQTT_PAYLOAD_ON if actual_level.value > 0 else MQTT_PAYLOAD_OFF, - retain=False, - ) + for topic, payload, retain in gen_topics(lamp_object, group_lamp): + client.publish(topic, payload, retain) logger.info( " - group address: %s, actual brightness level: %d (minimum: %d, max: %d, physical minimum: %d)", group_address.group, @@ -310,13 +284,13 @@ def on_message_cmd(mqtt_client, data_object, msg): retain=True, ) except DALIError as err: - logger.error("Failed to set light <%s> to %s: %s", light, "OFF", err) + logger.error("Failed to set light <%s> to OFF: %s", light, err) except KeyError: logger.error("Lamp %s doesn't exists", light) def on_message_reinitialize_lamps_cmd(mqtt_client, data_object, msg): - """Callback on MQTT scan lamps command message""" + """Callback on MQTT scan lamps command message.""" logger.debug("Reinitialize Command on %s", msg.topic) initialize_lamps(data_object, mqtt_client) @@ -345,11 +319,8 @@ def on_message_brightness_cmd(mqtt_client, data_object, msg): try: lamp_object = get_lamp_object(data_object, light) - level = None try: - level = msg.payload.decode("utf-8") - level = int(level) - lamp_object.level = level + lamp_object.level = int(msg.payload.decode("utf-8")) if lamp_object.level == 0: # 0 in DALI is turn off with fade out data_object["driver"].send(gear.Off(lamp_object.short_address)) @@ -368,7 +339,7 @@ def on_message_brightness_cmd(mqtt_client, data_object, msg): except ValueError as err: logger.error( "Can't convert <%s> to integer %d..%d: %s", - str(level), + msg.payload.decode("utf-8"), lamp_object.min_level, lamp_object.max_level, err, @@ -504,6 +475,7 @@ def delay(): def main(args): + """Main loop.""" mqttc = None config = Config(args, lambda: on_detect_changes_in_config(mqttc)) From 836693d17b9b57f711f27bb21345e15edd75b667 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 02:24:41 +0100 Subject: [PATCH 03/25] isort --- config.py | 38 ++++++++-------------------- dali_mqtt_daemon.py | 61 ++++++++++++++------------------------------- lamp.py | 20 ++++----------- 3 files changed, 35 insertions(+), 84 deletions(-) diff --git a/config.py b/config.py index 1860fa5..37b5669 100644 --- a/config.py +++ b/config.py @@ -1,36 +1,20 @@ """Configuration Object.""" import logging -import yaml + import voluptuous as vol +import yaml +from consts import (ALL_SUPPORTED_LOG_LEVELS, CONF_CONFIG, CONF_DALI_DRIVER, + CONF_DEVICES_NAMES_FILE, CONF_HA_DISCOVERY_PREFIX, + CONF_LOG_COLOR, CONF_LOG_LEVEL, CONF_MQTT_BASE_TOPIC, + CONF_MQTT_PASSWORD, CONF_MQTT_PORT, CONF_MQTT_SERVER, + CONF_MQTT_USERNAME, DALI_DRIVERS, DEFAULT_DALI_DRIVER, + DEFAULT_DEVICES_NAMES_FILE, DEFAULT_HA_DISCOVERY_PREFIX, + DEFAULT_LOG_COLOR, DEFAULT_LOG_LEVEL, + DEFAULT_MQTT_BASE_TOPIC, DEFAULT_MQTT_PORT, + DEFAULT_MQTT_SERVER, LOG_FORMAT) from watchdog.events import FileSystemEventHandler from watchdog.observers.polling import PollingObserver as Observer -from consts import ( - DEFAULT_MQTT_PORT, - DEFAULT_MQTT_SERVER, - DEFAULT_HA_DISCOVERY_PREFIX, - DEFAULT_MQTT_BASE_TOPIC, - DEFAULT_DEVICES_NAMES_FILE, - DEFAULT_LOG_LEVEL, - DEFAULT_LOG_COLOR, - DEFAULT_DALI_DRIVER, - DALI_DRIVERS, - ALL_SUPPORTED_LOG_LEVELS, - LOG_FORMAT, - CONF_CONFIG, - CONF_DALI_DRIVER, - CONF_LOG_COLOR, - CONF_LOG_LEVEL, - CONF_HA_DISCOVERY_PREFIX, - CONF_DEVICES_NAMES_FILE, - CONF_MQTT_BASE_TOPIC, - CONF_MQTT_PORT, - CONF_MQTT_SERVER, - CONF_MQTT_USERNAME, - CONF_MQTT_PASSWORD, -) - - CONF_SCHEMA = vol.Schema( { vol.Required(CONF_MQTT_SERVER, default=DEFAULT_MQTT_SERVER): str, diff --git a/dali_mqtt_daemon.py b/dali_mqtt_daemon.py index 9ae435b..0cbc783 100644 --- a/dali_mqtt_daemon.py +++ b/dali_mqtt_daemon.py @@ -10,48 +10,25 @@ import dali.address as address import dali.gear.general as gear import paho.mqtt.client as mqtt -from consts import ( - ALL_SUPPORTED_LOG_LEVELS, - CONF_CONFIG, - CONF_DALI_DRIVER, - CONF_DALI_LAMPS, - CONF_DEVICES_NAMES_FILE, - CONF_HA_DISCOVERY_PREFIX, - CONF_LOG_COLOR, - CONF_LOG_LEVEL, - CONF_MQTT_BASE_TOPIC, - CONF_MQTT_PASSWORD, - CONF_MQTT_PORT, - CONF_MQTT_SERVER, - CONF_MQTT_USERNAME, - DALI_DRIVERS, - DALI_SERVER, - DEFAULT_CONFIG_FILE, - DEFAULT_HA_DISCOVERY_PREFIX, - HA_DISCOVERY_PREFIX, - HASSEB, - LOG_FORMAT, - MAX_RETRIES, - MIN_BACKOFF_TIME, - MIN_HASSEB_FIRMWARE_VERSION, - MQTT_AVAILABLE, - MQTT_BRIGHTNESS_COMMAND_TOPIC, - MQTT_BRIGHTNESS_GET_COMMAND_TOPIC, - MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC, - MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC, - MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC, - MQTT_BRIGHTNESS_STATE_TOPIC, - MQTT_COMMAND_TOPIC, - MQTT_DALI2MQTT_STATUS, - MQTT_NOT_AVAILABLE, - MQTT_PAYLOAD_OFF, - MQTT_PAYLOAD_ON, - MQTT_SCAN_LAMPS_COMMAND_TOPIC, - MQTT_STATE_TOPIC, - RED_COLOR, - TRIDONIC, - YELLOW_COLOR, -) +from consts import (ALL_SUPPORTED_LOG_LEVELS, CONF_CONFIG, CONF_DALI_DRIVER, + CONF_DALI_LAMPS, CONF_DEVICES_NAMES_FILE, + CONF_HA_DISCOVERY_PREFIX, CONF_LOG_COLOR, CONF_LOG_LEVEL, + CONF_MQTT_BASE_TOPIC, CONF_MQTT_PASSWORD, CONF_MQTT_PORT, + CONF_MQTT_SERVER, CONF_MQTT_USERNAME, DALI_DRIVERS, + DALI_SERVER, DEFAULT_CONFIG_FILE, + DEFAULT_HA_DISCOVERY_PREFIX, HA_DISCOVERY_PREFIX, HASSEB, + LOG_FORMAT, MAX_RETRIES, MIN_BACKOFF_TIME, + MIN_HASSEB_FIRMWARE_VERSION, MQTT_AVAILABLE, + MQTT_BRIGHTNESS_COMMAND_TOPIC, + MQTT_BRIGHTNESS_GET_COMMAND_TOPIC, + MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC, + MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC, + MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC, + MQTT_BRIGHTNESS_STATE_TOPIC, MQTT_COMMAND_TOPIC, + MQTT_DALI2MQTT_STATUS, MQTT_NOT_AVAILABLE, + MQTT_PAYLOAD_OFF, MQTT_PAYLOAD_ON, + MQTT_SCAN_LAMPS_COMMAND_TOPIC, MQTT_STATE_TOPIC, RED_COLOR, + TRIDONIC, YELLOW_COLOR) from dali.command import YesNoResponse from dali.exceptions import DALIError from devicesnamesconfig import DevicesNamesConfig diff --git a/lamp.py b/lamp.py index 283e4f6..40ab632 100644 --- a/lamp.py +++ b/lamp.py @@ -3,21 +3,11 @@ import logging import dali.gear.general as gear -from consts import ( - ALL_SUPPORTED_LOG_LEVELS, - LOG_FORMAT, - MQTT_AVAILABLE, - MQTT_BRIGHTNESS_COMMAND_TOPIC, - MQTT_BRIGHTNESS_STATE_TOPIC, - MQTT_COMMAND_TOPIC, - MQTT_DALI2MQTT_STATUS, - MQTT_NOT_AVAILABLE, - MQTT_PAYLOAD_OFF, - MQTT_STATE_TOPIC, - __author__, - __email__, - __version__, -) +from consts import (ALL_SUPPORTED_LOG_LEVELS, LOG_FORMAT, MQTT_AVAILABLE, + MQTT_BRIGHTNESS_COMMAND_TOPIC, MQTT_BRIGHTNESS_STATE_TOPIC, + MQTT_COMMAND_TOPIC, MQTT_DALI2MQTT_STATUS, + MQTT_NOT_AVAILABLE, MQTT_PAYLOAD_OFF, MQTT_STATE_TOPIC, + __author__, __email__, __version__) from slugify import slugify logging.basicConfig(format=LOG_FORMAT) From c7a240487bf72f86b28ba256e6949de58ff4ecbf Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 02:25:01 +0100 Subject: [PATCH 04/25] black --- config.py | 33 ++++++++++++++++------- dali_mqtt_daemon.py | 61 +++++++++++++++++++++++++++++-------------- devicesnamesconfig.py | 5 +++- lamp.py | 26 +++++++++++------- 4 files changed, 87 insertions(+), 38 deletions(-) diff --git a/config.py b/config.py index 37b5669..42ce6bd 100644 --- a/config.py +++ b/config.py @@ -3,15 +3,30 @@ import voluptuous as vol import yaml -from consts import (ALL_SUPPORTED_LOG_LEVELS, CONF_CONFIG, CONF_DALI_DRIVER, - CONF_DEVICES_NAMES_FILE, CONF_HA_DISCOVERY_PREFIX, - CONF_LOG_COLOR, CONF_LOG_LEVEL, CONF_MQTT_BASE_TOPIC, - CONF_MQTT_PASSWORD, CONF_MQTT_PORT, CONF_MQTT_SERVER, - CONF_MQTT_USERNAME, DALI_DRIVERS, DEFAULT_DALI_DRIVER, - DEFAULT_DEVICES_NAMES_FILE, DEFAULT_HA_DISCOVERY_PREFIX, - DEFAULT_LOG_COLOR, DEFAULT_LOG_LEVEL, - DEFAULT_MQTT_BASE_TOPIC, DEFAULT_MQTT_PORT, - DEFAULT_MQTT_SERVER, LOG_FORMAT) +from consts import ( + ALL_SUPPORTED_LOG_LEVELS, + CONF_CONFIG, + CONF_DALI_DRIVER, + CONF_DEVICES_NAMES_FILE, + CONF_HA_DISCOVERY_PREFIX, + CONF_LOG_COLOR, + CONF_LOG_LEVEL, + CONF_MQTT_BASE_TOPIC, + CONF_MQTT_PASSWORD, + CONF_MQTT_PORT, + CONF_MQTT_SERVER, + CONF_MQTT_USERNAME, + DALI_DRIVERS, + DEFAULT_DALI_DRIVER, + DEFAULT_DEVICES_NAMES_FILE, + DEFAULT_HA_DISCOVERY_PREFIX, + DEFAULT_LOG_COLOR, + DEFAULT_LOG_LEVEL, + DEFAULT_MQTT_BASE_TOPIC, + DEFAULT_MQTT_PORT, + DEFAULT_MQTT_SERVER, + LOG_FORMAT, +) from watchdog.events import FileSystemEventHandler from watchdog.observers.polling import PollingObserver as Observer diff --git a/dali_mqtt_daemon.py b/dali_mqtt_daemon.py index 0cbc783..9ae435b 100644 --- a/dali_mqtt_daemon.py +++ b/dali_mqtt_daemon.py @@ -10,25 +10,48 @@ import dali.address as address import dali.gear.general as gear import paho.mqtt.client as mqtt -from consts import (ALL_SUPPORTED_LOG_LEVELS, CONF_CONFIG, CONF_DALI_DRIVER, - CONF_DALI_LAMPS, CONF_DEVICES_NAMES_FILE, - CONF_HA_DISCOVERY_PREFIX, CONF_LOG_COLOR, CONF_LOG_LEVEL, - CONF_MQTT_BASE_TOPIC, CONF_MQTT_PASSWORD, CONF_MQTT_PORT, - CONF_MQTT_SERVER, CONF_MQTT_USERNAME, DALI_DRIVERS, - DALI_SERVER, DEFAULT_CONFIG_FILE, - DEFAULT_HA_DISCOVERY_PREFIX, HA_DISCOVERY_PREFIX, HASSEB, - LOG_FORMAT, MAX_RETRIES, MIN_BACKOFF_TIME, - MIN_HASSEB_FIRMWARE_VERSION, MQTT_AVAILABLE, - MQTT_BRIGHTNESS_COMMAND_TOPIC, - MQTT_BRIGHTNESS_GET_COMMAND_TOPIC, - MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC, - MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC, - MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC, - MQTT_BRIGHTNESS_STATE_TOPIC, MQTT_COMMAND_TOPIC, - MQTT_DALI2MQTT_STATUS, MQTT_NOT_AVAILABLE, - MQTT_PAYLOAD_OFF, MQTT_PAYLOAD_ON, - MQTT_SCAN_LAMPS_COMMAND_TOPIC, MQTT_STATE_TOPIC, RED_COLOR, - TRIDONIC, YELLOW_COLOR) +from consts import ( + ALL_SUPPORTED_LOG_LEVELS, + CONF_CONFIG, + CONF_DALI_DRIVER, + CONF_DALI_LAMPS, + CONF_DEVICES_NAMES_FILE, + CONF_HA_DISCOVERY_PREFIX, + CONF_LOG_COLOR, + CONF_LOG_LEVEL, + CONF_MQTT_BASE_TOPIC, + CONF_MQTT_PASSWORD, + CONF_MQTT_PORT, + CONF_MQTT_SERVER, + CONF_MQTT_USERNAME, + DALI_DRIVERS, + DALI_SERVER, + DEFAULT_CONFIG_FILE, + DEFAULT_HA_DISCOVERY_PREFIX, + HA_DISCOVERY_PREFIX, + HASSEB, + LOG_FORMAT, + MAX_RETRIES, + MIN_BACKOFF_TIME, + MIN_HASSEB_FIRMWARE_VERSION, + MQTT_AVAILABLE, + MQTT_BRIGHTNESS_COMMAND_TOPIC, + MQTT_BRIGHTNESS_GET_COMMAND_TOPIC, + MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC, + MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC, + MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC, + MQTT_BRIGHTNESS_STATE_TOPIC, + MQTT_COMMAND_TOPIC, + MQTT_DALI2MQTT_STATUS, + MQTT_NOT_AVAILABLE, + MQTT_PAYLOAD_OFF, + MQTT_PAYLOAD_ON, + MQTT_SCAN_LAMPS_COMMAND_TOPIC, + MQTT_STATE_TOPIC, + RED_COLOR, + TRIDONIC, + YELLOW_COLOR, +) from dali.command import YesNoResponse from dali.exceptions import DALIError from devicesnamesconfig import DevicesNamesConfig diff --git a/devicesnamesconfig.py b/devicesnamesconfig.py index a8bea73..0e38a7a 100644 --- a/devicesnamesconfig.py +++ b/devicesnamesconfig.py @@ -41,7 +41,10 @@ def load_devices_names_file(self): logger.error("In devices file %s: %s", self._path, error) raise DevicesNamesConfigLoadError() except Exception as err: - logger.error("Could not load device names config <%s>, a new one will be created after successfull start", self._path) + logger.error( + "Could not load device names config <%s>, a new one will be created after successfull start", + self._path, + ) def save_devices_names_file(self, all_lamps): """Save configuration back to yaml file.""" diff --git a/lamp.py b/lamp.py index 40ab632..920838c 100644 --- a/lamp.py +++ b/lamp.py @@ -3,11 +3,21 @@ import logging import dali.gear.general as gear -from consts import (ALL_SUPPORTED_LOG_LEVELS, LOG_FORMAT, MQTT_AVAILABLE, - MQTT_BRIGHTNESS_COMMAND_TOPIC, MQTT_BRIGHTNESS_STATE_TOPIC, - MQTT_COMMAND_TOPIC, MQTT_DALI2MQTT_STATUS, - MQTT_NOT_AVAILABLE, MQTT_PAYLOAD_OFF, MQTT_STATE_TOPIC, - __author__, __email__, __version__) +from consts import ( + ALL_SUPPORTED_LOG_LEVELS, + LOG_FORMAT, + MQTT_AVAILABLE, + MQTT_BRIGHTNESS_COMMAND_TOPIC, + MQTT_BRIGHTNESS_STATE_TOPIC, + MQTT_COMMAND_TOPIC, + MQTT_DALI2MQTT_STATUS, + MQTT_NOT_AVAILABLE, + MQTT_PAYLOAD_OFF, + MQTT_STATE_TOPIC, + __author__, + __email__, + __version__, +) from slugify import slugify logging.basicConfig(format=LOG_FORMAT) @@ -46,10 +56,8 @@ def gen_ha_config(self, mqtt_base_topic): "obj_id": f"dali_light_{self.device_name}", "uniq_id": f"{type(self.driver).__name__}_{self.short_address}", "stat_t": MQTT_STATE_TOPIC.format(mqtt_base_topic, self.device_name), - "cmd_t": MQTT_COMMAND_TOPIC.format( - mqtt_base_topic, self.device_name - ), - "pl_off": MQTT_PAYLOAD_OFF.decode('utf-8'), + "cmd_t": MQTT_COMMAND_TOPIC.format(mqtt_base_topic, self.device_name), + "pl_off": MQTT_PAYLOAD_OFF.decode("utf-8"), "bri_stat_t": MQTT_BRIGHTNESS_STATE_TOPIC.format( mqtt_base_topic, self.device_name ), From c5057190467a5d7679cdfc6eb504a675e43350b2 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 12:21:46 +0100 Subject: [PATCH 05/25] implement backoff on mqtt broker errors --- consts.py | 1 + dali_mqtt_daemon.py | 43 ++++++++++++++++++++----------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/consts.py b/consts.py index 04b88b1..dcb1ed0 100644 --- a/consts.py +++ b/consts.py @@ -52,6 +52,7 @@ MIN_HASSEB_FIRMWARE_VERSION = 2.3 MIN_BACKOFF_TIME = 2 +MAX_BACKOFF_TIME = 10 MAX_RETRIES = 10 ALL_SUPPORTED_LOG_LEVELS = { diff --git a/dali_mqtt_daemon.py b/dali_mqtt_daemon.py index 9ae435b..7d7cba9 100644 --- a/dali_mqtt_daemon.py +++ b/dali_mqtt_daemon.py @@ -6,6 +6,7 @@ import random import re import time +import os import dali.address as address import dali.gear.general as gear @@ -33,6 +34,7 @@ LOG_FORMAT, MAX_RETRIES, MIN_BACKOFF_TIME, + MAX_BACKOFF_TIME, MIN_HASSEB_FIRMWARE_VERSION, MQTT_AVAILABLE, MQTT_BRIGHTNESS_COMMAND_TOPIC, @@ -59,7 +61,7 @@ from config import Config -logging.basicConfig(format=LOG_FORMAT) +logging.basicConfig(format=LOG_FORMAT, level=os.environ.get("LOGLEVEL", "INFO")) logger = logging.getLogger(__name__) @@ -468,12 +470,6 @@ def create_mqtt_client( mqttc.connect(mqtt_server, mqtt_port, 60) return mqttc - -def delay(): - """Generate a random backoff time.""" - return MIN_BACKOFF_TIME + random.randint(0, 1000) / 1000.0 - - def main(args): """Main loop.""" mqttc = None @@ -517,24 +513,25 @@ def main(args): dali_driver = DaliServer("localhost", 55825) - should_backoff = True retries = 0 - run = True - while run: - mqttc = create_mqtt_client( - dali_driver, - *config.mqtt_conf, - devices_names_config, - config.ha_discovery_prefix, - config.log_level, - ) - mqttc.loop_forever() - if should_backoff: + while True: + try: + mqttc = create_mqtt_client( + dali_driver, + *config.mqtt_conf, + devices_names_config, + config.ha_discovery_prefix, + config.log_level, + ) + mqttc.loop_forever() + retries = 0 #if we reach here, it means we where already connected successfully + except Exception as e: + logger.error("%s: %s", type(e).__name__, e) if retries == MAX_RETRIES: - run = False - time.sleep(delay()) - retries += 1 # TODO reset on successfull connection - + logger.error("Maximum retries of %d reached, exiting...", retries) + break + time.sleep(random.randint(MIN_BACKOFF_TIME, MAX_BACKOFF_TIME)) + retries += 1 if __name__ == "__main__": parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS) From 4e55d9bde633c189796c7c9e1371f7b06bf21e35 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 12:35:55 +0100 Subject: [PATCH 06/25] refactor initialize_lamps --- dali_mqtt_daemon.py | 42 ++++++++++++++++-------------------------- lamp.py | 3 +++ 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/dali_mqtt_daemon.py b/dali_mqtt_daemon.py index 7d7cba9..2cdba8d 100644 --- a/dali_mqtt_daemon.py +++ b/dali_mqtt_daemon.py @@ -145,29 +145,29 @@ def gen_topics(lamp_object, lamp): ), ( MQTT_BRIGHTNESS_STATE_TOPIC.format(mqtt_base_topic, lamp), - actual_level.value, + lamp_object.level, False, ), ( MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC.format(mqtt_base_topic, lamp), - max_level.value, + lamp_object.max_level, True, ), ( MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC.format(mqtt_base_topic, lamp), - min_level.value, + lamp_object.min_level, True, ), ( MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC.format( mqtt_base_topic, lamp ), - physical_minimum.value, + lamp_object.min_physical_level, True, ), ( MQTT_STATE_TOPIC.format(mqtt_base_topic, lamp), - MQTT_PAYLOAD_ON if actual_level.value > 0 else MQTT_PAYLOAD_OFF, + MQTT_PAYLOAD_ON if lamp_object.level > 0 else MQTT_PAYLOAD_OFF, False, ), ] @@ -182,7 +182,6 @@ def gen_topics(lamp_object, lamp): min_level = driver_object.send(gear.QueryMinLevel(short_address)) max_level = driver_object.send(gear.QueryMaxLevel(short_address)) device_name = devices_names_config.get_friendly_name(short_address.address) - lamp = device_name lamp_object = Lamp( log_level, @@ -196,18 +195,12 @@ def gen_topics(lamp_object, lamp): ) data_object["all_lamps"][lamp_object.device_name] = lamp_object - lamp = lamp_object.device_name - for topic, payload, retain in gen_topics(lamp_object, lamp): + for topic, payload, retain in gen_topics( + lamp_object, lamp_object.device_name + ): client.publish(topic, payload, retain) - logger.info( - " - short address: %d, actual brightness level: %d (minimum: %d, max: %d, physical minimum: %d)", - short_address.address, - actual_level.value, - min_level.value, - max_level.value, - physical_minimum.value, - ) + logger.info(lamp_object) except DALIError as err: logger.error("While initializing lamp<%s>: %s", lamp, err) @@ -246,14 +239,7 @@ def gen_topics(lamp_object, lamp): for topic, payload, retain in gen_topics(lamp_object, group_lamp): client.publish(topic, payload, retain) - logger.info( - " - group address: %s, actual brightness level: %d (minimum: %d, max: %d, physical minimum: %d)", - group_address.group, - actual_level.value, - min_level.value, - max_level.value, - physical_minimum.value, - ) + logger.info(lamp_object) except DALIError as err: logger.error("Error while initializing group <%s>: %s", group_lamp, err) @@ -470,6 +456,7 @@ def create_mqtt_client( mqttc.connect(mqtt_server, mqtt_port, 60) return mqttc + def main(args): """Main loop.""" mqttc = None @@ -524,14 +511,17 @@ def main(args): config.log_level, ) mqttc.loop_forever() - retries = 0 #if we reach here, it means we where already connected successfully + retries = ( + 0 # if we reach here, it means we where already connected successfully + ) except Exception as e: logger.error("%s: %s", type(e).__name__, e) if retries == MAX_RETRIES: logger.error("Maximum retries of %d reached, exiting...", retries) break time.sleep(random.randint(MIN_BACKOFF_TIME, MAX_BACKOFF_TIME)) - retries += 1 + retries += 1 + if __name__ == "__main__": parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS) diff --git a/lamp.py b/lamp.py index 920838c..e83fd3a 100644 --- a/lamp.py +++ b/lamp.py @@ -93,3 +93,6 @@ def level(self, value): logger.debug( "Set lamp <%s> brightness level to %s", self.friendly_name, self.level ) + + def __str__(self): + return f"{self.device_name} - address: {self.short_address.address}, actual brightness level: {self.level} (minimum: {self.min_level}, max: {self.max_level}, physical minimum: {self.min_physical_level})" From 5789017b607174ea7087ed8f4567d8cf4d994914 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 12:52:05 +0100 Subject: [PATCH 07/25] more refactor initialize_lamps --- dali_mqtt_daemon.py | 70 +++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 50 deletions(-) diff --git a/dali_mqtt_daemon.py b/dali_mqtt_daemon.py index 2cdba8d..e6000b9 100644 --- a/dali_mqtt_daemon.py +++ b/dali_mqtt_daemon.py @@ -172,26 +172,19 @@ def gen_topics(lamp_object, lamp): ), ] - for lamp in lamps: + def create_mqtt_lamp(address, name): try: - short_address = address.Short(lamp) - actual_level = driver_object.send(gear.QueryActualLevel(short_address)) - physical_minimum = driver_object.send( - gear.QueryPhysicalMinimum(short_address) - ) - min_level = driver_object.send(gear.QueryMinLevel(short_address)) - max_level = driver_object.send(gear.QueryMaxLevel(short_address)) - device_name = devices_names_config.get_friendly_name(short_address.address) - lamp_object = Lamp( log_level, driver_object, - device_name, - short_address, - physical_minimum.value, - min_level.value, - actual_level.value, - max_level.value, + name, + address, + min_physical_level=driver_object.send( + gear.QueryPhysicalMinimum(address) + ).value, + min_level=driver_object.send(gear.QueryMinLevel(address)).value, + max_level=driver_object.send(gear.QueryMaxLevel(address)).value, + level=driver_object.send(gear.QueryActualLevel(address)).value, ) data_object["all_lamps"][lamp_object.device_name] = lamp_object @@ -203,46 +196,23 @@ def gen_topics(lamp_object, lamp): logger.info(lamp_object) except DALIError as err: - logger.error("While initializing lamp<%s>: %s", lamp, err) + logger.error("While initializing <%s> @ %s: %s", name, address, err) + + for lamp in lamps: + short_address = address.Short(lamp) + + create_mqtt_lamp( + short_address, + devices_names_config.get_friendly_name(short_address.address), + ) groups = scan_groups(driver_object, lamps) for group in groups: logger.debug("Publishing group %d", group) - try: - logger.debug("Group %s" % group) - group_address = address.Group(int(group)) - actual_level = driver_object.send(gear.QueryActualLevel(group_address)) - physical_minimum = driver_object.send( - gear.QueryPhysicalMinimum(group_address) - ) - min_level = driver_object.send(gear.QueryMinLevel(group_address)) - max_level = driver_object.send(gear.QueryMaxLevel(group_address)) - device_name = f"group_{group}" + group_address = address.Group(int(group)) - group_lamp = device_name - logger.debug("Group Name: %s", group_lamp) - - lamp_object = Lamp( - log_level, - driver_object, - device_name, - group_address, - physical_minimum.value, - min_level.value, - actual_level.value, - max_level.value, - ) - - data_object["all_lamps"][lamp_object.device_name] = lamp_object - group_lamp = lamp_object.device_name - - for topic, payload, retain in gen_topics(lamp_object, group_lamp): - client.publish(topic, payload, retain) - logger.info(lamp_object) - - except DALIError as err: - logger.error("Error while initializing group <%s>: %s", group_lamp, err) + create_mqtt_lamp(group_address, f"group_{group}") if devices_names_config.is_devices_file_empty(): devices_names_config.save_devices_names_file(data_object["all_lamps"]) From 894848e44aea30e3be5f565f931ea521afa4057e Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 12:56:13 +0100 Subject: [PATCH 08/25] more refactor initialize_lamps --- dali_mqtt_daemon.py | 77 ++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/dali_mqtt_daemon.py b/dali_mqtt_daemon.py index e6000b9..683230b 100644 --- a/dali_mqtt_daemon.py +++ b/dali_mqtt_daemon.py @@ -136,42 +136,6 @@ def initialize_lamps(data_object, client): len(lamps), ) - def gen_topics(lamp_object, lamp): - return [ - ( - HA_DISCOVERY_PREFIX.format(ha_prefix, lamp), - lamp_object.gen_ha_config(mqtt_base_topic), - True, - ), - ( - MQTT_BRIGHTNESS_STATE_TOPIC.format(mqtt_base_topic, lamp), - lamp_object.level, - False, - ), - ( - MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC.format(mqtt_base_topic, lamp), - lamp_object.max_level, - True, - ), - ( - MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC.format(mqtt_base_topic, lamp), - lamp_object.min_level, - True, - ), - ( - MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC.format( - mqtt_base_topic, lamp - ), - lamp_object.min_physical_level, - True, - ), - ( - MQTT_STATE_TOPIC.format(mqtt_base_topic, lamp), - MQTT_PAYLOAD_ON if lamp_object.level > 0 else MQTT_PAYLOAD_OFF, - False, - ), - ] - def create_mqtt_lamp(address, name): try: lamp_object = Lamp( @@ -187,12 +151,45 @@ def create_mqtt_lamp(address, name): level=driver_object.send(gear.QueryActualLevel(address)).value, ) - data_object["all_lamps"][lamp_object.device_name] = lamp_object + data_object["all_lamps"][name] = lamp_object - for topic, payload, retain in gen_topics( - lamp_object, lamp_object.device_name - ): + mqtt_data = [ + ( + HA_DISCOVERY_PREFIX.format(ha_prefix, name), + lamp_object.gen_ha_config(mqtt_base_topic), + True, + ), + ( + MQTT_BRIGHTNESS_STATE_TOPIC.format(mqtt_base_topic, name), + lamp_object.level, + False, + ), + ( + MQTT_BRIGHTNESS_MAX_LEVEL_TOPIC.format(mqtt_base_topic, name), + lamp_object.max_level, + True, + ), + ( + MQTT_BRIGHTNESS_MIN_LEVEL_TOPIC.format(mqtt_base_topic, name), + lamp_object.min_level, + True, + ), + ( + MQTT_BRIGHTNESS_PHYSICAL_MINIMUM_LEVEL_TOPIC.format( + mqtt_base_topic, name + ), + lamp_object.min_physical_level, + True, + ), + ( + MQTT_STATE_TOPIC.format(mqtt_base_topic, name), + MQTT_PAYLOAD_ON if lamp_object.level > 0 else MQTT_PAYLOAD_OFF, + False, + ), + ] + for topic, payload, retain in mqtt_data: client.publish(topic, payload, retain) + logger.info(lamp_object) except DALIError as err: From 3e5fdc569b27657621f8205193dc0b22d93c9eb4 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 15:15:22 +0100 Subject: [PATCH 09/25] tests added --- tests/test_lamp.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/test_lamp.py diff --git a/tests/test_lamp.py b/tests/test_lamp.py new file mode 100644 index 0000000..570c8c1 --- /dev/null +++ b/tests/test_lamp.py @@ -0,0 +1,72 @@ +"""Tests for lamp.""" + +from lamp import Lamp +from consts import __version__ +from unittest import mock +from dali.address import Short +import pytest +import json +from slugify import slugify + +MIN_BRIGHTNESS = 2 +MAX_BRIGHTNESS = 250 + + +@pytest.fixture +def fake_driver(): + drive = mock.Mock() + drive.send = lambda x: 0x00 + yield drive + + +@pytest.fixture +def fake_address(): + address = mock.Mock() + address.address = 1 + + address.__repr__ = lambda: "1" + +def test_ha_config(fake_driver, fake_address): + + friendly_name = "my lamp" + addr_number = 1 + addr = Short(1) + + lamp1 = Lamp( + log_level="debug", + driver=fake_driver, + friendly_name=friendly_name, + short_address=addr, + min_physical_level=2, + min_level=MIN_BRIGHTNESS, + level=100, + max_level=MAX_BRIGHTNESS, + ) + + assert lamp1.device_name == slugify(friendly_name) + assert lamp1.short_address.address == addr_number + + assert str(lamp1) == f'my-lamp - address: {addr_number}, actual brightness level: 100 (minimum: {MIN_BRIGHTNESS}, max: {MAX_BRIGHTNESS}, physical minimum: 2)' + + assert json.loads(lamp1.gen_ha_config("test")) == { + "name": friendly_name, + "obj_id": "dali_light_my-lamp", + "uniq_id": f"Mock_{addr}", + "stat_t": "test/my-lamp/light/status", + "cmd_t": "test/my-lamp/light/switch", + "pl_off": "OFF", + "bri_stat_t": "test/my-lamp/light/brightness/status", + "bri_cmd_t": "test/my-lamp/light/brightness/set", + "bri_scl": MAX_BRIGHTNESS, + "on_cmd_type": "brightness", + "avty_t": "test/status", + "pl_avail": "online", + "pl_not_avail": "offline", + "device": { + "ids": "dali2mqtt", + "name": "DALI Lights", + "sw": f"dali2mqtt {__version__}", + "mdl": "Mock", + "mf": "dali2mqtt", + }, + } From 1467412fb3ef374a8e4c465162b92a296aa76bfd Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 15:19:31 +0100 Subject: [PATCH 10/25] add tests --- .github/workflows/pytest.yml | 31 ++++++++++++++++++ dali_mqtt_daemon.py | 7 ++-- tests/__init__.py | 5 ++- tests/test_hasseb.py | 62 +++++++++++++++++++++++++++--------- 4 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..ee17d9a --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,31 @@ +name: Tests + +# Run this workflow every time a new commit pushed to your repository +on: push + +jobs: + + unit_tests: + name: Tests + runs-on: ubuntu-latest + + steps: + # Checks out a copy of your repository on the ubuntu-latest machine + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Test with pytest + timeout-minutes: 3 + run: | + pytest -vvv diff --git a/dali_mqtt_daemon.py b/dali_mqtt_daemon.py index 683230b..02eb313 100644 --- a/dali_mqtt_daemon.py +++ b/dali_mqtt_daemon.py @@ -468,7 +468,7 @@ def main(args): dali_driver = DaliServer("localhost", 55825) retries = 0 - while True: + while retries < MAX_RETRIES: try: mqttc = create_mqtt_client( dali_driver, @@ -483,12 +483,11 @@ def main(args): ) except Exception as e: logger.error("%s: %s", type(e).__name__, e) - if retries == MAX_RETRIES: - logger.error("Maximum retries of %d reached, exiting...", retries) - break time.sleep(random.randint(MIN_BACKOFF_TIME, MAX_BACKOFF_TIME)) retries += 1 + logger.error("Maximum retries of %d reached, exiting...", retries) + if __name__ == "__main__": parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS) diff --git a/tests/__init__.py b/tests/__init__.py index 7b49369..cc2c79b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,7 +1,6 @@ import os import sys + PROJECT_PATH = os.getcwd() -SOURCE_PATH = os.path.join( - PROJECT_PATH,"." -) +SOURCE_PATH = os.path.join(PROJECT_PATH, ".") sys.path.append(SOURCE_PATH) diff --git a/tests/test_hasseb.py b/tests/test_hasseb.py index 9865cec..d9ac826 100644 --- a/tests/test_hasseb.py +++ b/tests/test_hasseb.py @@ -11,8 +11,10 @@ DEFAULT_LOG_LEVEL, DEFAULT_LOG_COLOR, DEFAULT_HA_DISCOVERY_PREFIX, + MAX_RETRIES, ) + @pytest.fixture def args(): mock_args = mock.Mock() @@ -25,24 +27,54 @@ def args(): mock_args.log_color = DEFAULT_LOG_COLOR return mock_args + @pytest.fixture def config(): - return {"config": "config.yaml", - "dali_driver": "hasseb", - "dali_lamps": 2, - "mqtt_server": "localhost", - "mqtt_port": 1883, - "mqtt_base_topic": "dali2mqtt", - "ha_discovery_prefix": "homeassistant", - "log_level": "info", - "log_color": False, + return { + "config": "config.yaml", + "dali_driver": "hasseb", + "dali_lamps": 2, + "mqtt_server": "localhost", + "mqtt_port": 1883, + "mqtt_base_topic": "dali2mqtt", + "ha_discovery_prefix": "homeassistant", + "log_level": "info", + "log_color": False, } -def test_main(args, config): - with mock.patch('dali_mqtt_daemon.create_mqtt_client', return_value=mock.Mock()) as mock_mqtt_client: - with mock.patch("dali_mqtt_daemon.delay", return_value=0): - with mock.patch('yaml.load', return_value={}) as mock_config_file: - mock_mqtt_client.loop_forever = mock.Mock() +@pytest.fixture +def fake_data_object(): + driver = mock.Mock() + driver.send = lambda x: 0x00 + + return { + "driver": driver, + "base_topic": "test", + "ha_prefix": "hass", + "log_level": "debug", + "devices_names_config": None + } + +@pytest.fixture +def fake_mqttc(): + mqttc = mock.Mock() + def loop_forever(): + import sys + raise Exception() + mqttc.loop_forever = loop_forever + return mqttc + + +def test_main(args, config, fake_mqttc, caplog): + """Test main loop.""" + with mock.patch( + "dali_mqtt_daemon.create_mqtt_client", return_value=fake_mqttc + ) as mock_mqtt_client: + with mock.patch("yaml.safe_load", return_value={}) as mock_config_file: + with mock.patch("time.sleep", return_value=None) as sleep: main(args) mock_config_file.assert_called() - assert mock_mqtt_client.call_count == 11 \ No newline at end of file + assert sleep.call_count == MAX_RETRIES + assert mock_mqtt_client.call_count == MAX_RETRIES + assert any("Could not load a configuration from config.yaml" in rec.message for rec in caplog.records) + assert any("Maximum retries of 10 reached, exiting" in rec.message for rec in caplog.records) From 8bd713a11d5f2d1c1d245bb8bd275efe3ac98ab0 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 15:26:46 +0100 Subject: [PATCH 11/25] don't rely in the existence of config.yaml --- tests/test_hasseb.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/test_hasseb.py b/tests/test_hasseb.py index d9ac826..cf8f635 100644 --- a/tests/test_hasseb.py +++ b/tests/test_hasseb.py @@ -70,11 +70,9 @@ def test_main(args, config, fake_mqttc, caplog): with mock.patch( "dali_mqtt_daemon.create_mqtt_client", return_value=fake_mqttc ) as mock_mqtt_client: - with mock.patch("yaml.safe_load", return_value={}) as mock_config_file: - with mock.patch("time.sleep", return_value=None) as sleep: - main(args) - mock_config_file.assert_called() - assert sleep.call_count == MAX_RETRIES - assert mock_mqtt_client.call_count == MAX_RETRIES - assert any("Could not load a configuration from config.yaml" in rec.message for rec in caplog.records) - assert any("Maximum retries of 10 reached, exiting" in rec.message for rec in caplog.records) + with mock.patch("time.sleep", return_value=None) as sleep: + main(args) + assert sleep.call_count == MAX_RETRIES + assert mock_mqtt_client.call_count == MAX_RETRIES + assert any("Could not load a configuration from config.yaml" in rec.message for rec in caplog.records) + assert any("Maximum retries of 10 reached, exiting" in rec.message for rec in caplog.records) From c07e6c7d9b15fc76c6655afdb8e42e9c57ecb2dd Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 15:29:30 +0100 Subject: [PATCH 12/25] don't rely in the existence of config.yaml --- tests/test_hasseb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_hasseb.py b/tests/test_hasseb.py index cf8f635..868d61c 100644 --- a/tests/test_hasseb.py +++ b/tests/test_hasseb.py @@ -74,5 +74,4 @@ def test_main(args, config, fake_mqttc, caplog): main(args) assert sleep.call_count == MAX_RETRIES assert mock_mqtt_client.call_count == MAX_RETRIES - assert any("Could not load a configuration from config.yaml" in rec.message for rec in caplog.records) assert any("Maximum retries of 10 reached, exiting" in rec.message for rec in caplog.records) From fe7af9e284ef07cc6dae0c30d60bb68a5db86504 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 18:17:01 +0100 Subject: [PATCH 13/25] new file structure --- config.py => dali2mqtt/config.py | 0 consts.py => dali2mqtt/consts.py | 0 dali_mqtt_daemon.py => dali2mqtt/dali2mqtt.py | 2 +- devicesnamesconfig.py => dali2mqtt/devicesnamesconfig.py | 0 lamp.py => dali2mqtt/lamp.py | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename config.py => dali2mqtt/config.py (100%) rename consts.py => dali2mqtt/consts.py (100%) rename dali_mqtt_daemon.py => dali2mqtt/dali2mqtt.py (100%) rename devicesnamesconfig.py => dali2mqtt/devicesnamesconfig.py (100%) rename lamp.py => dali2mqtt/lamp.py (100%) diff --git a/config.py b/dali2mqtt/config.py similarity index 100% rename from config.py rename to dali2mqtt/config.py diff --git a/consts.py b/dali2mqtt/consts.py similarity index 100% rename from consts.py rename to dali2mqtt/consts.py diff --git a/dali_mqtt_daemon.py b/dali2mqtt/dali2mqtt.py similarity index 100% rename from dali_mqtt_daemon.py rename to dali2mqtt/dali2mqtt.py index 02eb313..e9fae1c 100644 --- a/dali_mqtt_daemon.py +++ b/dali2mqtt/dali2mqtt.py @@ -57,8 +57,8 @@ from dali.command import YesNoResponse from dali.exceptions import DALIError from devicesnamesconfig import DevicesNamesConfig -from lamp import Lamp +from lamp import Lamp from config import Config logging.basicConfig(format=LOG_FORMAT, level=os.environ.get("LOGLEVEL", "INFO")) diff --git a/devicesnamesconfig.py b/dali2mqtt/devicesnamesconfig.py similarity index 100% rename from devicesnamesconfig.py rename to dali2mqtt/devicesnamesconfig.py diff --git a/lamp.py b/dali2mqtt/lamp.py similarity index 100% rename from lamp.py rename to dali2mqtt/lamp.py From 1b49f4ed6c1e6d8a4f87c76577d04bb62d34de51 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 18:22:28 +0100 Subject: [PATCH 14/25] adjust tests --- dali2mqtt/__init__.py | 0 dali2mqtt/config.py | 2 +- dali2mqtt/dali2mqtt.py | 16 +++++++++------- dali2mqtt/devicesnamesconfig.py | 2 +- dali2mqtt/lamp.py | 2 +- tests/test_hasseb.py | 6 +++--- tests/test_lamp.py | 4 ++-- 7 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 dali2mqtt/__init__.py diff --git a/dali2mqtt/__init__.py b/dali2mqtt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dali2mqtt/config.py b/dali2mqtt/config.py index 42ce6bd..d044a59 100644 --- a/dali2mqtt/config.py +++ b/dali2mqtt/config.py @@ -3,7 +3,7 @@ import voluptuous as vol import yaml -from consts import ( +from dali2mqtt.consts import ( ALL_SUPPORTED_LOG_LEVELS, CONF_CONFIG, CONF_DALI_DRIVER, diff --git a/dali2mqtt/dali2mqtt.py b/dali2mqtt/dali2mqtt.py index e9fae1c..10972c5 100644 --- a/dali2mqtt/dali2mqtt.py +++ b/dali2mqtt/dali2mqtt.py @@ -8,10 +8,17 @@ import time import os +import paho.mqtt.client as mqtt + import dali.address as address import dali.gear.general as gear -import paho.mqtt.client as mqtt -from consts import ( +from dali.command import YesNoResponse +from dali.exceptions import DALIError + +from dali2mqtt.devicesnamesconfig import DevicesNamesConfig +from dali2mqtt.lamp import Lamp +from dali2mqtt.config import Config +from dali2mqtt.consts import ( ALL_SUPPORTED_LOG_LEVELS, CONF_CONFIG, CONF_DALI_DRIVER, @@ -54,12 +61,7 @@ TRIDONIC, YELLOW_COLOR, ) -from dali.command import YesNoResponse -from dali.exceptions import DALIError -from devicesnamesconfig import DevicesNamesConfig -from lamp import Lamp -from config import Config logging.basicConfig(format=LOG_FORMAT, level=os.environ.get("LOGLEVEL", "INFO")) logger = logging.getLogger(__name__) diff --git a/dali2mqtt/devicesnamesconfig.py b/dali2mqtt/devicesnamesconfig.py index 0e38a7a..4b78ac9 100644 --- a/dali2mqtt/devicesnamesconfig.py +++ b/dali2mqtt/devicesnamesconfig.py @@ -2,7 +2,7 @@ import logging import yaml -from consts import ALL_SUPPORTED_LOG_LEVELS, LOG_FORMAT +from dali2mqtt.consts import ALL_SUPPORTED_LOG_LEVELS, LOG_FORMAT logging.basicConfig(format=LOG_FORMAT) logger = logging.getLogger(__name__) diff --git a/dali2mqtt/lamp.py b/dali2mqtt/lamp.py index e83fd3a..c26b756 100644 --- a/dali2mqtt/lamp.py +++ b/dali2mqtt/lamp.py @@ -3,7 +3,7 @@ import logging import dali.gear.general as gear -from consts import ( +from dali2mqtt.consts import ( ALL_SUPPORTED_LOG_LEVELS, LOG_FORMAT, MQTT_AVAILABLE, diff --git a/tests/test_hasseb.py b/tests/test_hasseb.py index 868d61c..f59cfc2 100644 --- a/tests/test_hasseb.py +++ b/tests/test_hasseb.py @@ -1,10 +1,10 @@ """Tests based on hasseb driver.""" -from dali_mqtt_daemon import main +from dali2mqtt.dali2mqtt import main from unittest import mock import pytest -from consts import ( +from dali2mqtt.consts import ( DEFAULT_CONFIG_FILE, DEFAULT_MQTT_SERVER, DEFAULT_MQTT_PORT, @@ -68,7 +68,7 @@ def loop_forever(): def test_main(args, config, fake_mqttc, caplog): """Test main loop.""" with mock.patch( - "dali_mqtt_daemon.create_mqtt_client", return_value=fake_mqttc + "dali2mqtt.dali2mqtt.create_mqtt_client", return_value=fake_mqttc ) as mock_mqtt_client: with mock.patch("time.sleep", return_value=None) as sleep: main(args) diff --git a/tests/test_lamp.py b/tests/test_lamp.py index 570c8c1..78260d4 100644 --- a/tests/test_lamp.py +++ b/tests/test_lamp.py @@ -1,7 +1,7 @@ """Tests for lamp.""" -from lamp import Lamp -from consts import __version__ +from dali2mqtt.lamp import Lamp +from dali2mqtt.consts import __version__ from unittest import mock from dali.address import Short import pytest From 0940625d897f67c7b775d372a871f3d6d87e9b49 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 21:09:24 +0100 Subject: [PATCH 15/25] missing --- tests/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index cc2c79b..517887d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1 @@ -import os -import sys - -PROJECT_PATH = os.getcwd() -SOURCE_PATH = os.path.join(PROJECT_PATH, ".") -sys.path.append(SOURCE_PATH) +"""Tests for dali2mqtt.""" From 3e0f7ac6302a374f4d2441a78db06e0030c25f18 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 21:12:43 +0100 Subject: [PATCH 16/25] Add pytest badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dea9352..bdf575e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +![pytest workflow](https://github.com/dgomes/dali2mqtt/actions/workflows/pytest.yml/badge.svg) # dali2mqtt DALI <-> MQTT bridge From db1a1db262ba65d9807c8693cb9fc8832e784aae Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 21:15:43 +0100 Subject: [PATCH 17/25] New path --- dali2mqtt.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dali2mqtt.service b/dali2mqtt.service index 0034206..2ea8dd9 100644 --- a/dali2mqtt.service +++ b/dali2mqtt.service @@ -3,7 +3,7 @@ Description=dali2mqtt After=network.target [Service] -ExecStart=/home/homeassistant/dali2mqtt/venv/bin/python3 dali-mqtt-daemon.py +ExecStart=/home/homeassistant/dali2mqtt/venv/bin/python3 dali2mqtt/dali2mqtt.py WorkingDirectory=/home/homeassistant/dali2mqtt StandardOutput=inherit StandardError=inherit From 250fdedd2b2d7947eb4cb8d42347341854dd854a Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 22:00:39 +0100 Subject: [PATCH 18/25] add test_config --- dali2mqtt/config.py | 5 +++-- tests/data/config.yaml | 0 tests/test_config.py | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/data/config.yaml create mode 100644 tests/test_config.py diff --git a/dali2mqtt/config.py b/dali2mqtt/config.py index d044a59..dc593b8 100644 --- a/dali2mqtt/config.py +++ b/dali2mqtt/config.py @@ -100,7 +100,8 @@ def load_config_file(self): ) configuration = {} self._config = CONF_SCHEMA(configuration) - self._callback() + if self._callback: + self._callback() except AttributeError: # No callback configured pass @@ -111,8 +112,8 @@ def load_config_file(self): def save_config_file(self): """Save configuration back to yaml file.""" try: + cfg = self._config.pop(CONF_CONFIG) # temporary displace config file with open(self._path, "w", encoding="utf8") as outfile: - cfg = self._config.pop(CONF_CONFIG) # temporary displace config file yaml.dump( self._config, outfile, default_flow_style=False, allow_unicode=True ) diff --git a/tests/data/config.yaml b/tests/data/config.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..8d9ee62 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,17 @@ +"""Tests for config.""" + +from dali2mqtt.config import Config +from unittest import mock + +def test_load_config(): + args = mock.Mock() + args.config = "tests/data/config.yaml" + + cfg = Config(args) + + assert cfg.mqtt_conf == ('localhost', 1883, None, None, 'dali2mqtt') + assert cfg,dali_driver == "hasseb" + assert cfg.ha_discovery_prefix == "homeassistant" + assert cfg.log_level == "info" + assert cfg.log_color == False + assert cfg.devices_names_file == "devices.yaml" \ No newline at end of file From cc9d3531c49551dfbab51d1f6584a11b62bb569b Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 24 Apr 2022 23:59:29 +0100 Subject: [PATCH 19/25] move dali driver interation to lamp --- dali2mqtt/__init__.py | 1 + dali2mqtt/consts.py | 5 +++-- dali2mqtt/dali2mqtt.py | 40 ++++++++++++++------------------- dali2mqtt/devicesnamesconfig.py | 2 +- dali2mqtt/lamp.py | 27 +++++++++++++--------- tests/test_lamp.py | 15 ++++++++----- 6 files changed, 48 insertions(+), 42 deletions(-) diff --git a/dali2mqtt/__init__.py b/dali2mqtt/__init__.py index e69de29..b26da2a 100644 --- a/dali2mqtt/__init__.py +++ b/dali2mqtt/__init__.py @@ -0,0 +1 @@ +"""DALI 2 MQTT package.""" diff --git a/dali2mqtt/consts.py b/dali2mqtt/consts.py index dcb1ed0..f662ffb 100644 --- a/dali2mqtt/consts.py +++ b/dali2mqtt/consts.py @@ -1,8 +1,9 @@ +"""Constants common the various modules.""" + import logging -"""Constants common the various modules.""" __author__ = "Diogo Gomes" -__version__ = "0.0.1" +__version__ = "0.0.3" __email__ = "diogogomes@gmail.com" HASSEB = "hasseb" diff --git a/dali2mqtt/dali2mqtt.py b/dali2mqtt/dali2mqtt.py index 10972c5..563572e 100644 --- a/dali2mqtt/dali2mqtt.py +++ b/dali2mqtt/dali2mqtt.py @@ -67,13 +67,15 @@ logger = logging.getLogger(__name__) -def dali_scan(driver): +def dali_scan(dali_driver): """Scan a maximum number of dali devices.""" lamps = [] for lamp in range(0, 63): try: logging.debug("Search for Lamp %s", lamp) - present = driver.send(gear.QueryControlGearPresent(address.Short(lamp))) + present = dali_driver.send( + gear.QueryControlGearPresent(address.Short(lamp)) + ) if isinstance(present, YesNoResponse) and present.value: lamps.append(lamp) logger.debug("Found lamp at address %d", lamp) @@ -126,13 +128,13 @@ def scan_groups(dali_driver, lamps): def initialize_lamps(data_object, client): """Initialize all lamps and groups.""" - driver_object = data_object["driver"] + driver = data_object["driver"] mqtt_base_topic = data_object["base_topic"] ha_prefix = data_object["ha_prefix"] log_level = data_object["log_level"] devices_names_config = data_object["devices_names_config"] devices_names_config.load_devices_names_file() - lamps = dali_scan(driver_object) + lamps = dali_scan(driver) logger.info( "Found %d lamps", len(lamps), @@ -142,15 +144,9 @@ def create_mqtt_lamp(address, name): try: lamp_object = Lamp( log_level, - driver_object, + driver, name, address, - min_physical_level=driver_object.send( - gear.QueryPhysicalMinimum(address) - ).value, - min_level=driver_object.send(gear.QueryMinLevel(address)).value, - max_level=driver_object.send(gear.QueryMaxLevel(address)).value, - level=driver_object.send(gear.QueryActualLevel(address)).value, ) data_object["all_lamps"][name] = lamp_object @@ -205,7 +201,7 @@ def create_mqtt_lamp(address, name): devices_names_config.get_friendly_name(short_address.address), ) - groups = scan_groups(driver_object, lamps) + groups = scan_groups(driver, lamps) for group in groups: logger.debug("Publishing group %d", group) @@ -234,7 +230,7 @@ def on_message_cmd(mqtt_client, data_object, msg): try: lamp_object = data_object["all_lamps"][light] logger.debug("Set light <%s> to %s", light, msg.payload) - data_object["driver"].send(gear.Off(lamp_object.short_address)) + lamp_object.off() mqtt_client.publish( MQTT_STATE_TOPIC.format(data_object["base_topic"], light), MQTT_PAYLOAD_OFF, @@ -280,7 +276,7 @@ def on_message_brightness_cmd(mqtt_client, data_object, msg): lamp_object.level = int(msg.payload.decode("utf-8")) if lamp_object.level == 0: # 0 in DALI is turn off with fade out - data_object["driver"].send(gear.Off(lamp_object.short_address)) + lamp_object.off() logger.debug("Set light <%s> to OFF", light) mqtt_client.publish( @@ -316,27 +312,25 @@ def on_message_brightness_get_cmd(mqtt_client, data_object, msg): lamp_object = get_lamp_object(data_object, light) try: - level = data_object["driver"].send( - gear.QueryActualLevel(lamp_object.short_address) - ) - logger.debug("Get light <%s> results in %d", light, level.value) + lamp_object.actual_level() + logger.debug("Get light <%s> results in %d", light, lamp_object.level) mqtt_client.publish( MQTT_BRIGHTNESS_STATE_TOPIC.format(data_object["base_topic"], light), - level.value, + lamp_object.level, retain=False, ) mqtt_client.publish( MQTT_STATE_TOPIC.format(data_object["base_topic"], light), - MQTT_PAYLOAD_ON if level.value != 0 else MQTT_PAYLOAD_OFF, + MQTT_PAYLOAD_ON if lamp_object.level != 0 else MQTT_PAYLOAD_OFF, retain=False, ) except ValueError as err: logger.error( "Can't convert <%s> to integer %d..%d: %s", - str(level), + lamp_object.level, lamp_object.min_level, lamp_object.max_level, err, @@ -374,7 +368,7 @@ def on_connect( def create_mqtt_client( - driver_object, + driver, mqtt_server, mqtt_port, mqtt_username, @@ -389,7 +383,7 @@ def create_mqtt_client( mqttc = mqtt.Client( client_id="dali2mqtt", userdata={ - "driver": driver_object, + "driver": driver, "base_topic": mqtt_base_topic, "ha_prefix": ha_prefix, "devices_names_config": devices_names_config, diff --git a/dali2mqtt/devicesnamesconfig.py b/dali2mqtt/devicesnamesconfig.py index 4b78ac9..4b861f2 100644 --- a/dali2mqtt/devicesnamesconfig.py +++ b/dali2mqtt/devicesnamesconfig.py @@ -40,7 +40,7 @@ def load_devices_names_file(self): except yaml.YAMLError as error: logger.error("In devices file %s: %s", self._path, error) raise DevicesNamesConfigLoadError() - except Exception as err: + except Exception: logger.error( "Could not load device names config <%s>, a new one will be created after successfull start", self._path, diff --git a/dali2mqtt/lamp.py b/dali2mqtt/lamp.py index c26b756..eddc285 100644 --- a/dali2mqtt/lamp.py +++ b/dali2mqtt/lamp.py @@ -14,8 +14,6 @@ MQTT_NOT_AVAILABLE, MQTT_PAYLOAD_OFF, MQTT_STATE_TOPIC, - __author__, - __email__, __version__, ) from slugify import slugify @@ -33,20 +31,19 @@ def __init__( driver, friendly_name, short_address, - min_physical_level, - min_level, - level, - max_level, ): """Initialize Lamp.""" self.driver = driver self.short_address = short_address self.friendly_name = friendly_name + self.device_name = slugify(friendly_name) - self.min_physical_level = min_physical_level - self.min_level = min_level - self.max_level = max_level - self.level = level + + self.min_physical_level = driver.send(gear.QueryPhysicalMinimum(short_address)).value + self.min_level = driver.send(gear.QueryMinLevel(short_address)).value + self.max_level = driver.send(gear.QueryMaxLevel(short_address)).value + self.level = driver.send(gear.QueryActualLevel(short_address)).value + logger.setLevel(ALL_SUPPORTED_LOG_LEVELS[log_level]) def gen_ha_config(self, mqtt_base_topic): @@ -79,6 +76,10 @@ def gen_ha_config(self, mqtt_base_topic): } return json.dumps(json_config) + def actual_level(self): + """Retrieve actual level from ballast.""" + self.__level = self.driver.send(gear.QueryActualLevel(self.short_address)) + @property def level(self): """Return brightness level.""" @@ -86,6 +87,7 @@ def level(self): @level.setter def level(self, value): + """Commit level to ballast.""" if not self.min_level <= value <= self.max_level and value != 0: raise ValueError self.__level = value @@ -94,5 +96,10 @@ def level(self, value): "Set lamp <%s> brightness level to %s", self.friendly_name, self.level ) + def off(self): + """Turn off ballast.""" + self.driver.send(gear.Off(self.short_address)) + def __str__(self): + """Serialize lamp information.""" return f"{self.device_name} - address: {self.short_address.address}, actual brightness level: {self.level} (minimum: {self.min_level}, max: {self.max_level}, physical minimum: {self.min_physical_level})" diff --git a/tests/test_lamp.py b/tests/test_lamp.py index 78260d4..4ecb3b5 100644 --- a/tests/test_lamp.py +++ b/tests/test_lamp.py @@ -11,12 +11,19 @@ MIN_BRIGHTNESS = 2 MAX_BRIGHTNESS = 250 +def generate_driver_values(results): + for res in results: + result = mock.Mock() + result.value = res + print(result.value) + yield result @pytest.fixture def fake_driver(): drive = mock.Mock() - drive.send = lambda x: 0x00 - yield drive + drive.dummy = generate_driver_values([MIN_BRIGHTNESS, MIN_BRIGHTNESS, MAX_BRIGHTNESS, 100, 100]) + drive.send = lambda x: next(drive.dummy) + return drive @pytest.fixture @@ -37,10 +44,6 @@ def test_ha_config(fake_driver, fake_address): driver=fake_driver, friendly_name=friendly_name, short_address=addr, - min_physical_level=2, - min_level=MIN_BRIGHTNESS, - level=100, - max_level=MAX_BRIGHTNESS, ) assert lamp1.device_name == slugify(friendly_name) From e44d0d3fab3f2d9b7ed0a9c3a80c3b4ca9bc688f Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 25 Apr 2022 00:01:48 +0100 Subject: [PATCH 20/25] parameterize tests --- tests/test_lamp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_lamp.py b/tests/test_lamp.py index 4ecb3b5..4b088a5 100644 --- a/tests/test_lamp.py +++ b/tests/test_lamp.py @@ -8,8 +8,10 @@ import json from slugify import slugify +MIN__PHYSICAL_BRIGHTNESS = 1 MIN_BRIGHTNESS = 2 MAX_BRIGHTNESS = 250 +ACTUAL_BRIGHTNESS = 100 def generate_driver_values(results): for res in results: @@ -21,7 +23,7 @@ def generate_driver_values(results): @pytest.fixture def fake_driver(): drive = mock.Mock() - drive.dummy = generate_driver_values([MIN_BRIGHTNESS, MIN_BRIGHTNESS, MAX_BRIGHTNESS, 100, 100]) + drive.dummy = generate_driver_values([MIN__PHYSICAL_BRIGHTNESS, MIN_BRIGHTNESS, MAX_BRIGHTNESS, ACTUAL_BRIGHTNESS, ACTUAL_BRIGHTNESS]) drive.send = lambda x: next(drive.dummy) return drive @@ -49,7 +51,7 @@ def test_ha_config(fake_driver, fake_address): assert lamp1.device_name == slugify(friendly_name) assert lamp1.short_address.address == addr_number - assert str(lamp1) == f'my-lamp - address: {addr_number}, actual brightness level: 100 (minimum: {MIN_BRIGHTNESS}, max: {MAX_BRIGHTNESS}, physical minimum: 2)' + assert str(lamp1) == f'my-lamp - address: {addr_number}, actual brightness level: {ACTUAL_BRIGHTNESS} (minimum: {MIN_BRIGHTNESS}, max: {MAX_BRIGHTNESS}, physical minimum: {MIN__PHYSICAL_BRIGHTNESS})' assert json.loads(lamp1.gen_ha_config("test")) == { "name": friendly_name, From 61c988df03cdf485632c43d11528bc86724c4892 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 25 Apr 2022 00:17:13 +0100 Subject: [PATCH 21/25] adjust --- dali2mqtt.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dali2mqtt.service b/dali2mqtt.service index 2ea8dd9..07dce2a 100644 --- a/dali2mqtt.service +++ b/dali2mqtt.service @@ -3,7 +3,7 @@ Description=dali2mqtt After=network.target [Service] -ExecStart=/home/homeassistant/dali2mqtt/venv/bin/python3 dali2mqtt/dali2mqtt.py +ExecStart=/home/homeassistant/dali2mqtt/venv/bin/python3 -m dali2mqtt.dali2mqtt WorkingDirectory=/home/homeassistant/dali2mqtt StandardOutput=inherit StandardError=inherit From 6e06fcb4e8f5d1eb0662031a50d4d8640c3d904c Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 25 Apr 2022 00:17:41 +0100 Subject: [PATCH 22/25] fixes --- dali2mqtt/dali2mqtt.py | 2 +- tests/test_lamp.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dali2mqtt/dali2mqtt.py b/dali2mqtt/dali2mqtt.py index 563572e..e7904a5 100644 --- a/dali2mqtt/dali2mqtt.py +++ b/dali2mqtt/dali2mqtt.py @@ -252,7 +252,7 @@ def get_lamp_object(data_object, light): """Retrieve lamp object from data object.""" if "group_" in light: """Check if the comand is for a dali group""" - group = int(re.search("group_(\d+)", light).group(1)) + group = int(re.search(r"group_(\d+)", light).group(1)) lamp_object = data_object["all_lamps"][group] else: """The command is for a single lamp""" diff --git a/tests/test_lamp.py b/tests/test_lamp.py index 4b088a5..a9375e5 100644 --- a/tests/test_lamp.py +++ b/tests/test_lamp.py @@ -32,7 +32,6 @@ def fake_driver(): def fake_address(): address = mock.Mock() address.address = 1 - address.__repr__ = lambda: "1" def test_ha_config(fake_driver, fake_address): From 26c158604c986fe1806d81d0ec7516b3c382a5ef Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 25 Apr 2022 00:22:20 +0100 Subject: [PATCH 23/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bdf575e..995d9af 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ pip install -r requirements.txt You can create a configuration file when you call the daemon the first time ```bash -venv/bin/python3 ./dali_mqtt_daemon.py +venv/bin/python3 -m dali2mqtt.dali2mqtt ``` Then just edit the file accordingly. You can also create the file with the right values, by using the arguments of dali_mqtt_daemon.py: From 72a7258a4dd676526b17aab36da1d88e1c7a3710 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 25 Apr 2022 00:38:06 +0100 Subject: [PATCH 24/25] add flake8 --- .github/workflows/pytest.yml | 16 +++++++++++----- dali2mqtt/lamp.py | 10 ++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ee17d9a..d8a0b93 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,30 +1,36 @@ name: Tests -# Run this workflow every time a new commit pushed to your repository -on: push +on: [push, pull_request] jobs: unit_tests: name: Tests runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10'] steps: # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - + + - name: Lint with flake8 + run: | + flake8 dali2mqtt --count --show-source --statistics --max-line-length=127 + - name: Test with pytest timeout-minutes: 3 run: | diff --git a/dali2mqtt/lamp.py b/dali2mqtt/lamp.py index eddc285..272948d 100644 --- a/dali2mqtt/lamp.py +++ b/dali2mqtt/lamp.py @@ -39,7 +39,9 @@ def __init__( self.device_name = slugify(friendly_name) - self.min_physical_level = driver.send(gear.QueryPhysicalMinimum(short_address)).value + self.min_physical_level = driver.send( + gear.QueryPhysicalMinimum(short_address) + ).value self.min_level = driver.send(gear.QueryMinLevel(short_address)).value self.max_level = driver.send(gear.QueryMaxLevel(short_address)).value self.level = driver.send(gear.QueryActualLevel(short_address)).value @@ -102,4 +104,8 @@ def off(self): def __str__(self): """Serialize lamp information.""" - return f"{self.device_name} - address: {self.short_address.address}, actual brightness level: {self.level} (minimum: {self.min_level}, max: {self.max_level}, physical minimum: {self.min_physical_level})" + return ( + f"{self.device_name} - address: {self.short_address.address}, " + f"actual brightness level: {self.level} (minimum: {self.min_level}, " + f"max: {self.max_level}, physical minimum: {self.min_physical_level})" + ) From 7cfd876b7278346e3a5457946ed9770c051e41ea Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 25 Apr 2022 00:46:56 +0100 Subject: [PATCH 25/25] missed install flake8 --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index d8a0b93..1a7bfc7 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pytest + pip install pytest flake8 if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8