From 0530e66f02da52eef2452e413430ae7d45541682 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 1 May 2024 14:59:17 +0200 Subject: [PATCH 1/8] zigbee2mqtt: shorten bridge messages for INFO logging --- zigbee2mqtt/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zigbee2mqtt/__init__.py b/zigbee2mqtt/__init__.py index b8cd20b02..6a541aea9 100755 --- a/zigbee2mqtt/__init__.py +++ b/zigbee2mqtt/__init__.py @@ -27,6 +27,7 @@ import json from lib.model.mqttplugin import MqttPlugin +from logging import DEBUG from .rgbxy import Converter from .webif import WebInterface @@ -417,7 +418,13 @@ def on_mqtt_msg(self, topic: str, payload, qos=None, retain=None): if item is not None: item(value, src) - self.logger.info(f"{device}: Item '{item}' set to value {value}") + if device == 'bridge' and (isinstance(value, list) or isinstance(value, dict)): + if self.logger.isEnabledFor(DEBUG): + self.logger.debug(f"{device}: Item '{item}' set to value {value}") + else: + self.logger.info(f"{device}: Item '{item}' set to value {str(value)[:80]}[...] (enable debug log for full output)") + else: + self.logger.info(f"{device}: Item '{item}' set to value {value}") else: self.logger.info(f"{device}: No item for attribute '{attr}' defined to set to {value}") From 868393a7313dd06a833e1e60c15c2b1539d8a97e Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 1 May 2024 14:59:17 +0200 Subject: [PATCH 2/8] zigbee2mqtt: shorten bridge messages for INFO logging --- zigbee2mqtt/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zigbee2mqtt/__init__.py b/zigbee2mqtt/__init__.py index b8cd20b02..6a541aea9 100755 --- a/zigbee2mqtt/__init__.py +++ b/zigbee2mqtt/__init__.py @@ -27,6 +27,7 @@ import json from lib.model.mqttplugin import MqttPlugin +from logging import DEBUG from .rgbxy import Converter from .webif import WebInterface @@ -417,7 +418,13 @@ def on_mqtt_msg(self, topic: str, payload, qos=None, retain=None): if item is not None: item(value, src) - self.logger.info(f"{device}: Item '{item}' set to value {value}") + if device == 'bridge' and (isinstance(value, list) or isinstance(value, dict)): + if self.logger.isEnabledFor(DEBUG): + self.logger.debug(f"{device}: Item '{item}' set to value {value}") + else: + self.logger.info(f"{device}: Item '{item}' set to value {str(value)[:80]}[...] (enable debug log for full output)") + else: + self.logger.info(f"{device}: Item '{item}' set to value {value}") else: self.logger.info(f"{device}: No item for attribute '{attr}' defined to set to {value}") From 436cea14a37fa5f4888713638b4ec9f0f19a919a Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:37:00 +0200 Subject: [PATCH 3/8] z2m: adjust for suspend updates --- zigbee2mqtt/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/zigbee2mqtt/__init__.py b/zigbee2mqtt/__init__.py index 6a541aea9..59a56b4e5 100755 --- a/zigbee2mqtt/__init__.py +++ b/zigbee2mqtt/__init__.py @@ -116,11 +116,11 @@ def run(self): self.logger.debug("Run method called") self.alive = True + self.set_suspend(by='run') # start subscription to all topics self.start_subscriptions() - self.scheduler_add('z2m_cycle', self.poll_bridge, cycle=self.cycle) self.publish_z2m_topic('bridge', 'config', 'devices', 'get') if self.read_at_init: @@ -136,7 +136,6 @@ def stop(self): self.alive = False self.logger.debug("Stop method called") - self.scheduler_remove('z2m_c') # stop subscription to all topics self.stop_subscriptions() @@ -155,15 +154,12 @@ def parse_item(self, item): can be sent to the knx with a knx write function within the knx plugin. """ - # remove this block when its included in smartplugin.py, - # replace with super().parse_item(item) # check for suspend item if item.property.path == self._suspend_item_path: self.logger.debug(f'suspend item {item.property.path} registered') self._suspend_item = item self.add_item(item, updating=True) return self.update_item - # end block if self.has_iattr(item.conf, Z2M_ATTR): self.logger.debug(f"parsing item: {item}") @@ -253,6 +249,12 @@ def update_item(self, item, caller='', source=None, dest=None): """ self.logger.debug(f"update_item: {item} called by {caller} and source {source}") + if item is self._suspend_item: + if caller != self.get_shortname(): + self.logger.debug(f'Suspend item changed to {item()}') + self.set_suspend(by=f'suspend item {item.property.path}') + return + if self.alive and not self.suspended and not caller.startswith(self.get_shortname()): if item in self._items_write: @@ -318,7 +320,7 @@ def update_item(self, item, caller='', source=None, dest=None): else: self.logger.warning(f"update_item: {item}, trying to change item in SmartHomeNG that is readonly (by {caller})") - def poll_bridge(self): + def poll_device(self): """ Polls for health state of the bridge """ self.logger.info("poll_bridge: Checking health status of bridge") From 5a8dd0490055220a259b8960b662dc5c366a643e Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:43:11 +0200 Subject: [PATCH 4/8] z2m: add pause_item functions --- zigbee2mqtt/__init__.py | 59 ++++++++++++++++++++++++----------------- zigbee2mqtt/plugin.yaml | 8 +++--- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/zigbee2mqtt/__init__.py b/zigbee2mqtt/__init__.py index 59a56b4e5..1becfd8ce 100755 --- a/zigbee2mqtt/__init__.py +++ b/zigbee2mqtt/__init__.py @@ -25,9 +25,9 @@ from datetime import datetime import json +from logging import DEBUG from lib.model.mqttplugin import MqttPlugin -from logging import DEBUG from .rgbxy import Converter from .webif import WebInterface @@ -48,7 +48,7 @@ class Zigbee2Mqtt(MqttPlugin): """ Main class of the Plugin. Does all plugin specific stuff and provides the update functions for the items """ - PLUGIN_VERSION = '2.0.1' + PLUGIN_VERSION = '2.0.2' def __init__(self, sh, **kwargs): """ Initializes the plugin. """ @@ -56,6 +56,8 @@ def __init__(self, sh, **kwargs): # Call init code of parent class (MqttPlugin) super().__init__() + # self.logger = logging.getLogger(__name__) + self.logger.info(f'Init {self.get_shortname()} plugin {self.PLUGIN_VERSION}') # get the parameters for the plugin (as defined in metadata plugin.yaml): @@ -63,8 +65,9 @@ def __init__(self, sh, **kwargs): self.cycle = self.get_parameter_value('poll_period') self.read_at_init = self.get_parameter_value('read_at_init') self._z2m_gui = self.get_parameter_value('z2m_gui') + self._pause_item_path = self.get_parameter_value('pause_item') - # bool_values is only good if used internally, because MQTT data is + # bool_values is only good if used internally, because MQTT data is # usually sent in JSON. So just make this easy... self.bool_values = [False, True] @@ -116,11 +119,13 @@ def run(self): self.logger.debug("Run method called") self.alive = True - self.set_suspend(by='run') + if self._pause_item: + self._pause_item(False, self.get_fullname()) # start subscription to all topics self.start_subscriptions() + self.scheduler_add('z2m_cycle', self.poll_bridge, cycle=self.cycle) self.publish_z2m_topic('bridge', 'config', 'devices', 'get') if self.read_at_init: @@ -135,7 +140,10 @@ def stop(self): """ Stop method for the plugin """ self.alive = False + if self._pause_item: + self._pause_item(True, self.get_fullname()) self.logger.debug("Stop method called") + self.scheduler_remove('z2m_cycle') # stop subscription to all topics self.stop_subscriptions() @@ -154,12 +162,15 @@ def parse_item(self, item): can be sent to the knx with a knx write function within the knx plugin. """ - # check for suspend item - if item.property.path == self._suspend_item_path: - self.logger.debug(f'suspend item {item.property.path} registered') - self._suspend_item = item + # remove this block when its included in smartplugin.py, + # replace with super().parse_item(item) + # check for alive item + if item.property.path == self._pause_item_path: + self.logger.debug(f'alive item {item.property.path} registered') + self._pause_item = item self.add_item(item, updating=True) return self.update_item + # end block if self.has_iattr(item.conf, Z2M_ATTR): self.logger.debug(f"parsing item: {item}") @@ -171,13 +182,6 @@ def parse_item(self, item): attr = self.get_iattr_value(item.conf, Z2M_ATTR) - if item.type() == 'bool': - bval = self.get_iattr_value(item.conf, Z2M_BVAL) - if bval == []: - bval = None - if bval is None or type(bval) is not list: - bval = self.bool_values - # invert read-only/write-only logic to allow read/write write = not self.get_iattr_value(item.conf, Z2M_RO, False) read = not self.get_iattr_value(item.conf, Z2M_WO, False) or not write @@ -195,6 +199,9 @@ def parse_item(self, item): 'write': write, } if item.type() == 'bool': + bval = self.get_iattr_value(item.conf, Z2M_BVAL) + if bval is None or bval == [] or type(bval) is not list: + bval = self.bool_values data['bool_values'] = bval self._devices[device][attr].update(data) @@ -213,7 +220,7 @@ def parse_item(self, item): def remove_item(self, item): if item not in self._plg_item_dict: - return + return False mapping = self.get_item_mapping(item) if mapping: @@ -236,9 +243,9 @@ def remove_item(self, item): except ValueError: pass - super().remove_item(item) + return super().remove_item(item) - def update_item(self, item, caller='', source=None, dest=None): + def update_item(self, item, caller=None, source=None, dest=None): """ Item has been updated @@ -249,13 +256,17 @@ def update_item(self, item, caller='', source=None, dest=None): """ self.logger.debug(f"update_item: {item} called by {caller} and source {source}") - if item is self._suspend_item: + # check for pause item + if item is self._pause_item: if caller != self.get_shortname(): - self.logger.debug(f'Suspend item changed to {item()}') - self.set_suspend(by=f'suspend item {item.property.path}') + self.logger.debug(f'pause item changed to {item()}') + if item() and self.alive: + self.stop() + elif not item() and not self.alive: + self.run() return - if self.alive and not self.suspended and not caller.startswith(self.get_shortname()): + if self.alive and caller and not caller.startswith(self.get_shortname()): if item in self._items_write: @@ -314,13 +325,13 @@ def update_item(self, item, caller='', source=None, dest=None): attr: value }) else: - payload = None + payload = '' self.publish_z2m_topic(device, topic_3, topic_4, topic_5, payload, item, bool_values=bool_values) else: self.logger.warning(f"update_item: {item}, trying to change item in SmartHomeNG that is readonly (by {caller})") - def poll_device(self): + def poll_bridge(self): """ Polls for health state of the bridge """ self.logger.info("poll_bridge: Checking health status of bridge") diff --git a/zigbee2mqtt/plugin.yaml b/zigbee2mqtt/plugin.yaml index 9629740b5..335a49f6b 100755 --- a/zigbee2mqtt/plugin.yaml +++ b/zigbee2mqtt/plugin.yaml @@ -12,7 +12,7 @@ plugin: documentation: '' support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1856775-support-thread-f%C3%BCr-das-zigbee2mqtt-plugin - version: 2.0.1 # Plugin version + version: 2.0.2 # Plugin version sh_minversion: 1.9.5.6 # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) py_minversion: 3.8 # minimum Python version to use for this plugin @@ -45,12 +45,12 @@ parameters: de: Einlesen aller Werte beim Start en: Read all values at init - suspend_item: + pause_item: type: str default: '' description: - de: Pfad zum Suspend-Item - en: Path to suspend item + de: Pfad zum Pause-Item + en: Path to pause item z2m_gui: type: str From 85756fc9bdf4ee3bddb7dce4e7faa2746f0f24ed Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:54:20 +0200 Subject: [PATCH 5/8] russound: move to pause_item support --- russound/__init__.py | 75 ++++++++++++++++++++------------------------ russound/plugin.yaml | 10 +++--- 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/russound/__init__.py b/russound/__init__.py index ad6a07379..1d4520f10 100755 --- a/russound/__init__.py +++ b/russound/__init__.py @@ -42,7 +42,7 @@ class Russound(SmartPlugin): the update functions for the items """ - PLUGIN_VERSION = '1.7.2' + PLUGIN_VERSION = '1.7.3' def __init__(self, sh, *args, **kwargs): """ @@ -52,16 +52,10 @@ def __init__(self, sh, *args, **kwargs): if '.'.join(VERSION.split('.', 2)[:2]) <= '1.5': self.logger = logging.getLogger(__name__) - super().__init__(sh, args, kwargs) - try: - # sh = self.get_sh() to get it. - self.host = self.get_parameter_value('host') - self.port = self.get_parameter_value('port') - except KeyError as e: - self.logger.critical( - "Plugin '{}': Inconsistent plugin (invalid metadata definition: {} not defined)".format(self.get_shortname(), e)) - self._init_complete = False - return + super().__init__() + self.host = self.get_parameter_value('host') + self.port = self.get_parameter_value('port') + self._pause_item_path = self.get_parameter_value('pause_item') # Initialization code goes here self.terminator = RESP_DELIMITER @@ -69,10 +63,8 @@ def __init__(self, sh, *args, **kwargs): self._client.set_callbacks(data_received=self.found_terminator) self.params = {} self.sources = {} - self.suspended = False - + self.init_webinterface() - return def run(self): """ @@ -80,21 +72,30 @@ def run(self): """ self.logger.debug("Run method called") if not self._client.connect(): - self.logger.debug(f'Connection to {self.host}:{self.port} not possible. Plugin deactivated.') + self.logger.debug(f'Connection to {self.host}:{self.port} not possible. Plugin stopped.') + self.stop() return + self.alive = True + if self._pause_item: + self._pause_item(False, self.get_fullname()) def activate(self): - self.logger.debug("Activate method called, queries to russound will be resumes and data will be written again") - self.resume() - + self.logger.debug("Activate method called, but is deprecated. Please move to run()") + self.run() + def stop(self): """ Stop method for the plugin """ self.logger.debug("Stop method called") self.alive = False - self._client.close() + if self._pause_item: + self._pause_item(True, self.get_fullname()) + try: + self._client.close() + except Exception: + pass def connect(self): self._client.open() @@ -121,10 +122,10 @@ def parse_item(self, item): # self.logger.debug("Source {0} added".format(s)) # return None - if item.property.path == self._suspend_item_path: - self._suspend_item = item - self.logger.info(f'set suspend_item to {item.property.path}') - return + if item.property.path == self._pause_item_path: + self._pause_item = item + self.logger.info(f'set pause_item to {item.property.path}') + return self.update_item if self.has_iattr(item.conf, 'rus_path'): self.logger.debug("parse item: {}".format(item)) @@ -177,9 +178,6 @@ def parse_item(self, item): return self.update_item - def parse_logic(self, logic): - pass - def _restrict(self, val, minval, maxval): if val < minval: return minval @@ -200,19 +198,21 @@ def update_item(self, item, caller=None, source=None, dest=None): :param source: if given it represents the source :param dest: if given it represents the dest """ + # check for pause item + if item is self._pause_item: + if caller != self.get_shortname(): + self.logger.debug(f'pause item changed to {item()}') + if item() and self.alive: + self.stop() + elif not item() and not self.alive: + self.run() + return + if self.alive and caller != self.get_shortname(): # code to execute if the plugin is not stopped # and only, if the item has not been changed by this this plugin: self.logger.info("Update item: {}, item has been changed outside this plugin (caller={}, source={}, dest={})".format(item.property.path, caller, source, dest)) - if item.property.path == self._suspend_item_path: - if self._suspend_item is not None: - if item(): - self.suspend(f'suspend item {item.property.path}') - else: - self.resume(f'suspend item {item.property.path}') - return - if self.has_iattr(item.conf, 'rus_path'): path = self.get_iattr_value(item.conf, 'rus_path') p = self.params[path] @@ -282,18 +282,11 @@ def _send_cmd(self, cmd): if not self.alive: self.logger.error('Trying to send data but plugin is not running') return - - if self.suspended: - self.logger.debug('Plugin is suspended, data will not be written') - return self.logger.debug("Sending request: {0}".format(cmd)) # if connection is closed we don't wait for sh.con to reopen it # instead we reconnect immediatly -# - # if not self.connected: - # self.connect() if not self._client.connected: self._client.connect() diff --git a/russound/plugin.yaml b/russound/plugin.yaml index 96dd49907..8a88bc94e 100755 --- a/russound/plugin.yaml +++ b/russound/plugin.yaml @@ -12,8 +12,8 @@ plugin: documentation: https://www.smarthomeng.de/developer/plugins/russound/user_doc.html # url of documentation (wiki) page support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1800440-support-thread-für-das-russound-plugin - version: 1.7.2 # Plugin version - sh_minversion: 1.9.0 # minimum shNG version to use this plugin + version: 1.7.3 # Plugin version + sh_minversion: 1.10.0 # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) multi_instance: false # plugin supports multi instance restartable: true @@ -36,12 +36,12 @@ parameters: en: 'Russound port' fr: "Port de Russound" - standby_item: + pause_item: type: str default: '' description: - de: 'Item zum Aktivieren des Suspend-Modus' - en: 'item for activating suspend mode' + de: 'Item zum Anhalten/Fortsetzen des Plugins' + en: 'item for stopping/starting the plugin' item_attributes: rus_path: From 8e556a67aedeaaed54454425796c733c3a669c91 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:32:36 +0200 Subject: [PATCH 6/8] db_addon: adjust to smartplugin changes --- db_addon/__init__.py | 40 +++++----------------------------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/db_addon/__init__.py b/db_addon/__init__.py index c5e85b60f..2416b5c31 100644 --- a/db_addon/__init__.py +++ b/db_addon/__init__.py @@ -99,7 +99,6 @@ def __init__(self, sh): self.item_attribute_search_str = 'database' # attribute, on which an item configured for database can be identified self.last_connect_time = 0 # mechanism for limiting db connection requests self.alive = None # Is plugin alive? - self.suspended = False # Is plugin activity suspended self.active_queue_item: str = '-' # String holding item path of currently executed item self.onchange_delay_time = 30 @@ -139,19 +138,19 @@ def run(self): # check existence of db-plugin, get parameters, and init connection to db if not self._check_db_existence(): self.logger.error(f"Check of existence of database plugin incl connection check failed. Plugin not loaded") - return self.deinit() + return # create db object self._db = lib.db.Database("DatabaseAddOn", self.db_driver, self.connection_data) if not self._db.api_initialized: self.logger.error("Initialization of database API failed") - return self.deinit() + return self.logger.debug("Initialization of database API successful") # check initialization of db if not self._initialize_db(): self.logger.error("Connection to database failed") - return self.deinit() + return self._db.close() # check db connection settings @@ -750,11 +749,8 @@ def update_item(self, item, caller=None, source=None, dest=None): if item in self._database_items(): # if not self.startup_finished: # self.logger.info(f"Handling of 'onchange' is paused for startup. No updated will be processed.") - if self.suspended: - self.logger.info(f"Plugin is suspended. No updated will be processed.") - else: - self.logger.debug(f" Updated Item {item.property.path} with value {item()} will be put to queue in approx. {self.onchange_delay_time}s resp. after startup.") - self.update_item_delay_deque.append([item, item(), int(time.time() + self.onchange_delay_time)]) + self.logger.debug(f" Updated Item {item.property.path} with value {item()} will be put to queue in approx. {self.onchange_delay_time}s resp. after startup.") + self.update_item_delay_deque.append([item, item(), int(time.time() + self.onchange_delay_time)]) # handle admin items elif self.has_iattr(item.conf, 'db_addon_admin'): @@ -956,10 +952,6 @@ def _create_due_items() -> list: if self.debug_log.execute: self.logger.debug(f"execute_items called with {option=}") - if self.suspended: - self.logger.info(f"Plugin is suspended. No items will be calculated.") - return - suspended_items = self._suspended_items() if len(suspended_items) > 0: self.logger.info(f"{len(suspended_items)} are suspended and will not be calculated.") @@ -1537,28 +1529,6 @@ def fetch_raw(self, query: str, params: dict = None) -> Union[list, None]: return self._fetchall(query, params) - def suspend(self, state: bool = False) -> bool: - """ - Will pause value evaluation of plugin - - """ - - if state: - self.logger.info("Plugin is set to 'suspended'. Queries to database will not be made until suspension is cleared.") - self.suspended = True - self._clear_queue() - else: - self.logger.info("Plugin suspension cleared. Queries to database will be resumed.") - self.suspended = False - - # write back value to item, if one exists - for item in self.get_item_list('db_addon', 'admin'): - item_config = self.get_item_config(item) - if item_config['db_addon_fct'] == 'suspend': - item(self.suspended, self.get_shortname()) - - return self.suspended - ############################################## # Calculation methods / Using Item Object ############################################## From f3ef10e11d8fa1795c2f519b415818a05b4a1b21 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Thu, 27 Jun 2024 07:57:00 +0200 Subject: [PATCH 7/8] modbus: move to pause_item support --- modbus_tcp/__init__.py | 124 +++++++++++------------------------------ modbus_tcp/plugin.yaml | 22 ++++++-- 2 files changed, 49 insertions(+), 97 deletions(-) diff --git a/modbus_tcp/__init__.py b/modbus_tcp/__init__.py index bc75596c2..90559c425 100755 --- a/modbus_tcp/__init__.py +++ b/modbus_tcp/__init__.py @@ -47,13 +47,14 @@ AttrObjectType = 'modBusObjectType' AttrDirection = 'modBusDirection' + class modbus_tcp(SmartPlugin): """ This class provides a Plugin for SmarthomeNG to read and or write to modbus devices. """ - PLUGIN_VERSION = '1.0.12' + PLUGIN_VERSION = '1.0.13' def __init__(self, sh, *args, **kwargs): """ @@ -85,9 +86,11 @@ def __init__(self, sh, *args, **kwargs): if not (self._cycle or self._crontab): self.logger.error(f"{self.get_fullname()}: no update cycle or crontab set. Modbus will not be queried automatically") - self._slaveUnit = int(self.get_parameter_value('slaveUnit')) + self._slaveUnit = self.get_parameter_value('slaveUnit') self._slaveUnitRegisterDependend = False + self._pause_item_path = self.get_parameter_value('pause_item') + self._sh = sh self._regToRead = {} self._regToWrite = {} @@ -99,8 +102,6 @@ def __init__(self, sh, *args, **kwargs): self.init_webinterface(WebInterface) - return - def run(self): """ Run method for the plugin @@ -108,67 +109,25 @@ def run(self): self.logger.debug(f"Plugin '{self.get_fullname()}': run method called") if self.alive: return + self.alive = True - self.set_suspend(by='run()') - + if self._cycle or self._crontab: - self.error_count = 0 # Initialize error count - if not self.suspended: - self._create_cyclic_scheduler() + self.error_count = 0 # Initialize error count + self.scheduler_add('poll_device_' + self._host, self.poll_device, cycle=self._cycle, cron=self._crontab, prio=5) self.logger.debug(f"Plugin '{self.get_fullname()}': run method finished ") - def _create_cyclic_scheduler(self): - self.scheduler_add('poll_device_' + self._host, self.poll_device, cycle=self._cycle, cron=self._crontab, prio=5) - - def _remove_cyclic_scheduler(self): - self.scheduler_remove('poll_device_' + self._host) - def stop(self): """ Stop method for the plugin """ self.alive = False self.logger.debug(f"Plugin '{self.get_fullname()}': stop method called") - self._remove_cyclic_scheduler() + self.scheduler_remove('poll_device_' + self._host) self._Mclient.close() self.connected = False self.logger.debug(f"Plugin '{self.get_fullname()}': stop method finished") - # sh.plugins.return_plugin('pluginName').suspend() - def set_suspend(self, suspend_active=None, by=None): - """ - enable / disable suspend mode: open/close connections, schedulers - """ - - if suspend_active is None: - if self._suspend_item is not None: - # if no parameter set, try to use item setting - suspend_active = bool(self._suspend_item()) - else: - # if not available, default to "resume" (non-breaking default) - suspend_active = False - - # print debug logging - if suspend_active: - msg = 'Suspend mode enabled' - else: - msg = 'Suspend mode disabled' - if by: - msg += f' (set by {by})' - self.logger.debug(msg) - - # activate selected mode, use smartplugin methods - if suspend_active: - self.suspend(by) - else: - self.resume(by) - - if suspend_active: - self._remove_cyclic_scheduler() - else: - self._create_cyclic_scheduler() - - def parse_item(self, item): """ Default plugin parse_item method. Is called when the plugin is initialized. @@ -178,10 +137,10 @@ def parse_item(self, item): :param item: The item to process. """ - # check for suspend item - if item.property.path == self._suspend_item_path: - self.logger.debug(f'suspend item {item.property.path} registered') - self._suspend_item = item + # check for pause item + if item.property.path == self._pause_item_path: + self.logger.debug(f'pause item {item.property.path} registered') + self._pause_item = item self.add_item(item, updating=True) return self.update_item @@ -207,7 +166,7 @@ def parse_item(self, item): if self.has_iattr(item.conf, AttrObjectType): objectType = self.get_iattr_value(item.conf, AttrObjectType) - reg = str(objectType) # dictionary key: objectType.regAddr.slaveUnit // HoldingRegister.528.1 + reg = str(objectType) # dictionary key: objectType.regAddr.slaveUnit // HoldingRegister.528.1 reg += '.' reg += str(regAddr) reg += '.' @@ -274,7 +233,7 @@ def poll_device(self): changes on it's own, but has to be polled to get the actual status. It is called by the scheduler which is set within run() method. """ - if self.suspended: + if not self.alive: return with self.lock: @@ -302,7 +261,6 @@ def poll_device(self): try: for reg, regPara in self._regToRead.items(): with self.lock: - regAddr = regPara['regAddr'] value = self.__read_Registers(regPara) # self.logger.debug(f"value read: {value} type: {type(value)}") if value is not None: @@ -330,8 +288,6 @@ def poll_device(self): except Exception as e: self.logger.error(f"something went wrong in the poll_device function: {e}") - - # called each time an item changes. def update_item(self, item, caller=None, source=None, dest=None): """ Item has been updated @@ -349,21 +305,16 @@ def update_item(self, item, caller=None, source=None, dest=None): slaveUnit = self._slaveUnit dataDirection = 'read' - # check for suspend item - if item is self._suspend_item: + # check for pause item + if item is self._pause_item: if caller != self.get_shortname(): - self.logger.debug(f'Suspend item changed to {item()}') - self.set_suspend(item(), by=f'suspend item {item.property.path}') + self.logger.debug(f'pause item changed to {item()}') + if item() and self.alive: + self.stop() + elif not item() and not self.alive: + self.run() return - if self.suspended: - if self.suspend_log_update is None or self.suspend_log_update is False: # debug - Nachricht nur 1x ausgeben - self.logger.info('Plugin is suspended, data will not be written') - self.suspend_log_update = True - return - else: - self.suspend_log_update = False - if caller == self.get_fullname(): # self.logger.debug(f'item was changed by the plugin itself - caller:{caller} source:{source} dest:{dest}') return @@ -389,7 +340,7 @@ def update_item(self, item, caller=None, source=None, dest=None): # else: # self.logger.debug(f'update_item:{item} default modBusObjectTyp: {objectType}') - reg = str(objectType) # Dict-key: HoldingRegister.528.1 *** objectType.regAddr.slaveUnit *** + reg = str(objectType) # Dict-key: HoldingRegister.528.1 *** objectType.regAddr.slaveUnit *** reg += '.' reg += str(regAddr) reg += '.' @@ -417,8 +368,6 @@ def update_item(self, item, caller=None, source=None, dest=None): self.connected = False return - startTime = datetime.now() - regCount = 0 try: self.__write_Registers(regPara, item()) except Exception as e: @@ -431,19 +380,13 @@ def __write_Registers(self, regPara, value): bo = regPara['byteOrder'] wo = regPara['wordOrder'] dataTypeStr = regPara['dataType'] - dataType = ''.join(filter(str.isalpha, dataTypeStr)) # vom dataType die Ziffen entfernen z.B. uint16 = uint - registerCount = 0 # Anzahl der zu schreibenden Register (Words) + dataType = ''.join(filter(str.isalpha, dataTypeStr)) # vom dataType die Ziffen entfernen z.B. uint16 = uint try: - bits = int(''.join(filter(str.isdigit, dataTypeStr))) # bit-Zahl aus aus dataType z.B. uint16 = 16 + bits = int(''.join(filter(str.isdigit, dataTypeStr))) # bit-Zahl aus aus dataType z.B. uint16 = 16 except: bits = 16 - if dataType.lower() == 'string': - registerCount = int(bits / 2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount - else: - registerCount = int(bits / 16) - if regPara['factor'] != 1: # self.logger.debug(f"value {value} divided by: {regPara['factor']}") value = value * (1 / regPara['factor']) @@ -480,11 +423,11 @@ def __write_Registers(self, regPara, value): builder.add_string(value) elif dataType.lower() == 'bit': if objectType == 'Coil' or objectType == 'DiscreteInput': - if not isinstance(value, bool): # test is boolean + if not isinstance(value, bool): # test is boolean self.logger.error(f"Value is not boolean: {value}") return else: - if set(value).issubset({'0', '1'}) and bool(value): # test is bit-string '00110101' + if set(value).issubset({'0', '1'}) and bool(value): # test is bit-string '00110101' builder.add_bits(value) else: self.logger.error(f"Value is not a bitstring: {value}") @@ -541,13 +484,13 @@ def __read_Registers(self, regPara): bits = 16 if dataType.lower() == 'string': - registerCount = int(bits / 2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount + registerCount = int(bits / 2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount else: registerCount = int(bits / 16) - if self.connected == False: + if not self.connected: self.logger.error(f"not connected to {self._host}:{self._port}") - return None + return # self.logger.debug(f"read {objectType}.{address}.{slaveUnit} (address.slaveUnit) regCount:{registerCount}") if objectType == 'Coil': @@ -560,11 +503,11 @@ def __read_Registers(self, regPara): result = self._Mclient.read_holding_registers(address, registerCount, slave=slaveUnit) else: self.logger.error(f"{AttrObjectType} not supported: {objectType}") - return None + return if result.isError(): self.logger.error(f"read error: {result} {objectType}.{address}.{slaveUnit} (address.slaveUnit) regCount:{registerCount}") - return None + return if objectType == 'Coil': value = result.bits[0] @@ -615,4 +558,3 @@ def __read_Registers(self, regPara): return decoder.decode_bits() else: self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}") - return None diff --git a/modbus_tcp/plugin.yaml b/modbus_tcp/plugin.yaml index 51c6cc90d..f479d08a1 100755 --- a/modbus_tcp/plugin.yaml +++ b/modbus_tcp/plugin.yaml @@ -11,13 +11,13 @@ plugin: keywords: modbus_tcp modbus smartmeter inverter heatpump #documentation: http://smarthomeng.de/user/plugins/modbus_tcp/user_doc.html support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1154368-einbindung-von-modbus-tcp - version: 1.0.12 # Plugin version - sh_minversion: 1.8 # minimum shNG version to use this plugin + version: 1.0.13 # Plugin version + sh_minversion: 1.10 # minimum shNG version to use this plugin #sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) py_minversion: 3.6 # py_maxversion: # maximum Python version to use for this plugin (leave empty if latest) - multi_instance: True # plugin supports multi instance - restartable: unknown + multi_instance: true # plugin supports multi instance + restartable: true classname: modbus_tcp # class containing the plugin parameters: @@ -26,6 +26,7 @@ parameters: description: de: 'IP Adresse des Modbus-Geraetes' en: 'IP address from the modbus-device' + mandatory: true port: type: int @@ -34,6 +35,7 @@ parameters: description: de: 'modbus Port' en: 'modbus port' + mandatory: true cycle: type: int @@ -41,7 +43,7 @@ parameters: valid_min: 0 description: de: 'Update Zyklus in Sekunden. Wenn der Wert 0 ist, wird keine Abfrage über cycle ausgeführt' - en: 'Update cycle in seconds. If value is 0 then noch query will be made by means of cycle' + en: 'Update cycle in seconds. If value is 0 then no query will be made by means of cycle' crontab: type: str @@ -50,12 +52,20 @@ parameters: en: 'Update by means of a crontab' slaveUnit: - type: num + type: int default: 1 description: de: 'Slave-Addresse der zu lesenden Modbus-Einheit' en: 'slave-address of the Modbus-Unit to read' + pause_item: + type: str + default: '' + description: + de: 'Item, um die Ausführung des Plugins zu steuern' + en: 'item for controlling plugin execution' + + item_attributes: modBusObjectType: type: str From b6fbeb387b9d650d5afdfc5f750294174cd9063c Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:57:00 +0200 Subject: [PATCH 8/8] zigbee2mqtt: fix pause item --- zigbee2mqtt/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/zigbee2mqtt/__init__.py b/zigbee2mqtt/__init__.py index 1becfd8ce..2256f9df9 100755 --- a/zigbee2mqtt/__init__.py +++ b/zigbee2mqtt/__init__.py @@ -162,15 +162,12 @@ def parse_item(self, item): can be sent to the knx with a knx write function within the knx plugin. """ - # remove this block when its included in smartplugin.py, - # replace with super().parse_item(item) - # check for alive item + # check for pause item if item.property.path == self._pause_item_path: - self.logger.debug(f'alive item {item.property.path} registered') + self.logger.debug(f'pause item {item.property.path} registered') self._pause_item = item self.add_item(item, updating=True) return self.update_item - # end block if self.has_iattr(item.conf, Z2M_ATTR): self.logger.debug(f"parsing item: {item}")